Files
2nd/10_Wiki/Topics/Domain_Programming/Coding/CS_CRDT_Patterns.md
T
Antigravity Agent c24165b8bc refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게
[공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류.
문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인).

- Topic_Programming → Domain_Programming (내부 구조 보존)
- Topic_Graphic → Domain_Design
- Topic_Business → Domain_Product
- Topic_General → Domain_General
- _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning),
  Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing)
- 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서)
- 빈 폴더 정리 (memory/procedures)
- 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:05:56 +09:00

5.4 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
cs-crdt-patterns CRDT — 자동 conflict 해결 / 협업 Coding draft B conceptual 2026-05-09 2026-05-09
cs
crdt
collab
vibe-coding
language applicable_to
TS / yjs / automerge
Frontend
Backend
CRDT
Yjs
Automerge
conflict-free
eventually consistent
OT vs CRDT

CRDT (Conflict-free Replicated Data Type)

여러 replica 가 다른 변경 후 자동 merge — conflict 없이. 실시간 협업 (Notion, Figma, Google Docs), local-first, offline sync. Yjs / Automerge.

📖 핵심 개념

  • 모든 operation 가 commutative + idempotent.
  • Merge 결과 = operation 순서 무관.
  • State-based vs Operation-based.
  • OT (Operational Transform) vs CRDT — CRDT 가 modern.

💻 코드 패턴

Yjs (가장 인기, 작음)

import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { IndexeddbPersistence } from 'y-indexeddb';

const ydoc = new Y.Doc();

// 다른 client 와 동기화
const wsProvider = new WebsocketProvider('ws://localhost:1234', 'doc-1', ydoc);

// 로컬 영속
const persistence = new IndexeddbPersistence('doc-1', ydoc);

// Y.Map (object)
const ymap = ydoc.getMap('config');
ymap.set('theme', 'dark');
ymap.observe((event) => console.log('changed:', event.changes));

// Y.Array
const yarray = ydoc.getArray('items');
yarray.push(['item1']);
yarray.insert(0, ['first']);

// Y.Text (rich text)
const ytext = ydoc.getText('doc');
ytext.insert(0, 'Hello');

Tiptap + Yjs (collab editor)

import Collaboration from '@tiptap/extension-collaboration';
import CollaborationCursor from '@tiptap/extension-collaboration-cursor';

const editor = useEditor({
  extensions: [
    StarterKit.configure({ history: false }), // Yjs 가 history 관리
    Collaboration.configure({ document: ydoc }),
    CollaborationCursor.configure({
      provider: wsProvider,
      user: { name: 'Alice', color: '#f0f' },
    }),
  ],
});

Awareness (presence)

const awareness = wsProvider.awareness;
awareness.setLocalStateField('user', { name: 'Alice', cursor: { x: 100, y: 200 } });

awareness.on('change', () => {
  const states = Array.from(awareness.getStates().values());
  // 다른 사용자 보임
});
import * as A from '@automerge/automerge';

let doc = A.from({ items: [], title: 'Untitled' });

doc = A.change(doc, 'add item', d => {
  d.items.push({ id: '1', text: 'first' });
});

// Sync (binary)
const change = A.getLastLocalChange(doc);
sendToPeer(change);

// 다른 peer
let doc2 = A.from({ items: [], title: 'Untitled' });
doc2 = A.applyChanges(doc2, [change])[0];

Merge (자동)

// Alice
docA.text.insert(0, 'A');

// Bob (offline)
docB.text.insert(0, 'B');

// 동기화 → 둘 다 'AB' 또는 'BA' (deterministic)

Server provider

// Hocuspocus (Yjs 전용 server)
import { Server } from '@hocuspocus/server';

const server = Server.configure({
  port: 1234,
  async onAuthenticate({ token }) {
    const user = verifyJwt(token);
    return { user };
  },
  async onLoadDocument({ documentName }) {
    const ydoc = new Y.Doc();
    const persisted = await db.docs.get(documentName);
    if (persisted) Y.applyUpdate(ydoc, persisted);
    return ydoc;
  },
  async onStoreDocument({ documentName, document }) {
    await db.docs.upsert(documentName, Y.encodeStateAsUpdate(document));
  },
});

server.listen();

LiveBlocks (managed)

import { useStorage, useMutation } from '@/liveblocks.config';

const items = useStorage(root => root.items);
const addItem = useMutation(({ storage }, text: string) => {
  storage.get('items').push({ id: nanoid(), text });
}, []);

→ Yjs / 자체 server 관리 안 해도 됨.

Local-first

1. 로컬 변경 → IndexedDB 저장
2. 네트워크 OK → server 와 sync
3. Offline → 로컬만 — 정상 동작
4. Reconnect → 자동 merge

→ Notion / Linear 의 UX.

단점

- Document size 시간 따라 커짐 (history 누적).
- Garbage collection 필요.
- Schema 변경 어려움.
- Server-side validation 어려움 (CRDT 가 자유 변경).

사용 예

  • 협업 editor: Yjs + Tiptap / Lexical.
  • Real-time dashboard: Yjs + React.
  • Offline-first app: Automerge / Yjs IndexedDB.
  • Multi-cursor: Awareness.

🤔 의사결정 기준

상황 추천
Real-time editor Yjs + ProseMirror/Tiptap
작은 data + JSON-like Automerge
Managed LiveBlocks / Liveblocks
단순 sync (last-write-wins) 직접 구현
Strong consistency DB transaction (CRDT X)
1 사용자 / 1 device CRDT 불필요

안티패턴

  • Server-side validation 강 가정: CRDT 가 client 자유. 별도 룰 + reject.
  • Offline 무한 길이: history 누적. GC.
  • CRDT 안에 secret / PII: client 가 모든 history 접근.
  • Schema 자주 변경: 마이그레이션 어려움.
  • 너무 큰 document (10MB): sync 느림.
  • Custom CRDT 자체 구현: 어려움. Yjs / Automerge 사용.
  • LWW (last write wins) 만 사용 + 잃음: CRDT = 양쪽 보존.

🤖 LLM 활용 힌트

  • Yjs 가 가장 인기 + 강력.
  • Hocuspocus 또는 LiveBlocks 가 server 답.
  • Awareness 로 cursors / presence.
  • Strong validation 필요 시 server-side check 분리.

🔗 관련 문서