ふくふくHukuhuku Inc.
EP.07Algorithms 10分公開: 2026-05-11

CRDT:Notion・Figma・Linear のリアルタイム同時編集を支える分散データ構造

「複数人が同じドキュメントを同時に編集してもコンフリクトしない」を数学的に保証する CRDT を Python で実装。G-Counter・LWW-Set・RGA の仕組み。

#CRDT#分散システム#Notion#Figma
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

Notion でドキュメントを 5 人で同時編集する。Figma で複数のデザイナーが同じファイルを操作する。Linear でオフライン中に書いたチケットが再接続後に同期される。これらすべての中核技術が CRDT (Conflict-free Replicated Data Types) ── 数学的に「コンフリクトしない」ことが保証された分散データ構造。

1. これで何が動いているか

  • Notion: ドキュメント / データベースの編集
  • Figma: マルチプレイヤー (リアルタイム同時編集)
  • Linear: オフライン操作の同期
  • Yjs: WebRTC ベースの CRDT ライブラリ (Notion 等で採用)
  • Automerge: Ink & Switch の CRDT ライブラリ
  • Riak / Redis: KV ストアの CRDT 型 (CRDT counter / set)
  • Apple Notes / Cosmos DB: バックエンド分散同期

2. 仕組みのざっくり

  • 各レプリカ (ユーザの端末) はローカルに状態を持つ
  • 更新は他レプリカに非同期で伝播する (オフラインも OK)
  • マージ操作は「可換・結合・べき等」の 3 条件を満たす
  • 結果: いつ、どの順序でマージしても、最終的に全レプリカが同じ状態になる (Strong Eventual Consistency)

3. G-Counter (増加のみカウンタ) の実装

最も簡単な CRDT: 各ノードのカウンタを別々に持ち、合計を取る
Python
class GCounter:    '''各ノードのカウンタを別々に保持し、合計を value() とする'''    def __init__(self, node_id):        self.node = node_id        self.counts = {}  # {node_id: count}
    def inc(self, n=1):        self.counts[self.node] = self.counts.get(self.node, 0) + n
    def value(self):        return sum(self.counts.values())
    def merge(self, other):        '''各ノードの最大値を取る (べき等・可換・結合)'''        for k, v in other.counts.items():            self.counts[k] = max(self.counts.get(k, 0), v)
# 3 ノードでバラバラに +1, +2, +3a = GCounter('A'); a.inc(3)b = GCounter('B'); b.inc(2)c = GCounter('C'); c.inc(1)
# 任意の順序でマージa.merge(b); a.merge(c)b.merge(a)c.merge(a)
print(f'A={a.value()}, B={b.value()}, C={c.value()}')# 全て 6 (= 3+2+1)

4. LWW-Element-Set (タイムスタンプベース集合) の実装

Last-Write-Wins: 同じ要素の add/remove はタイムスタンプで決着
Python
import time
class LWWSet:    '''Last-Write-Wins Element Set'''    def __init__(self):        self.adds = {}     # {element: timestamp}        self.removes = {}  # {element: timestamp}
    def add(self, e, ts=None):        ts = ts or time.time()        self.adds[e] = max(self.adds.get(e, 0), ts)
    def remove(self, e, ts=None):        ts = ts or time.time()        self.removes[e] = max(self.removes.get(e, 0), ts)
    def __contains__(self, e):        a = self.adds.get(e, 0)        r = self.removes.get(e, 0)        return a > r  # add が新しければ含まれる
    def merge(self, other):        for e, t in other.adds.items():            self.adds[e] = max(self.adds.get(e, 0), t)        for e, t in other.removes.items():            self.removes[e] = max(self.removes.get(e, 0), t)
# シナリオ: 2 人が同じ要素を異なるタイミングで add/removes1 = LWWSet(); s2 = LWWSet()s1.add('apple', ts=100)s2.add('apple', ts=100)s2.remove('apple', ts=200)  # s2 だけ削除
s1.merge(s2)print(f'apple in s1: {"apple" in s1}')  # False (削除が新しい)

5. CRDT の種類と用途

CRDT用途代表ライブラリ
G-Counter増加のみ整数 (PV カウント)Riak
PN-Counter増減整数 (いいね数)Riak / Redis
G-Set追加のみ集合Riak
LWW-Setタイムスタンプ付き集合Cassandra
OR-SetUUID 付き集合 (削除も安全)Riak / Redis
RGA / Yata順序付きテキストYjs / Automerge
JSON CRDTJSON ツリーAutomerge

6. 実用ライブラリ

y-py (Yjs の Python バインディング)
Python
# pip install y-py# ※ 本番では JS の Yjs を WebSocket で接続するのが一般的
import y_py as Y
doc = Y.YDoc()text = doc.get_text('content')with doc.begin_transaction() as txn:    text.insert(txn, 0, 'Hello, ')    text.insert(txn, 7, 'World!')
print(str(text))  # 'Hello, World!'# Yjs の update バイナリは差分同期に使える (WebRTC / WebSocket で配信)update = Y.encode_state_as_update(doc)

7. メリットとデメリット

  • メリット: 中央サーバ不要、オフライン編集可、自動マージ
  • メリット: 数学的に「最終整合性」が保証される
  • デメリット: 全操作の履歴を持つ場合、メモリ・帯域が膨らむ (GC が必要)
  • デメリット: トランザクション (atomicity) はサポートしない
  • デメリット: 「最後の書き込みが勝つ」等のセマンティクスは用途次第で適切でないことも

8. 関連ツール

  • Yjs (JS): 業界標準、WebRTC / WebSocket / IndexedDB バインディング
  • Automerge (JS / Rust): JSON CRDT 中心
  • Riak: KV ストアの CRDT 型 (Distributed Counter / Set / Map)
  • redis-stack: Redis CRDT モジュール
  • Loro: Rust 製の最新 CRDT

9. 次の話

EP.08 では Raft 合意アルゴリズム を扱います。etcd / Consul / TiDB / CockroachDB の中核、リーダー選出とログ複製の仕組みを Python で実装。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp