write-ahead-log

3 개의 포스트

netflix원문

넷플릭스에서 Write-Ahead (새 탭에서 열림)

넷플릭스는 대규모 데이터 환경에서 발생하는 데이터 손실, 시스템 엔트로피, 복제 및 재시도 메커니즘의 한계를 극복하기 위해 분산 **Write-Ahead Log(WAL)** 추상화 레이어를 구축했습니다. 이 시스템은 데이터 변경 사항을 캡처하고 강력한 내구성을 보장하며 하위 소비자에게 데이터를 안정적으로 전달하는 단일 인터페이스를 제공합니다. 결과적으로 개발자는 복잡한 데이터 정합성 문제를 직접 해결할 필요 없이 비즈니스 로직에 집중할 수 있게 되었으며, 플랫폼 전반의 탄력성과 운영 효율성이 크게 향상되었습니다. **WAL의 핵심 구조와 유연한 API** * **WriteToLog API:** 단순한 인터페이스를 통해 내부 구현을 추상화하며, 데이터 내구성을 '성공/실패/알 수 없음'의 세 가지 상태(Trilean)로 반환하여 신뢰성을 높였습니다. * **네임스페이스(Namespace):** 데이터의 저장 위치와 방식을 정의하는 논리적 격리 단위로, 설정에 따라 Kafka, SQS 등 다양한 기반 스토리지를 선택할 수 있습니다. * **페르소나 기반 아키텍처:** 네임스페이스 설정에 따라 지연 큐, 복제 도구, 인덱싱 도구 등 목적에 맞는 다양한 '페르소나'로 동작합니다. **지연 큐와 신뢰할 수 있는 재시도 메커니즘** * 네트워크 오류나 다운스트림 서비스 장애 발생 시 데이터 처리 처리량을 희생하지 않고도 실패한 메시지를 안전하게 재시도합니다. * SQS를 기본 스토리지로 활용하여 메시지 전달 시점을 조절하는 지연 기능을 구현함으로써 실시간 데이터 파이프라인의 안정성을 확보했습니다. **범용 교차 리전 복제 및 데이터 동기화** * Kafka를 활용하여 서로 다른 리전 간에 데이터를 복제하며, 기본적으로 복제를 지원하지 않는 스토리지 엔진에서도 리전 간 데이터 정합성을 유지할 수 있게 합니다. * Key-Value 저장소와 Elasticsearch 같은 서로 다른 데이터 저장소 간의 상태를 동기화하여 구체화된 뷰(Materialized Views)나 보조 인덱스를 안정적으로 구축합니다. **안정적인 데이터 삭제 및 부하 관리** * 데이터베이스에서 대량의 데이터를 삭제할 때 발생하는 메모리 부족(OOM) 문제를 해결하기 위해 WAL을 활용합니다. * 삭제 요청을 WAL에 기록한 후 처리 속도를 제어(Rate-limiting)하거나 예약된 시간에 실행함으로써 데이터베이스 노드에 가해지는 충격을 완화합니다. **시스템 설계 원칙과 격리 전략** * **수집 및 소비의 분리:** 고가용성 수집 레이어와 신뢰 중심의 소비 레이어를 분리하여 트래픽 급증이나 다운스트림 장애가 전체 시스템으로 전이되는 것을 방지합니다. * **멀티테넌시와 격리:** 공유 리소스를 사용하되 네임스페이스별로 격리된 리소스 풀을 할당하여 특정 작업이 다른 서비스의 성능에 영향을 주지 않도록 설계되었습니다. 데이터 플랫폼 차원의 통합 WAL 솔루션 도입은 각 서비스 팀이 개별적으로 구축하던 복제 및 재시도 로직의 중복을 제거하고 기술 부채를 크게 줄여줍니다. 대규모 분산 시스템을 운영하는 조직이라면 데이터의 최종 정합성과 시스템 탄력성을 확보하기 위해 이러한 추상화된 로그 계층을 검토하는 것이 권장됩니다.

figma3분 읽기큐레이션 요약

대규모 실시간 데이터를

Figma의 실시간 데이터 서비스 LiveGraph는 사용자와 쿼리 증가, 데이터베이스 샤딩으로 기존 구조의 한계에 도달했다. Figma는 초기 로컬 캐시와 단일 PostgreSQL의 전역 변경 스트림에 의존하던 아키텍처를 100배 규모까지 확장할 수 있도록 근본적으로 재설계하려 했다. 새 설계의 목표는 성능과 안정성을 유지하면서 데이터베이스 샤드와 읽기·업데이트 부하를 독립적으로 확장하고, 사용자 영향 없이 점진적으로 마이그레이션하는 것이다. ## LiveGraph의 역할 - LiveGraph는 GraphQL과 유사한 쿼리를 구독하는 웹 API를 제공한다. - 쿼리 결과를 JSON 트리로 반환하며, 객체와 관계를 정의한 스키마와 특정 그래프 일부를 조회하는 뷰를 사용한다. - Figma의 커스텀 React Hook을 통해 데이터가 변경되면 프런트엔드가 자동으로 다시 렌더링된다. - 캔버스 공동 편집, 댓글, FigJam 투표 등 여러 협업 기능에서 최신 데이터를 유지하는 기반 역할을 한다. ## 규모 증가로 드러난 문제 - 2021년 이후 LiveGraph 세션 수가 3배 증가했다. - 최근 1년 동안 뷰 요청 수는 5배 늘어났고, 세션 하나의 처리 비용도 점점 커졌다. - 데이터베이스 역시 단일 PostgreSQL 인스턴스에서 여러 수직·수평 샤드 구조로 변화하고 있었다. - 따라서 문제는 단순히 각 LiveGraph 서버가 데이터베이스 변경 사항을 모두 수집하는 데 그치지 않고, 클라이언트 세션·읽기 요청·데이터베이스 업데이트가 동시에 증가하는 복합적인 확장성 문제였다. ## LiveGraph 100x의 설계 목표 Figma는 현재의 읽기 및 데이터베이스 업데이트 부하를 장기적으로 100배까지 처리하기 위한 “LiveGraph 100x” 계획을 시작했다. - **서비스 속도 유지** - 초기 로드 시간과 실시간 업데이트에 대한 SLO를 유지하거나 개선해야 했다. - **데이터베이스 확장 지원** - 수직 확장뿐 아니라 수평 샤딩도 기본적으로 지원해야 했다. - 샤드가 늘어나도 신뢰성과 성능이 저하되지 않아야 했다. - **독립적인 확장 수단 확보** - 클라이언트 읽기량이 증가할 때와 쿼리 업데이트량이 증가할 때 서로 다른 구성 요소를 확장할 수 있어야 했다. - **안전한 점진적 마이그레이션** - 기존 LiveGraph 사용자를 중단시키지 않고 단계적으로 구조를 개선해야 했다. ## 초기 아키텍처와 변경 스트림 초기 LiveGraph는 단일 PostgreSQL과 하나의 서버를 중심으로 구성됐다. - PostgreSQL의 논리적 복제 스트림은 WAL에 기록된 행 단위 변경 사항을 전달한다. - 각 변경에는 행의 변경 전·후 이미지와 단조 증가하는 시퀀스 번호가 포함된다. - LiveGraph는 이 스트림을 추적해 데이터 변경을 실시간 업데이트로 재사용했다. - 모든 LiveGraph 쿼리는 기본 데이터베이스인 primary를 조회했다. - 서버 내부에는 인메모리 쿼리 캐시가 있었고, PostgreSQL의 각 행 변경이 발생할 때마다 관련 쿼리 결과를 직접 수정했다. - 즉, 캐시는 전체 결과를 다시 계산하기보다 개별 mutation을 결과에 반영하는 방식이었다. ## 단일 데이터베이스 구조의 한계 초기 구조에서는 데이터베이스가 하나였기 때문에 변경 스트림의 전역 순서를 가정할 수 있었다. - 모든 변경 사항이 하나의 PostgreSQL 인스턴스에서 생성됐다. - 따라서 LiveGraph는 하나의 전역적으로 정렬된 업데이트 스트림을 처리하면 됐다. - 하지만 단일 PostgreSQL 인스턴스가 용량 한계에 도달하면서 데이터베이스를 여러 수직 샤드로 나누게 됐다. - 여러 샤드가 동시에 변경 사항을 생성하면서 업데이트의 전역 순서가 더 이상 보장되지 않았다. - 기존의 “하나의 전역 순서 스트림”이라는 가정은 샤딩된 데이터베이스 환경에서 유지될 수 없었다. ## 재설계가 필요해진 이유 - LiveGraph는 데이터베이스 확장에 맞춰 변경 사항 수집과 캐시 갱신 방식을 바꿔야 했다. - 수직·수평 샤딩 환경에서는 여러 변경 스트림을 안정적으로 처리해야 한다. - 읽기 요청과 실시간 업데이트가 서로 다른 속도로 증가하므로, 전체 시스템을 한 방식으로만 확장해서는 효율적이지 않다. - Figma는 데이터베이스 용량 문제에 신속히 대응하면서도 기존 서비스의 성능과 안정성을 유지할 수 있는 전략적 변경이 필요했다. 실무적으로는 단일 데이터베이스의 전역 순서와 중앙 캐시에 의존하는 실시간 시스템이 초기에는 단순하고 효율적이지만, 샤딩 단계에서는 변경 순서·캐시 일관성·부하 분리 문제를 별도로 설계해야 한다는 점을 보여준다.

원문 읽기(새 탭에서 열림)
figma3분 읽기큐레이션 요약

멀티플레이어를 더 안정적으로

Figma는 인메모리 상태와 30~60초 간격의 체크포인트에 의존하던 멀티플레이어 시스템에 변경 이력을 기록하는 저널(write-ahead log)을 도입했다. 저널은 파일의 전체 상태가 아닌 증분 변경을 자주 저장하므로 장애 발생 시 최신 체크포인트 이후의 변경을 재생해 복구할 수 있으며, 목표 데이터 손실을 1초 미만으로 줄였다. 또한 배포 시 모든 파일을 동시에 체크포인트하는 쓰기 부하 급증도 해소했다. ## 기존 멀티플레이어 구조 - 브라우저 클라이언트는 WebSocket으로 `multiplayer` 서비스에 연결한다. - 서버는 파일 상태를 메모리에 보관하면서 여러 클라이언트의 변경 사항을 수신·검증·정렬·충돌 해결한 뒤 전체 클라이언트에 전달한다. - 메모리 상태는 휘발성이므로 30~60초마다 파일 전체를 바이너리로 인코딩하고 압축해 S3에 체크포인트로 저장한다. - 체크포인트는 버전 기록 등 일부 기능의 기반이 된다. ## 체크포인트 중심 방식의 문제점 - 서버가 장애를 일으키면 마지막 체크포인트 이후 최대 60초의 작업을 잃을 수 있다. - 파일 전체를 저장하므로 파일의 크기와 복잡도가 커질수록 저장 비용도 증가한다. - 멀티플레이어를 재배포하면 메모리에 있던 모든 파일을 닫아야 하므로 동시에 대량의 체크포인트 쓰기가 발생한다. - 이로 인해 데이터베이스 부하가 급증하고, 배포가 사용자에게 보이지 않는 작업이어야 한다는 목표를 방해한다. ## 증분 변경을 저장하는 저널 - Figma는 파일 변경 사항을 기록하는 내구성 있는 트랜잭션 로그인 저널을 추가했다. - 멀티플레이어가 변경을 수락하면 변경 내용을 비동기적으로 저널에 기록한다. - 각 변경에는 파일별로 증가하는 시퀀스 번호를 부여한다. - 체크포인트에도 해당 시점의 시퀀스 번호를 함께 저장한다. - 저널에는 전체 파일이 아니라 사용자가 수행한 증분 변경만 저장한다. - 예: 텍스트 수정, 디자인 요소의 위치 변경, 목업 업데이트 등 - 증분 변경은 전체 파일보다 훨씬 작기 때문에 더 자주 기록해도 효율적이다. ## 장애 복구 방식 - 서버가 재시작되면 기존 체크포인트를 먼저 불러온다. - 체크포인트의 시퀀스 번호보다 큰 시퀀스 번호를 가진 저널 항목을 조회한다. - 해당 변경들을 순서대로 재생해 최신 파일 상태를 복원한다. - 기존 체크포인트 방식은 약 60초 간격으로 저장했지만, 저널은 약 0.5초 수준으로 변경 사항을 기록하는 방향을 취한다. - 그 결과 장애 시 데이터 손실 목표를 1초 미만으로 낮췄다. ## 배포 시 쓰기 부하 안정화 - 배포할 때 모든 연결을 종료하고, 아직 저장되지 않은 변경이 저널에 기록될 때까지 기다린다. - 99번째 백분위수 기준으로 이 과정은 1초 이내에 완료된다. - 배포를 위해 대규모 체크포인트를 한꺼번에 생성할 필요가 없어졌다. - 저널 쓰기는 평상시에도 지속적으로 발생하므로 데이터베이스 부하가 일정하고 예측 가능해진다. ## 데이터 저장소 선택 - 저널의 백엔드 저장소로 DynamoDB를 사용했다. - Postgres와 로컬 디스크 등 여러 선택지를 검토했지만, 높은 쓰기량을 수평 확장해야 한다는 점 때문에 Postgres는 선택하지 않았다. - 이 사례에서는 익숙한 데이터베이스보다 쓰기 규모와 확장성을 감당할 수 있는 저장소가 더 중요한 기준이었다. ## 변경 사항 배치 처리 - 클라이언트는 초당 30프레임, 즉 약 33ms마다 업데이트를 보낸다. - 모든 업데이트를 같은 빈도로 저널에 기록할 필요는 없으므로 여러 변경 사항을 묶어 일정 주기로 저장한다. - 이 배치 처리는 저널 쓰기 횟수를 줄이고 성능을 개선하면서도 체크포인트보다 훨씬 짧은 복구 지연 시간을 유지하기 위한 방식이다. Figma의 사례는 전체 상태를 드물게 저장하는 체크포인트와, 작은 변경을 자주 저장하는 저널을 함께 사용하는 구조가 실시간 협업 시스템에 적합하다는 점을 보여준다. 장애 복구 시간과 데이터 손실을 줄이려면 증분 로그를 도입하고, 시퀀스 번호를 기준으로 체크포인트와 로그를 연결하는 방식을 고려할 수 있다.

원문 읽기(새 탭에서 열림)