노트

CRDT

Conflict-free Replicated Data Type

CS#algorithm · 연결된 개념 4개

쉽게 말하면

CRDT는 각자 고친 사본을 어떤 순서로 합쳐도 같은 결과가 나오게 만든 자료구조예요. 사람마다 자기 칸에만 찍는 도장판을 칸별 최댓값으로 합치면, 누가 먼저 합치든 같은 판이 되는 식이죠.

비유의 한계 도장판처럼 늘기만 하면 쉽지만, 지운 것도 툼스톤으로 남겨야 해서 메타데이터가 계속 쌓여요. '재고가 음수면 안 된다' 같은 업무 규칙은 자동 병합으로 표현하기 어려워요.

여러 사본이 각자 독립적으로 수정돼도, 변경이 어떤 순서로 도착하든 병합하면 항상 같은 상태로 수렴하도록 수학적으로 설계한 자료구조. 이름 그대로 "충돌 없는 복제 데이터 타입"이다. 중앙 서버가 순서를 정해 주지 않아도 된다.

두 가지 방식

  • 상태 기반(State-based, CvRDT): 상태 전체를 주고받고 병합 함수로 합친다. 병합은 순서와 중복에 상관없이 같은 결과를 내야 한다
  • 연산 기반(Operation-based, CmRDT): 연산을 주고받되, 동시에 일어난 연산끼리는 순서를 바꿔 적용해도 결과가 같도록 설계한다. 대신 전달 계층이 연산을 빠짐없이 한 번씩, 인과 순서대로 전달해야 한다
// G-Counter: 증가만 하는 카운터. 노드마다 자기 칸만 올리고, 병합은 칸별 최댓값
type GCounter = Record<string, number>
const merge = (a: GCounter, b: GCounter) => {
  const out = { ...a }
  for (const [node, n] of Object.entries(b)) out[node] = Math.max(out[node] ?? 0, n)
  return out
}
const value = (c: GCounter) => Object.values(c).reduce((s, n) => s + n, 0)

주요 타입

타입용도방식
G-Counter / PN-Counter카운터노드별 칸, 증가·감소 칸 쌍
LWW-Register(Last-Writer-Wins)단일 값타임스탬프가 늦은 쪽이 이김
OR-Set(Observed-Remove Set)집합추가마다 고유 태그를 붙여 추적
RGA(Replicated Growable Array), YATA 계열텍스트·리스트문자마다 고유 ID

텍스트 편집에서는 문자에 고유 ID를 붙이고 "어느 문자 뒤에 넣는다"를 인덱스가 아닌 ID로 표현해, 동시에 넣어도 위치가 꼬이지 않는다.

한계

  • 삭제된 요소도 표시(툼스톤, Tombstone)로 남겨야 해서 메타데이터가 계속 쌓인다. 정리 전략이 필요하다
  • "재고가 음수가 되면 안 된다" 같은 업무 규칙이나 의도 기반 연산은 자동 병합으로 표현하기 어렵다

오프라인에서 각자 고친 뒤 나중에 합치는 로컬 퍼스트와 잘 맞고, 서버가 연산을 변환하는 OT와 자주 비교된다. 각 사본이 언젠가 같아진다는 점에서 최종 일관성의 한 구현이다. 라이브러리로는 Yjs, Automerge가 대표적이다(2026 기준).

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 그래프

    정점(vertex, 노드)과 정점들을 잇는 간선(edge)으로 이루어진 자료구조. 트리도 그래프의 한 종류다. SNS 친구 관계, 지도와 경로, 웹 페이지 링크, 추천 시스템, 패키지 의존성처럼 "무엇과 무엇이 연결돼 있다"는 모든 것을 표현한다. 이 지식 맵도 노트를 정점, 링크를 간선으로 한 그래프다.

  • DB 인덱스와 트레이드오프

    DB 인덱스는 특정 컬럼 값으로 행을 빨리 찾도록 테이블 옆에 따로 유지하는 보조 자료구조(auxiliary data structure)다. 관계형 DB의 기본 인덱스는 정렬된 균형 트리(B-tree 계열)라서, 전체를 훑지 않고 트리를 따라 내려가 원하는 행에 닿는다.

  • 구조적 공유

    구조적 공유(Structural Sharing)는 새 버전의 데이터를 만들 때 바뀌지 않은 부분은 이전 버전의 객체를 그대로 가리키게 하는 기법이다. 바뀐 노드와 그 노드까지 올라가는 경로만 새로 만들고, 나머지 가지는 두 버전이 함께 쓴다. 불변 데이터를 다루면서도 매번 전체를 복사하지 않게 해 준다.

  • 데이터베이스 다중화

    같은 데이터를 여러 DB 서버에 복제해 두는 것. 가장 흔한 형태는 원본을 가진 주(Primary) 서버가 쓰기를 받고, 사본을 받는 부(Replica) 서버들이 읽기를 나눠 맡는 구조다.

  • useReducer

    useReducer는 "이전 상태와 액션을 받아 새 상태를 돌려주는" 리듀서(Reducer) 함수로 상태를 관리하는 훅이다. 이름처럼 Array.prototype.reduce의 아이디어를 시간축으로 늘린 것이다. 배열 요소 대신 시간순으로 들어오는 액션을 누적한다.

보기 옵션