Design Notion, 수천만 명이 실시간으로 편집하는 문서 시스템은 어떻게 설계될까?
“모든 것은 블록이다.” 이 단순한 UI 철학을 백엔드에서 구현하기 위해 겪어야 하는 분산 시스템의 지옥.
이번에는 실시간 협업 기반의 글로벌 생산성 툴인 Notion(노션) 같은 대규모 문서 및 지식 관리 플랫폼을 설계하는 문제를 다룹니다.
노션은 사용자가 페이지를 만들고, 텍스트, 이미지, 표, 데이터베이스 등 다양한 요소를 자유롭게 배치하며, 동료들과 실시간으로 동시에 문서를 편집할 수 있는 서비스입니다.
겉으로 보면 “웹에서 글을 쓰는 에디터” 같지만, 시스템 디자인 관점에서 노션은 단순한 텍스트 에디터가 아닙니다. 거대한 분산 트리(Tree) 데이터베이스이자, 상태 동기화(State Synchronization) 엔진이라고 할 수 있습니다.
사용자가 노션에서 타이핑을 하는 순간, 시스템은 단순한 긴 텍스트를 저장하는 것이 아닙니다.
“이 문장은 block_id: 123이고, 부모는 page_id: 456이며, 현재 버전은 v10인데, 마침 미국에 있는 동료가 같은 텍스트 블록을 동시에 수정했네? 둘의 충돌을 어떻게 병합(Merge)하지?”
AI가 코드를 짜주는 시대에, 시니어 엔지니어의 가치는 단순히 REST API를 만드는 데 있지 않습니다.
“왜 문서 전체를 통째로 저장하지 않고 수십 개의 블록(Block) 단위로 쪼개어 저장해야 하는지”
“다수의 사용자가 동일한 문장을 동시에 수정할 때, 어떻게 데이터 유실 없이 화면을 실시간으로 동기화(CRDT/OT)할 수 있는지”
“부모 페이지의 권한이 바뀌었을 때 수만 개의 하위 블록들의 읽기 권한을 어떻게 1초 만에 제어할지”
Notion 같은 협업 문서 시스템은 이러한 “상태 충돌(Conflict) 해결”과 “계층형 데이터(Tree) 처리” 역량을 훈련하기 가장 완벽한 문제이기 때문에 이 컨텐츠를 제작했습니다. 이 글을 끝까지 읽으면 다음 질문에 답할 수 있게 됩니다.
노션의 핵심인 ‘블록(Block) 아키텍처’를 데이터베이스로 어떻게 모델링할까?
5명이 동시에 같은 단락을 수정할 때 발생하는 충돌을 CRDT를 통해 어떻게 우아하게 해결할까?
수백만 개의 블록을 트리 구조로 렌더링할 때 발생하는 DB 쿼리 병목(N+1 문제)을 어떻게 피할까?
오프라인 상태에서 비행기 안에서 작성한 문서가, 온라인이 되었을 때 어떻게 완벽히 동기화될까?
노션의 무한히 깊어지는 페이지 트리에서 ‘검색 권한 필터링’을 어떻게 고속으로 처리할까?
Functional Requirements - 기능적 요구사항 (Notion)
문서는 통짜 텍스트가 아닌 텍스트, 이미지, 체크리스트 등의 ‘블록(Block)’ 단위로 생성, 수정, 삭제(CRUD) 되어야 합니다.
페이지 안에 페이지를 무한히 중첩하여 생성할 수 있어야 합니다.
여러 사용자가 동일한 페이지, 심지어 동일한 블록을 동시에 수정해도 지연 없이 실시간으로 반영되어야 합니다.
인터넷이 끊긴 상태에서도 문서를 읽고 편집할 수 있으며, 네트워크가 연결되면 자동으로 서버와 동기화되어야 합니다.
사용자는 권한이 있는 워크스페이스 내의 모든 페이지와 블록 내용을 빠르게 검색할 수 있어야 합니다.
Non-Functional Requirements - 비기능적 요구사항
High Availability & Durability
지식 베이스(Wiki)이므로 데이터 유실은 절대 허용되지 않습니다.
Eventual Consistency (실시간 동기화 영역)
여러 사용자가 동시 편집 시, 찰나의 순간에는 화면이 다를 수 있으나 궁극적으로는 모든 사용자가 완벽히 동일한 문서 상태를 보게 되어야 합니다(Strong Eventual Consistency).
Low Latency (Read/Write)
사용자가 타이핑하는 순간 로컬 반응은 0ms에 가까워야 하며, 동료에게 전파되는 시간은 수백 밀리초(ms) 이내여야 합니다.
Scope 정리
In Scope
블록 데이터 모델링, 실시간 동기화(CRDT), 트리 구조 렌더링 및 페이지 조회, 오프라인 로컬 캐싱, 검색 시스템.
Out of Scope
노션 데이터베이스(표, 칸반보드)의 롤업/포뮬라 연산 엔진 상세, 타사 툴 연동(Integration), 결제 시스템.
가정하는 규모
DAU: 3,000만 명
Concurrent Users (Peak): 500만 명
Blocks Written per day: 50억 건 (사용자 타이핑과 블록 생성이 잦음)
Peak QPS: * Block Write/Sync: 수십만 QPS (WebSocket을 통한 상태 동기화)
Page Read: 수만 QPS
Latency: 로컬 렌더링 즉시, 네트워크 동기화 < 500ms
Core Entities
Notion의 데이터 모델은 일반적인 블로그나 게시판과는 완전히 다릅니다. HTML 문서 통째로 DB에 넣는 방식(Anti-pattern)이 아니라, 모든 것을 잘게 쪼갠 그래프/트리(Graph/Tree) 데이터베이스에 가깝다고 할 수 있습니다.
workspaces
워크스페이스(회사/조직) 기본 정보. 노션 시스템 샤딩(Sharding)의 가장 핵심적인 기준(Partition Key)이 됩니다.
users
사용자 정보.
blocks (가장 핵심적이고 거대한 테이블)
노션의 심장입니다. 페이지도 블록이고, 텍스트 단락도 블록이며, 이미지도 블록입니다.
id: UUID (분산 환경에서 클라이언트가 직접 생성)workspace_id: 샤딩을 위한 파티션 키parent_id: 이 블록을 감싸고 있는 부모 블록(또는 페이지)의 ID. 이를 통해 트리 구조를 만듭니다.block_type:page,text,h1,image,toggle등properties: JSONB 형태의 페이로드 (텍스트 내용, 폰트 색상, 이미지 URL 등)version: 낙관적 락(Optimistic Locking) 및 동기화를 위한 버전 번호created_at,updated_at,created_by,last_edited_by
permissions (권한 테이블)
특정 블록(주로 페이지 레벨)에 대한 사용자/그룹의 읽기/쓰기 권한을 정의합니다.
API Signatures & Deep Dives
단순한 REST API가 아니라, 실시간 상태 동기화(State Sync)와 트리 탐색이 결합된 형태입니다.
1. Page Load API, 페이지 읽기 - 계층형 데이터 로드
사용자가 특정 페이지를 클릭했을 때, 서버는 해당 페이지와 그 안의 내용물(자식 블록들)을 어떻게 가져올까요?
POST /v1/sync/load_page
{
"page_id": "block_12345",
"limit": 50
}안티 패턴의 함정, WITH RECURSIVE가 DB를 죽이는 이유
주니어 엔지니어들이 노션 클론 코딩을 할 때 가장 많이 하는 실수는, RDBMS의 트리 탐색 기능인 WITH RECURSIVE (재귀 CTE)를 맹신하는 것입니다.
상황 가정
어느 회사에 “2026년 프로젝트 총람”이라는 최상위 페이지가 있습니다. 그 안에는 부서별 하위 페이지가 20개 있고, 각 하위 페이지 안에는 수백 개의 토글(Toggle)과 표(Table), 텍스트 블록들이 중첩되어 있습니다. 총 블록 수는 50,000개입니다.
재귀 쿼리를 쓴다면?


