Skip to content

필사 모드: 실시간 협업 & CRDT 2026 심층 가이드 — Liveblocks·PartyKit·Yjs·Automerge·Loro·ShareDB·Replicache·Fluid·Tldraw sync

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

프롤로그 — 2026년, "동시에 편집한다"는 것의 의미

2010년 Google Docs가 "동시에 같은 문서를 편집한다"를 일반 사용자에게 선물했을 때, 그 기술은 OT(Operational Transform)였다. 한 사람의 편집 연산을 다른 사람의 편집 연산에 맞춰 "변환"하는 방식. 동작은 했지만, 서버 중심이었고, 오프라인은 어려웠고, 잘못된 변환 함수는 영원히 디버깅해야 했다.

2026년 5월, 풍경이 완전히 바뀌었다. **CRDT(Conflict-free Replicated Data Type)** 가 웹 협업의 기본 단위가 되었다. Figma의 멀티플레이어 인프라, Linear의 로컬-퍼스트 동기화, Notion의 라이브 커서, tldraw의 실시간 캔버스, Excalidraw의 룸 동기화 — 이 모든 것이 CRDT 또는 그 변형 위에 서 있다.

그리고 더 큰 변화 두 가지가 더 있다.

- **2025년 11월**, Vercel이 Liveblocks와 PartyKit을 잇따라 협력 파트너로 끌어들이며 "Vercel for Realtime"이라는 포지셔닝을 굳혔다.

- **Ink & Switch 연구실**이 발표한 "Local-first software" 매니페스토는 더 이상 학술 호기심이 아니다. Automerge 2.x·Loro·Yrs(Yjs의 Rust 포트)가 실서비스에 들어갔다.

이 글은 2026년의 실시간 협업 스택을 한 호흡으로 정리한다. 이론(CRDT vs OT, 벡터 클럭, Lamport)부터 라이브러리 비교(Yjs vs Automerge vs Loro), SaaS 비교(Liveblocks vs PartyKit vs Hocuspocus), 운영 패턴(WebSocket vs WebRTC, awareness, 영속화)까지.

1장 · 동시 편집의 본질 — 같은 문서, 다른 시간선

먼저 문제를 정의하자. 두 사람이 같은 문서를 동시에 편집한다는 건, 컴퓨터과학적으로는 "같은 상태에 두 개의 독립적인 연산이 가해진다"는 뜻이다.

시각 t0: "Hello" (서버 = 클라A = 클라B)

시각 t1: 클라A가 5번 위치에 ", world" 삽입

시각 t1: 클라B가 0번 위치에 "Say: " 삽입

(서로 모름)

시각 t2: 두 연산이 서버에서 만남

-> 결과가 무엇이어야 하나?

답은 두 가지다. **OT**는 "두 연산을 서로의 좌표계에 맞게 변환"한다. **CRDT**는 "좌표를 처음부터 절대적·고유한 ID로 매기고 연산을 그대로 적용"한다.

OT는 서버라는 단일 진실의 원천을 가정하지만, CRDT는 서버 없이도 모든 클라이언트가 결국 같은 상태에 수렴한다(이를 **eventual consistency**라 한다). 로컬-퍼스트 소프트웨어의 전제 조건이 바로 이것이다.

2장 · CmRDT vs CvRDT — 연산이냐 상태냐

CRDT는 크게 두 종류로 나뉜다.

- **CmRDT(Operation-based)**: 연산(operation)을 전파한다. "5번 위치에 'x' 삽입"이라는 메시지를 보낸다. 메시지 크기는 작지만, **정확히 한 번 전달(exactly-once)** 또는 인과 순서(causal order) 보장이 필요하다.

- **CvRDT(State-based)**: 상태(state) 전체나 일부를 전파한다. 동기화 시점에 머지(merge) 함수가 두 상태를 결합한다. 머지 함수는 **교환·결합·멱등(commutative, associative, idempotent)** 이어야 한다.

Yjs와 Automerge는 둘 다 op-based로 시작했지만, 2026년의 Loro·Y-CRDT·Automerge 2.x는 **delta-state** 또는 하이브리드 모델로 진화했다. 변경분만 보내되, 멱등하게 처리할 수 있는 형태로.

| 종류 | 메시지 크기 | 네트워크 보장 | 대표 라이브러리 |

| --- | --- | --- | --- |

| CmRDT | 작음 | 인과 순서 + at-least-once | 초기 Treedoc, RGA |

| CvRDT | 큼 | best-effort, 멱등 | 초기 Riak DT |

| Delta-state | 중간 | 멱등 + 인과 메타 | Yjs, Automerge 2.x, Loro |

3장 · OT vs CRDT — 왜 CRDT가 이겼나

| 기준 | OT | CRDT |

| --- | --- | --- |

| 중앙 서버 필요 | 사실상 필요 | 불필요 (P2P 가능) |

| 오프라인 편집 | 어려움 | 자연스러움 |

| 변환 함수 검증 | 매우 어려움(TP2 문제) | 불필요 |

| 메모리 사용 | 작음 | 큼 (메타데이터 누적) |

| 텍스트 CRDT 성숙도 | 2000년대 검증됨 | 2020년대 검증됨 |

| 대표 사용처 | Google Docs (역사적) | Figma, Linear, Notion 라이브 커서 |

Google Docs는 여전히 OT를 쓴다(레거시 + 검증된 인프라). 하지만 2020년대 이후 신규 협업 앱은 거의 모두 CRDT를 택했다. 가장 큰 이유는 **오프라인 지원**과 **P2P 가능성**이다.

4장 · 벡터 클럭과 Lamport timestamp — 분산 시간의 두 얼굴

분산 시스템에서는 절대 시간이 없다. 그래서 두 가지 도구가 쓰인다.

- **Lamport timestamp**: 모든 이벤트에 단조 증가하는 정수를 부여한다. 두 노드의 시간을 비교하려면 max + 1. 전순서(total order)는 만들 수 있지만, 두 이벤트가 동시인지(concurrent) 알 수 없다.

- **Vector clock**: 노드별 카운터를 모두 들고 다닌다. `[A:3, B:2, C:1]`. 두 벡터를 비교하면 "선행/후행/동시" 셋 중 하나를 확정할 수 있다.

Yjs는 **각 클라이언트가 고유 ID를 받고, ID + 단조 시퀀스**의 조합으로 연산을 식별한다 (사실상 Lamport + client-id). Automerge는 액터(actor) ID와 시퀀스 번호의 조합 (벡터 클럭의 변형)을 쓴다. Loro도 actor + counter 모델.

5장 · 텍스트 CRDT 이론 — Yjs Y.Text의 내부

텍스트는 CRDT 중에서 가장 어렵다. "5번 위치에 'x' 삽입"이라는 인덱스 기반 연산은 동시 편집에서 무너진다(다른 사람이 4번에 뭔가 끼우면 5번이 6번이 된다). 그래서 CRDT는 **고유 ID 기반 위치 지정**을 쓴다.

Yjs의 Y.Text는 모든 문자에 `(clientID, clock)` 쌍을 부여하고, 이를 **이중 연결 리스트(double-linked list of Items)** 로 잇는다. 삽입은 "이 ID 다음에 끼워라"가 되고, 삭제는 tombstone(삭제 표시)으로 남는다. 동일 위치 동시 삽입은 clientID로 결정적 순서를 만든다.

// Yjs: 같은 문서를 두 사람이 편집한다

// 클라이언트 A

const docA = new Y.Doc()

const textA = docA.getText('content')

textA.insert(0, 'Hello')

// 클라이언트 B (오프라인에서)

const docB = new Y.Doc()

const textB = docB.getText('content')

Y.applyUpdate(docB, Y.encodeStateAsUpdate(docA))

textB.insert(5, ', world')

// 동시에, 클라이언트 A에서

textA.insert(0, 'Say: ')

// 양쪽 변경분을 서로에게 전달

Y.applyUpdate(docA, Y.encodeStateAsUpdate(docB))

Y.applyUpdate(docB, Y.encodeStateAsUpdate(docA))

// 두 문서가 같은 상태로 수렴

console.log(textA.toString()) // "Say: Hello, world"

console.log(textB.toString()) // "Say: Hello, world"

이 코드의 핵심은 `encodeStateAsUpdate`와 `applyUpdate`다. 어떤 순서로, 몇 번을, 어떤 경로로 적용하든 결과는 같다. 멱등하고, 교환 가능하고, 결합 가능하다.

6장 · Yjs의 내부 구조 — Item 링크드 리스트와 GC

Yjs의 핵심 자료구조는 `Item`이다. 각 Item은:

- `id: { client: number, clock: number }` — 고유 식별자

- `origin: ID | null` — "이 Item이 어느 Item 다음에 삽입되었는가"

- `rightOrigin: ID | null` — 동시 삽입 충돌 해결용

- `content: ContentString | ContentEmbed | ...` — 실제 값

- `deleted: boolean` — tombstone 플래그

Yjs는 메모리 폭증을 막기 위해 **GC**를 한다. 모든 클라이언트가 본 삭제는 tombstone에서 실제 제거된다. 또한 인접한 동일-클라이언트 Item을 **블록 단위로 압축**한다(예: 한 번에 'Hello'를 친 5개 Item을 하나로).

성능 벤치마크에서 Yjs는 일관되게 가장 빠른 텍스트 CRDT 중 하나다. 100만 글자 문서에서도 메모리·CPU가 실용 수준에 머문다.

7장 · Automerge — JSON CRDT의 표준

Automerge는 Yjs와 같은 시기에 시작했지만 **JSON 문서 전체**를 CRDT로 다루는 데 초점을 둔다. 텍스트도 지원하지만(`Automerge.Text` → 최근에는 `splice` 기반 API), 강점은 깊게 중첩된 객체 트리의 머지다.

// Automerge: 같은 JSON 문서를 두 사람이 편집한다

// 초기 문서

let doc1 = from({

title: 'Realtime collab notes',

todos: [{ done: false, text: 'Write CRDT post' }],

})

// 직렬화 후 다른 클라이언트로 전달

const binary = save(doc1)

let doc2 = load(binary)

// 양쪽 동시 편집

doc1 = change(doc1, (d) => {

d.todos.push({ done: false, text: 'Add references' })

d.title = 'Realtime collab notes (draft)'

})

doc2 = change(doc2, (d) => {

d.todos[0].done = true

})

// 양쪽 머지

const merged1 = merge(doc1, doc2)

const merged2 = merge(doc2, doc1)

// 두 결과는 동일

console.log(JSON.stringify(merged1) === JSON.stringify(merged2)) // true

Automerge 2.x는 Rust로 다시 쓰였고(`automerge-rs`), WASM으로 컴파일되어 브라우저·Node·Electron에서 모두 돈다. 메모리·속도가 1.x 대비 한 자릿수 좋아졌다.

8장 · Loro — 2024-2026의 다크호스

Loro는 중국 발 신생 CRDT 라이브러리로, 2024년에 1.0을 찍고 2026년에는 Yjs·Automerge와 함께 "큰 세 개"로 묶인다. 특징은:

- 처음부터 Rust + WASM

- **시간 여행(time travel)** 을 1급 기능으로 — 임의 시점의 문서 상태로 되돌아가기

- 리치 텍스트 마크 CRDT(스타일 범위)를 별도 자료구조로 정밀하게 처리

- 트리 CRDT(부모-자식 관계의 동시 이동)를 진지하게 다룸

벤치마크에 따르면 Loro는 특정 워크로드(특히 트리·리치 텍스트)에서 Yjs보다 빠르다. 한편 Yjs는 텍스트 단순 편집·생태계·교재 자료에서 여전히 우위다.

9장 · Liveblocks — CRDT-as-a-Service의 첫 번째 챔피언

Liveblocks(파리 기반)는 **"멀티플레이어 인프라를 SaaS로"** 라는 명제를 가장 먼저 잡았다. 2026년 기준 주요 기능:

- `LiveObject`·`LiveList`·`LiveMap` — 자체 CRDT (Yjs와 별도)

- `useStorage` / `useMutation` — React 훅으로 상태 동기화

- **Awareness/Presence** — 커서·이름·선택 영역 실시간 공유

- **Comments·Threads** — 문서 위에 코멘트 다는 별도 모듈

- **Yjs 어댑터** — 기존 Yjs 앱(예: Tiptap·BlockNote·Lexical Yjs 어댑터)도 Liveblocks 트랜스포트로 동기화 가능

// Liveblocks: useStorage 훅으로 공유 상태 읽고 쓰기

function Todos() {

const todos = useStorage((root) => root.todos)

const addTodo = useMutation(({ storage }, text: string) => {

const list = storage.get('todos')

list.push({ id: crypto.randomUUID(), text, done: false })

}, [])

const toggle = useMutation(({ storage }, id: string) => {

const list = storage.get('todos')

const idx = list.findIndex((t) => t.id === id)

if (idx >= 0) {

const item = list.get(idx)

list.set(idx, { ...item, done: !item.done })

}

}, [])

return (

{todos.map((t) => (

{t.done ? 'X' : 'O'} {t.text}

))}

)

}

Liveblocks의 강점은 **운영의 단순함**과 **React에 최적화된 DX**다. 약점은 가격(시트당·MAU당 과금)과 자체 CRDT에 잠금(완전 자가 호스팅은 어려움).

10장 · PartyKit — Cloudflare Durable Objects 위의 협업

PartyKit(2023년 Sunil Pai 창립, 2024년 Cloudflare 인수)은 다른 길을 간다. **각 협업 룸이 하나의 Durable Object**이고, 그 안에 임의 코드(Yjs·Automerge·자체 로직)를 실행한다.

// PartyKit: 서버 코드 (server.ts)

export default class YjsServer implements Party.Server {

constructor(readonly room: Party.Room) {}

async onConnect(conn: Party.Connection) {

// y-partykit이 Yjs 동기화를 자동 처리

return onConnect(conn, this.room, {

persist: { mode: 'snapshot' },

})

}

}

PartyKit의 강점은 **사용자 코드를 엣지에서 실행**한다는 점이다. 룸 단위로 인증·권한·영속화·게임 로직을 자유롭게 짤 수 있다. 약점은 Cloudflare 잠금(이 부분은 2026년 와서는 사실상 장점이기도 함 — 글로벌 엣지 분산이 공짜).

11장 · 자가 호스팅 — Hocuspocus + Yjs

비용·잠금이 부담이면 **Hocuspocus**가 답이다. Tiptap 팀이 만든 Yjs 백엔드로, Node.js에서 돌고, Postgres·SQLite·Redis로 영속화한다.

// Hocuspocus 서버

const server = new Server({

port: 1234,

extensions: [

new Database({

fetch: async ({ documentName }) => {

// DB에서 Yjs 바이너리 로드

const row = await db.documents.findUnique({ where: { name: documentName } })

return row?.state ?? null

},

store: async ({ documentName, state }) => {

// DB에 저장

await db.documents.upsert({

where: { name: documentName },

update: { state, updatedAt: new Date() },

create: { name: documentName, state },

})

},

}),

],

async onAuthenticate({ token, documentName }) {

// JWT 검증 등

if (!verifyToken(token)) throw new Error('Unauthorized')

return { userId: extractUserId(token) }

},

})

server.listen()

운영을 직접 잡을 수 있고 데이터 주권이 보장된다. 대신 스케일링·HA·모니터링은 직접 한다. Notion·Linear급으로 가면 결국 자체 인프라를 짜게 된다.

12장 · ShareDB — OT 진영의 생존자

ShareDB(전 derby-share)는 **OT 기반**의 자가 호스팅 라이브러리다. Yjs 이전 시대(2015 전후) 협업 앱에서 폭넓게 쓰였고, 지금도 OT가 익숙한 팀에서는 현역이다.

- 문서를 JSON으로 표현하고, JSON OT 연산을 전파

- MongoDB·Postgres에 영속화

- 텍스트 OT(`json0`·`json1` 타입)는 검증됨

- 약점: P2P·오프라인이 CRDT만큼 자연스럽지 않음

2026년 신규 프로젝트라면 CRDT(Yjs/Automerge/Loro)를 택하는 게 일반적 권장이지만, **이미 ShareDB로 잘 굴러가는 시스템**을 굳이 갈아엎을 이유는 없다.

13장 · Replicache·Zero — "로컬-퍼스트 동기화 엔진"의 다른 답

Rocicorp의 Replicache(2020-)와 그 후속 Zero(2025-)는 CRDT가 아니다. **mutator 기반 동기화 + 서버 권위 + 낙관적 UI**라는 모델이다.

- 클라이언트가 "mutator" 함수를 정의한다(예: `addTodo(text)`).

- 로컬에서 mutator를 즉시 실행하고 UI를 업데이트.

- 같은 mutator 요청을 서버에 보내고, 서버에서 권위적으로 다시 실행.

- 서버 결과를 받으면 로컬 결과가 덮어쓰기됨(롤백 가능).

CRDT가 "여러 진실이 결국 수렴"이라면, Replicache는 "서버가 최종 진실, 클라이언트는 낙관적 추측"이다. Linear가 이 모델로 유명해졌다.

14장 · Fluid Framework — Microsoft의 엔터프라이즈 답

Fluid Framework는 Microsoft 365(Office Online·Loop)의 실시간 협업 엔진이다. CRDT는 아니지만 **시퀀스 기반**의 자체 동기화 모델이다.

- **SharedString**·**SharedMap**·**SharedTree** 등의 분산 자료구조(DDS)

- Azure Fluid Relay 위에서 도는 서비스

- 외부 개발자가 쓰기는 어렵고(공식 SDK는 있음), 사실상 Microsoft 내부 엔진

2026년 외부 신규 앱이 Fluid를 채택할 가능성은 낮지만, Office 통합·Microsoft Graph 위에서 협업을 짠다면 알아둘 가치가 있다.

15장 · 화이트보드 협업 — Tldraw와 Excalidraw

tldraw(@steveruizok·@orta)는 화이트보드·다이어그램 라이브러리이고, 멀티플레이어 sync 엔진을 자체 개발했다. 내부적으로는 Yjs와 비슷한 op-based 모델이지만 캔버스 자료구조(셰이프 트리)에 최적화되어 있다. `@tldraw/sync` 패키지로 React + WebSocket 서버로 룸 동기화하고, Cloudflare Durable Objects 위에서 도는 레퍼런스 구현이 있으며, 셰이프 단위의 마지막 작성자 우선(LWW)으로 그림 도구의 의미론에 맞춰져 있다.

반면 Excalidraw는 손그림 화이트보드의 대명사이고, 협업 룸 기능은 **암호화된 Firebase Realtime Database** 위에서 돈다. CRDT를 정식으로 안 쓰고, 단순한 LWW와 클라이언트 측 변경 머지로 처리한다. 의외로 합리적이다. 화이트보드는 짧은 세션 동안 적은 수의 셰이프를 동시 편집한다. CRDT의 메타데이터 오버헤드가 필요 없는 도메인이다.

tldraw의 교훈: **CRDT는 자료구조에 따라 다르게 디자인된다.** 텍스트 CRDT(Yjs Y.Text)와 캔버스 CRDT(tldraw)는 같은 이론 위에 있어도 구현 디테일이 전혀 다르다. 모든 협업 앱이 풀 CRDT를 깔아야 하는 건 아니라는 점.

16장 · Figma의 멀티플레이어 아키텍처

Figma는 2016-2017년에 자체 멀티플레이어 엔진을 만들었다. CRDT의 한 변형으로, 노드·속성 단위로 LWW를 적용한다. 핵심은:

- 도큐먼트 = 노드 트리, 각 노드는 속성 사전(property bag)

- 각 속성에 최신 변경의 `(timestamp, clientID)` 부여

- 충돌 = 더 큰 timestamp 승리(LWW), 동률은 clientID 큰 쪽

- 트리 구조 변경(부모 교체, 순서 변경)은 별도 트리 CRDT 로직

Figma의 엔지니어 Evan Wallace의 2019년 글이 이 아키텍처를 공개했고, 이후 많은 협업 앱이 비슷한 패턴을 따랐다. tldraw도 일부 영향을 받았다.

17장 · Google Docs와 OT의 역사

Google Docs는 2010년 Writely 인수로 시작해 2010년에 동시 편집을 정식 출시했다. 엔진은 **Jupiter(Google 내부 이름) OT**다. 핵심 아이디어:

- 모든 편집은 (revision, operation) 쌍

- 클라이언트는 서버에 op을 보내고, 서버는 다른 op과 변환해서 받음

- TP1(Transformation Property 1): `op1 \* (op2 transformed against op1) == op2 \* (op1 transformed against op2)` 가 성립해야 함

OT의 변환 함수를 정확히 짜는 건 악명 높게 어렵다. Google이 십수 년에 걸쳐 다듬은 이 코드는 2020년대 신규 앱이 따라가기에는 비용이 너무 컸고, 그래서 업계는 CRDT로 이동했다.

18장 · Presence / Awareness — "누가 어디서 무엇을 보고 있는가"

협업의 또 다른 절반은 **presence**다. 누가 온라인이고, 어디 커서가 있고, 무엇을 선택했고, 어떤 색깔로 표시되는가.

Yjs는 `y-protocols/awareness`로 표준화했다. CRDT 본체와 별개의 짧은 수명 상태(ephemeral state)다. 메타데이터:

- `clientID` (Y.Doc과 공유)

- `name`, `color`, `cursor`, `selection` 등 자유 JSON

- 하트비트로 살아있음 표시, 끊기면 자동 정리

Liveblocks는 awareness를 1급 시민으로 들고 와 `useOthers()`·`useUpdateMyPresence()`로 노출한다. PartyKit도 자체 broadcast로 구현 가능. 어느 쪽이든 **presence는 영속화하지 않는 것이 원칙** — 룸이 닫히면 사라진다.

19장 · WebSocket vs WebRTC — 트랜스포트의 선택

CRDT 동기화의 트랜스포트는 두 갈래다.

| 기준 | WebSocket | WebRTC (DataChannel) |

| --- | --- | --- |

| 서버 필요 | 필요 | NAT traversal용 STUN/TURN만 |

| 지연 | 서버 라운드트립 | 직통 P2P (빠를 수 있음) |

| 확장성 | 서버가 팬아웃 | 메시 토폴로지면 N^2 |

| 영속화 | 자연스러움 | 서버 미러 필요 |

| 방화벽 | 보통 잘 통과 | TURN 없으면 막힐 수 있음 |

| 표준 라이브러리 | y-websocket, y-partykit | y-webrtc |

2026년의 일반적 권장: **WebSocket을 기본**으로 하되, 진짜 P2P가 필요한 도메인(보안, 오프라인 메시) 또는 서버 부하를 분산하고 싶을 때 WebRTC를 보조로 쓴다.

20장 · CRDT 라이브러리 비교

| 라이브러리 | 언어 | 강점 | 약점 | 대표 사용처 |

| --- | --- | --- | --- | --- |

| Yjs | JS/TS (+Yrs Rust 포트) | 성숙·생태계·텍스트 성능 | 본체 메모리 다소 큼 | Tiptap, BlockNote, Lexical Yjs |

| Automerge | Rust/WASM | JSON 트리 머지 강함 | 텍스트 성능은 Yjs보다 약함 | PushPin, Trail Runner |

| Loro | Rust/WASM | 리치 텍스트·트리·시간여행 | 생태계 신생 | 신규 앱들 |

| Y.js + Yrs | Rust 포트 | Rust 네이티브 백엔드 | Yjs와 와이어 호환 보장은 부분적 | 서버 사이드 Yjs |

| Diamond Types | Rust | 단일 텍스트 CRDT 성능 챔피언 | JSON 미지원 | 벤치마크용 |

| sjs (Synchronized JS) | 실험 | TypeScript 친화 | 신생 | 연구용 |

21장 · SaaS vs 자가 호스팅 비교

| 기준 | Liveblocks | PartyKit | Hocuspocus (자가) | Replicache | Fluid (MS) |

| --- | --- | --- | --- | --- | --- |

| CRDT 종류 | 자체 + Yjs 어댑터 | Yjs/Automerge 자유 | Yjs | 비-CRDT(낙관적) | DDS (비-CRDT) |

| 호스팅 | SaaS | Cloudflare 엣지 | 셀프 | 셀프 또는 Rocicorp | Azure |

| 가격 | MAU 기반 | 사용량 기반 | 인프라 비용만 | 사용량 + SaaS | Azure 단가 |

| 자가 호스팅 가능 | 부분(Edge Storage 옵션) | 어려움 (CF 종속) | 완전 | Replicache는 가능 | 어려움 |

| 데이터 주권 | 미국·EU 옵션 | 글로벌 엣지 | 자유 | 자유 | Azure 리전 |

| Awareness 1급 | 예 | 직접 구현 | 예 | 미해당 | 부분 |

| 코멘트/스레드 모듈 | 예 | 없음 | 없음 | 없음 | Loop이 별도 |

22장 · 한국·일본 사례 — 노션, 카카오, 사이보즈, Sansan

**한국**의 협업 시장은 흥미롭다. **노션 한국 진출(2020)** 이후 Notion API와 자체 빌드 협업의 비율이 빠르게 늘었다(노션은 자체 CRDT-like 모델을 쓰지만 공개 문서는 적다). **카카오워크 / 카카오톡**의 메시지는 CRDT가 아니지만, 카카오워크의 게시판·메모 협업은 OT 기반(공개 발표)이며, 카카오엔터프라이즈가 "원격 협업"을 키운 시기에 자체 엔진을 만들었다. **네이버 라인웍스(Line Works)** 는 문서·표·캘린더 협업을 제공하며, 라인의 일본 시장과 통합되어 일본 엔터프라이즈에서 점유율이 크다. **토스 / 토스페이먼츠**는 내부 협업 툴(아카이브·문서)에서 Yjs + Tiptap 조합을 채택한 사례를 콘퍼런스에서 공유했다.

**일본**도 흥미롭다. **Cybozu kintone**은 비즈니스 앱 빌더의 폼·레코드 협업으로, 같은 레코드를 여러 사용자가 동시에 편집할 때 OT 기반 변환을 적용한다. 사이보즈는 오랜 OT 노하우를 가진 회사로 유명하다. **Sansan**은 명함 클라우드로, 명함 데이터 OCR 결과를 여러 사용자가 동시에 보정하는 워크플로에서 CRDT 적용을 발표한 바 있다. **라쿠텐 RMS**는 라쿠텐 쇼핑몰의 점주용 협업 툴로 내부적으로 자체 동기화 엔진을 쓴다. **Notion 일본팀**은 영어권과 동일한 엔진이지만, 일본어 IME와의 협업 충돌(조합 중인 글자)을 다루는 패치가 일본팀 PM 발표로 공개되었다.

23장 · CRDT 도입 의사결정 매트릭스

언제 CRDT가 정답이고 언제 아닌가.

| 상황 | 권장 |

| --- | --- |

| 텍스트 동시 편집(에디터) | Yjs + Tiptap/BlockNote/Lexical-Yjs |

| 화이트보드/캔버스 | tldraw sync 또는 Yjs Y.Map |

| 폼·테이블(셀 단위) | Yjs Y.Map + 셀별 키 또는 ShareDB |

| 게임 상태(시간 결정적) | CRDT보다 lock-step 또는 권위 서버 |

| 채팅 메시지 | CRDT 불필요, append-only 로그 |

| 알림·실시간 카운터 | CRDT G-Counter (학술적이지만) 또는 Redis |

| 오프라인 1순위 (로컬-퍼스트) | Automerge, Loro, Yjs IndexedDB persistence |

| 서버 권위가 중요 (결제·재고) | Replicache 또는 전통 트랜잭션 |

24장 · 영속화 전략 — IndexedDB·Postgres·바이너리 스냅샷

CRDT는 어딘가에 저장되어야 한다.

- **클라이언트 측**: Yjs의 `y-indexeddb`, Automerge의 IndexedDB 어댑터. 오프라인 편집 후 재접속 시 자동 동기화.

- **서버 측 (Yjs 바이너리)**: Yjs는 `encodeStateAsUpdate`로 전체 상태를 바이너리로 저장한다. Postgres `bytea` 컬럼, S3 객체, Redis 등 어디든 가능.

- **서버 측 (델타 로그)**: 매 업데이트를 로그로 누적하고 주기적으로 스냅샷 압축. 대용량 문서에 유리.

- **하이브리드**: 스냅샷 + 그 이후 델타. 로딩 시 스냅샷 + 델타 누적.

영속화의 함정은 **GC**다. 모든 클라이언트가 본 tombstone은 지울 수 있지만, "모든 클라이언트"를 어떻게 판단하느냐가 어렵다. Yjs는 보수적으로 GC하고, 운영자가 주기적으로 명시적 압축을 호출할 수도 있다.

25장 · 보안·권한·종단간 암호화

협업 SaaS의 보안 모델은 세 갈래다.

- **서버가 평문을 본다** (가장 흔함): Liveblocks·Hocuspocus 표준 모델. 권한 검사·검색·코멘트 모더레이션이 쉽다.

- **종단간 암호화 (E2EE)**: 클라이언트가 Yjs 업데이트를 암호화한 뒤 서버에 보냄. 서버는 바이너리 블롭만 알고 의미를 모름. Excalidraw 룸이 비슷한 모델. 약점: 검색·코멘트 분석·서버 측 머지가 불가능.

- **부분 암호화**: 콘텐츠는 E2EE, 메타데이터(룸 멤버, presence)는 평문. 절충안.

권한은 보통 **룸 단위 토큰** + **연산 단위 검증**의 조합이다. 누가 룸에 들어올 수 있나(JWT), 들어와서 무엇을 할 수 있나(Hocuspocus의 `onAuthenticate` + `beforeHandleMessage`).

26장 · 에필로그 — 2027년을 본다

CRDT는 이제 학술의 영역을 떠나 운영의 영역에 있다. 2027년에 일어날 변화 셋:

1. **모바일·네이티브 1급 시민화** — Yjs Swift 포트, Automerge Kotlin, Loro Flutter 바인딩이 모두 안정화되어 모바일 협업 앱이 폭발한다. 로컬-퍼스트가 진짜로 가능해진다.

2. **AI 에이전트가 협업 참여자가 된다** — Cursor·Claude Code가 같은 문서에 동시 편집하는 또 하나의 "유저"가 되고, awareness는 사람과 AI를 구분한다.

3. **표준화의 시작** — IETF·W3C 수준에서 CRDT 와이어 포맷이 표준화 논의에 들어간다. Yjs·Automerge·Loro의 와이어 호환성은 아직 없지만, "표준 텍스트 CRDT"의 윤곽이 보이기 시작한다.

가장 큰 교훈은 단순하다. **저장 버튼이 사라진 세계는 이미 와 있다.** 이제 남은 일은, 그 위에 무엇을 짓느냐다.

References

- Shapiro, Marc et al. "Conflict-free Replicated Data Types." INRIA Research Report (2011). https://hal.inria.fr/inria-00609399v1

- Kleppmann, Martin and Beresford, Alastair R. "A Conflict-Free Replicated JSON Datatype." IEEE TPDS (2017). https://arxiv.org/abs/1608.03960

- Kleppmann, Martin et al. "Local-first software: You own your data, in spite of the cloud." Onward! 2019, Ink & Switch. https://www.inkandswitch.com/local-first/

- Kleppmann, Martin. "Designing Data-Intensive Applications." O'Reilly (2017) — Chapter on replication and CRDTs.

- Yjs documentation. https://yjs.dev and https://docs.yjs.dev

- Automerge documentation. https://automerge.org and https://github.com/automerge/automerge

- Loro documentation. https://loro.dev and https://github.com/loro-dev/loro

- Liveblocks documentation. https://liveblocks.io/docs

- PartyKit documentation. https://docs.partykit.io

- Hocuspocus documentation. https://tiptap.dev/docs/hocuspocus

- ShareDB. https://github.com/share/sharedb

- Replicache and Zero. https://replicache.dev and https://zero.rocicorp.dev

- Fluid Framework. https://fluidframework.com

- tldraw sync. https://tldraw.dev/docs/sync

- Excalidraw collaboration. https://github.com/excalidraw/excalidraw

- Evan Wallace, "How Figma's multiplayer technology works." Figma blog (2019). https://www.figma.com/blog/how-figmas-multiplayer-technology-works/

- Diamond Types — Seph Gentle. https://github.com/josephg/diamond-types

- Kleppmann, Martin et al. "Moving Elements in List CRDTs." PaPoC 2020. https://martin.kleppmann.com/papers/list-move-papoc20.pdf

- Yjs internals: Kevin Jahns, "Are CRDTs suitable for shared editing?" https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/

현재 단락 (1/277)

2010년 Google Docs가 "동시에 같은 문서를 편집한다"를 일반 사용자에게 선물했을 때, 그 기술은 OT(Operational Transform)였다. 한 사람의 편집 연...

작성 글자: 0원문 글자: 14,209작성 단락: 0/277