Skip to content

redis를 통해 데이터베이스 쿼리 줄이기

ez edited this page Dec 1, 2024 · 8 revisions

현재 프로젝트 구조

현재 저희 프로젝트에서는 실시간 동시 편집을 구현하기 위해 yjs라는 라이브러리와 socket.io를 사용 중입니다.

자세한 설명

아래 그림에서 볼 수 있듯 yjs 라이브러리는 충돌 문제를 해결해주고 클라이언트마다 YDoc이라는 Document를 공유하여 실시간 문서 편집을 구현할 수 있습니다.

image

yjs는 내부적으로 CRDT라는 알고리즘을 사용합니다.

CRDT 알고리즘은 OT 알고리즘과 달리 중앙 서버의 부하를 줄이고 클라이언트 간 P2P 연결을 통해 실시간 동시 편집을 할 수 있게 해주는 알고리즘입니다.

하지만 저희 프로젝트의 경우 문서의 내용을 데이터베이스에 저장하는 과정이 필요했습니다.

다행히 yjs 라이브러리와 socket.io를 사용하면 client들이 공유하는 YDoc이라는 Document를 서버와 함께 사용할 수 있습니다.

즉, 클라이언트끼리 동시 편집을 진행하여 YDoc의 변경 사항이 발생하면 서버에서 이를 감지하여 데이터베이스에 저장할 수 있습니다.

가장 단순하게 구현을 한다면 YDoc의 변경 사항이 발생할 때마다 변경 사항을 데이터베이스에 반영하면 됩니다.

가장 처음에 생각한 방식은 rabbitMQ와 같은 message queue를 활용하는 방식이었습니다.

주기적인 영속화

그렇다면 어떻게하면 주기적으로 데이터베이스에 영속화 할 수 있을까요?

가장 단순한 방식을 생각하면 자바스크립트의 setInterval 함수를 사용할 수 있을 것 같습니다.

1. setInterval로 주기적인 영속화

setInterval을 통해 주기적으로 Y Document의 모든 값을 가져와서 데이터베이스에 저장하는 방식입니다.

image

하지만 다음과 같은 문제점이 존재합니다.

첫 번째, 변경 사항이 없는 문서도 갱신이 발생합니다.

저희 프로젝트는 다양한 문서가 존재할 수 있기 때문에 변경 사항이 없는 문서의 갱신이 발생한다면 굉장히 비효율적인 작업이 됩니다.

image

그렇다고 매번 문서마다 변경 사항 여부를 판단해서 데이터베이스에 저장한다면 역시 비효율적인 작업이 될 것이라고 생각했습니다.

두 번째, WAS가 중단된다면 interval 동안 발생한 변경 사항이 모두 날아가게 됩니다.

WAS의 변경 사항을 새로 배포하기 위해 서버를 내린다면 interval 동안 발생한 변경 사항은 데이터베이스에 반영되지 않습니다.

image

즉, 저희는 두 가지를 구현해야 했습니다.

  1. 오직 변경된 문서만 감지하여 데이터베이스에 반영합니다.
  2. WAS가 중단되더라도 변경 사항을 따로 저장해두었다가 WAS가 다시 실행되면 해당 변경 사항을 데이터베이스에 반영합니다.

처음에는 rabbiMQ와 같은 message queue를 사용하려고 했습니다.

rabbiMQ?

rabbitMQ는 AMQP를 구현한 오픈소스 메세지 브로커입니다.

클라이언트가 메시지를 전달할 때 직접적으로 전달하지 않고 특정 주제로 rabbitMQ에게 대신 전달해주면 해당 주제를 구독 중인 구독자에게 메시지를 전달할 수 있습니다.

rabbitMQ를 사용하면 위에서 나온 두 가지 문제점을 해결할 수 있습니다.

첫 번째, 클라이언트가 변경된 페이지 식별자와 변경된 내용을 큐에 넣어주기만 한다면 이를 구독 중인 서버에서 변경된 문서의 내용만 데이터베이스에 반영할 수 있습니다.

두 번째, WAS와 독립적으로 운영되는 서비스이기 때문에 WAS가 멈추더라도 메시지 정보는 여전히 큐에 남아있고 WAS가 다시 동작할 때 메시지들을 잃어버리지 않고 데이터베이스에 반영할 수 있습니다.

image

저희의 목적 달성을 위해 rabbitMQ를 도입하는 것은 합리적인 것처럼 보입니다.

하지만 저희 프로젝트 특성을 고려했을 때 rabbitMQ는 약간 비효율적일 수도 있다고 생각했습니다.

그 이유는 바로 rabbitMQ를 사용했을 때 모든 변경 사항을 데이터베이스에 반영하기 때문입니다.

변경 사항이 발생할 때마다 데이터베이스에 반영한다면?

변경 사항이 발생할 때마다 데이터베이스에 반영한다면 당연하게도 데이터베이스에 부하가 많이 발생합니다.

특히 문서 편집 특성 상 타이핑이 발생할 때마다 변경 사항이 발생합니다.

예를 들어 100명의 사람이 한 문서에서 한 번씩 타이핑 한다면 총 100개의 갱신 쿼리가 발생하게 됩니다.

즉 저희는 이 갱신 쿼리의 수를 줄여야 했습니다.

그런데 문서의 특성을 살펴본다면 모든 변경 사항에 대해서 데이터베이스에 갱신을 할 필요는 없습니다.

아래 그림처럼 여러 번의 변경이 발생했다면 가장 마지막에 있는 값만 저장해도 충분합니다.

image

물론 redo나 undo 기능을 사용한다면 변경 내역이 필요할지도 모릅니다.

하지만 yjs 라이브러리 자체에서 redo나 undo 기능을 제공해주고 있고 클라이언트에서 redo나 undo가 발생하면 서버 입장에서는 Document가 변경했다고 인식하기 때문에 굳이 변경 내역을 저장할 필요는 없습니다.

즉, 모든 변경 사항을 반영할 필요 없이 주기적으로 최신 문서 내용만 갱신하면 됩니다

이를 구현하기 위해 저희는 redis를 도입하기로 했습니다.

redis 도입

redis는 디스크 데이터베이스에 비해 속도가 굉장히 빠른 key-value 쌍을 저장할 수 있는 인메모리 데이터베이스입니다.

그리고 redis에는 다양한 데이터 타입을 저장할 수 있도록 지원합니다.

특히 hset이라는 데이터를 제공해주는데 이는 O(1)의 시간 복잡도로 빠르게 데이터를 찾을 수 있고 내부에 field-value 쌍을 저장할 수 있습니다.

JSON이랑 비슷한 형태로 저장할 수 있기 때문에 페이지 정보를 표현하기 좋다고 생각했습니다.

그렇다면 redis를 어떻게 활용하면 모든 변경 사항을 반영하지 않고 최신 값만 반영할 수 있을까요?

개발 문서

⚓️ 사용자 피드백과 버그 기록
👷🏻 기술적 도전
📖 위키와 학습정리
🚧 트러블슈팅

팀 문화

🧸 팀원 소개
⛺️ 그라운드 룰
🍞 커밋 컨벤션
🧈 이슈, PR 컨벤션
🥞 브랜치 전략

그룹 기록

📢 발표 자료
🌤️ 데일리 스크럼
📑 회의록
🏖️ 그룹 회고
🚸 멘토링 일지

Clone this wiki locally