-
Notifications
You must be signed in to change notification settings - Fork 3
redis를 통해 데이터베이스 쿼리 줄이기
현재 저희 프로젝트에서는 실시간 동시 편집을 구현하기 위해 yjs라는 라이브러리와 socket.io를 사용 중입니다.
아래 그림에서 볼 수 있듯 yjs 라이브러리는 충돌 문제를 해결해주고 클라이언트마다 YDoc이라는 Document를 공유하여 실시간 문서 편집을 구현할 수 있습니다.
yjs는 내부적으로 CRDT라는 알고리즘을 사용합니다.
CRDT 알고리즘은 OT 알고리즘과 달리 중앙 서버의 부하를 줄이고 클라이언트 간 P2P 연결을 통해 실시간 동시 편집을 할 수 있게 해주는 알고리즘입니다.
하지만 저희 프로젝트의 경우 문서의 내용을 데이터베이스에 저장하는 과정이 필요했습니다.
다행히 yjs 라이브러리와 socket.io를 사용하면 client들이 공유하는 YDoc이라는 Document를 서버와 함께 사용할 수 있습니다.
즉, 클라이언트끼리 동시 편집을 진행하여 YDoc의 변경 사항이 발생하면 서버에서 이를 감지하여 데이터베이스에 저장할 수 있습니다.
가장 단순하게 구현을 한다면 YDoc의 변경 사항이 발생할 때마다 변경 사항을 데이터베이스에 반영하면 됩니다.
가장 처음에 생각한 방식은 rabbitMQ와 같은 message queue를 활용하는 방식이었습니다.
그렇다면 어떻게하면 주기적으로 데이터베이스에 영속화 할 수 있을까요?
가장 단순한 방식을 생각하면 자바스크립트의 setInterval 함수를 사용할 수 있을 것 같습니다.
setInterval을 통해 주기적으로 Y Document의 모든 값을 가져와서 데이터베이스에 저장하는 방식입니다.
하지만 다음과 같은 문제점이 존재합니다.
첫 번째, 변경 사항이 없는 문서도 갱신이 발생합니다.
저희 프로젝트는 다양한 문서가 존재할 수 있기 때문에 변경 사항이 없는 문서의 갱신이 발생한다면 굉장히 비효율적인 작업이 됩니다.
그렇다고 매번 문서마다 변경 사항 여부를 판단해서 데이터베이스에 저장한다면 역시 비효율적인 작업이 될 것이라고 생각했습니다.
두 번째, WAS가 중단된다면 interval 동안 발생한 변경 사항이 모두 날아가게 됩니다.
WAS의 변경 사항을 새로 배포하기 위해 서버를 내린다면 interval 동안 발생한 변경 사항은 데이터베이스에 반영되지 않습니다.
즉, 저희는 두 가지를 구현해야 했습니다.
- 오직 변경된 문서만 감지하여 데이터베이스에 반영합니다.
- WAS가 중단되더라도 변경 사항을 따로 저장해두었다가 WAS가 다시 실행되면 해당 변경 사항을 데이터베이스에 반영합니다.
처음에는 rabbiMQ와 같은 message queue를 사용하려고 했습니다.
rabbitMQ는 AMQP를 구현한 오픈소스 메세지 브로커입니다.
클라이언트가 메시지를 전달할 때 직접적으로 전달하지 않고 특정 주제로 rabbitMQ에게 대신 전달해주면 해당 주제를 구독 중인 구독자에게 메시지를 전달할 수 있습니다.
rabbitMQ를 사용하면 위에서 나온 두 가지 문제점을 해결할 수 있습니다.
첫 번째, 클라이언트가 변경된 페이지 식별자와 변경된 내용을 큐에 넣어주기만 한다면 이를 구독 중인 서버에서 변경된 문서의 내용만 데이터베이스에 반영할 수 있습니다.
두 번째, WAS와 독립적으로 운영되는 서비스이기 때문에 WAS가 멈추더라도 메시지 정보는 여전히 큐에 남아있고 WAS가 다시 동작할 때 메시지들을 잃어버리지 않고 데이터베이스에 반영할 수 있습니다.
저희의 목적 달성을 위해 rabbitMQ를 도입하는 것은 합리적인 것처럼 보입니다.
하지만 저희 프로젝트 특성을 고려했을 때 rabbitMQ는 약간 비효율적일 수도 있다고 생각했습니다.
그 이유는 바로 rabbitMQ를 사용했을 때 모든 변경 사항을 데이터베이스에 반영하기 때문입니다.
변경 사항이 발생할 때마다 데이터베이스에 반영한다면 당연하게도 데이터베이스에 부하가 많이 발생합니다.
특히 문서 편집 특성 상 타이핑이 발생할 때마다 변경 사항이 발생합니다.
예를 들어 100명의 사람이 한 문서에서 한 번씩 타이핑 한다면 총 100개의 갱신 쿼리가 발생하게 됩니다.
즉 저희는 이 갱신 쿼리의 수를 줄여야 했습니다.
그런데 문서의 특성을 살펴본다면 모든 변경 사항에 대해서 데이터베이스에 갱신을 할 필요는 없습니다.
아래 그림처럼 여러 번의 변경이 발생했다면 가장 마지막에 있는 값만 저장해도 충분합니다.
물론 redo나 undo 기능을 사용한다면 변경 내역이 필요할지도 모릅니다.
하지만 yjs 라이브러리 자체에서 redo나 undo 기능을 제공해주고 있고 클라이언트에서 redo나 undo가 발생하면 서버 입장에서는 Document가 변경했다고 인식하기 때문에 굳이 변경 내역을 저장할 필요는 없습니다.
즉, 모든 변경 사항을 반영할 필요 없이 주기적으로 최신 문서 내용만 갱신하면 됩니다
이를 구현하기 위해 저희는 redis를 도입하기로 했습니다.
redis는 디스크 데이터베이스에 비해 속도가 굉장히 빠른 key-value 쌍을 저장할 수 있는 인메모리 데이터베이스입니다.
그리고 redis에는 다양한 데이터 타입을 저장할 수 있도록 지원합니다.
특히 hset이라는 데이터를 제공해주는데 이는 O(1)의 시간 복잡도로 빠르게 데이터를 찾을 수 있고 내부에 field-value 쌍을 저장할 수 있습니다.
JSON이랑 비슷한 형태로 저장할 수 있기 때문에 페이지 정보를 표현하기 좋다고 생각했습니다.
그렇다면 redis를 어떻게 활용하면 모든 변경 사항을 반영하지 않고 최신 값만 반영할 수 있을까요?
가장 먼저 redis에는 오직 변경 사항만 저장합니다.
해당 페이지가 이미 redis에 존재한다면 페이지 아이디에 해당하는 key에 값을 덮어 씌워버리는 거죠
그리고 주기적으로 redis에 있는 모든 값을 제거한 뒤 데이터베이스에 반영합니다.
이렇게 구현하면 위에서 언급했던 두 가지 문제점과 rabbitMQ의 문제점을 보완할 수 있습니다.
첫 번째, 데이터를 반영하고 나서 redis의 값을 삭제하기 때문에 일정 주기 동안 변경 사항이 없는 문서는 데이터베이스에 반영하지 않습니다.
두 번째, redis 역시 WAS와 독립적인 별도의 서비스이기 때문에 WAS가 멈추더라도 변경 사항은 여전히 redis에 남아있습니다.
세 번째, 무조건 최신 변경 사항으로 덮어 씌우기 때문에 모든 변경 사항이 아니라 특정 시점의 가장 최신 변경 사항만 반영합니다.
이렇게 저희 Octodocs 팀은 redis를 적용하여 데이터베이스에 걸리는 부하를 많이 줄일 수 있었습니다.
다음 포스팅에는 주기적인 데이터 영속화를 구현하면서 발생했던 여러가지 문제점과 해결 방안에 대해 작성해보겠습니다.
읽어주셔서 감사합니다!
⚓️ 사용자 피드백과 버그 기록
👷🏻 기술적 도전
📖 위키와 학습정리
✏️ 에디터
Novel이란?
Novel 스타일링 문제
에디터 저장 및 고려 사항들
📠 실시간 협업, 통신
Yorkie와 Novel editor 연동
YJS, Websocket, React-Flow
YJS, Socket.io
WebSocket과 Socket.io에 대해 간단히 알아보기
YJS 가이드 근데 이제 Socket.io를 곁들인
🏗️ 인프라와 CI/CD
NCloud CI CD 구축
BE 개발 스택과 기술적 고민
private key로 원격 서버 접근
nCloud 서버, VPC 만들고 설정
monorepo로 변경
⌛ 캐시, 최적화
rabbit mq 사용법
🔑 인증, 인가, 보안
passport로 oAuth 로그인 회원가입 구현
FE 로그인 기능 구현
JWT로 인증 인가 구현
JWT 쿠키로 사용하기
refresh token 보완하기
🧸 팀원 소개
⛺️ 그라운드 룰
🍞 커밋 컨벤션
🧈 이슈, PR 컨벤션
🥞 브랜치 전략
🌤️ 데일리 스크럼
📑 회의록
1️⃣ 1주차
킥오프(10/25)
2일차(10/29)
3일차(10/30)
4일차(10/31)
2️⃣ 2주차
8일차(11/04)
9일차(11/05)
11일차(11/07)
13일차(11/09)
3️⃣ 3주차
3주차 주간계획(11/11)
16일차(11/12)
18일차(11/14)
4️⃣ 4주차
4주차 주간계획(11/18)
23일차(11/19)
24일차(11/20)
25일차(11/21)
5️⃣ 5주차
5주차 주간계획(11/25)
29일차(11/25)
32일차(11/28)
34일차(11/30)
6️⃣ 6주차
6주차 주간계획(12/2)
37일차(12/3)