1 of 35

CRDTs for Collaborative Text Editing

Matthew Weidner

CMU PLunch 2/25/22

2 of 35

Goal: Collaborative (Plain) Text Editing

Google Docs minus formatting.

3 of 35

Challenge: Availability + Consistency

My typing should show up instantly (no network round trips).

But this creates concurrency.

Need to end up in consistent state (Strong Eventual Consistency).

The cat jumped on table.

The cat jumped on the table.

The gray cat jumped on table.

The gray cat jumped on the table.

The gray cat jumped on the table.

4 of 35

Strawman: Broadcast Indices

Problem???

When you change the text, broadcast your changes & their index.

Other users apply the same changes at same index.

The cat jumped on table.

The cat jumped on the table.

Broadcast “Insert ‘ the’ at index 17”.

The cat jumped on table.

The cat jumped on the table.

5 of 35

Strawman: Broadcast Indices

Indices move around during concurrent edits.

If you don’t correct for this, result would be inconsistent & not what users expect.

The cat jumped on table.

The cat jumped on the table.

The gray cat jumped on table.

The gray cat jumped on the table.

The gray cat jumped on the table.

Index 17

Index 22

6 of 35

One Solution: “Operational Transformation”

“Transform” indices in received ops, to account for concurrent ops you already performed.

Transform “Insert ‘ the’ at 17” by “Insert ‘ gray’ at 3”: Add 5 to index -> 22.

The cat jumped on table.

The cat jumped on the table.

The gray cat jumped on table.

The gray cat jumped on the table.

The gray cat jumped on the table.

Index 17

Index 22

7 of 35

One Solution: “Operational Transformation”

Define transformations for all pairs of ops (insert/insert, insert/delete, delete/delete, …).

Must satisfy algebraic properties.

Then use an OT algorithm to apply changes.

“An Integrating, Transformation-Oriented Approach to Concurrency Control and Undo in Group Editors” by Ressel, Nitsche-Ruhland, & Gunzenhäuser.

8 of 35

Operational Transformation: Problems

Checking algebraic properties: combinatorial explosion.

  • Many flawed algs before mechanized checking.�“Proving correctness of transformation functions in collaborative editing systems” by Oster et al.

Alg is slow in face of many conflicts.

In practice: central server + no offline mode (e.g. Google Docs).

  • Precludes offline/LAN mode, P2P networks, E2EE, open source.

9 of 35

Conflict-free Replicated Data Types (CRDTs)

10 of 35

Conflict-free Replicated Data Types (CRDTs)

Forget about indices, transformation properties, …

Add extra metadata to data structure & network messages so you can process all ops “directly”, w/o explicitly considering past ops.

In other words: no conflicts between concurrent ops.

I’ll focus on one text/list CRDT (“Double RGA”); many others, plus CRDTs for other data types.

11 of 35

CRDT Basics

Instead of using indices, assign each char a unique immutable position.

Positions are totally ordered and dense:�If p < q, can create new r such that p < r < q.

Assign r to char inserted between p & q.

「 T a b l e 」

0

0.5

0.875

0.75

0.25

0.9375

1

12 of 35

CRDT Basics

In case of concurrent edits: sort positions as usual.

The cat jumped on table.

The cat jumped on the table.

The gray cat jumped on table.

The gray cat jumped on the table.

The gray cat jumped on the table.

.7

.8

“Insert ‘ the’ at 0.75”

.15

.25

“Insert ‘ gray’ at 0.2”

13 of 35

Issue 1: Real numbers aren’t real (for computers).

Floats only get 53 bits of precision.

T y p i n g f r o m l e f t t o r i g h t

0.1

0.11

0.111

0.1111

0.11111

0.111111

0.1111111

. . .

14 of 35

Issue 1: Real numbers aren’t real (for computers).

Arbitrary-precision floats

Represent as binary tree.

0.10001

1

1

0

0

0

15 of 35

CRDT Attempt 2

Order: Tree walk.

Insert: Assign char a path between its neighbors.�Broadcast char & path.

I

t

r

o

d

u

c

n

Introduc

16 of 35

Issue 2: Concurrent insertions at same position (path).

Sort arbitrarily (e.g. by sender).

But what if someone types in between?

Two

Three

people

typing

^

Four

17 of 35

CRDT Attempt 3 (Working)

Distinguish nodes w/ same parent and side by UID.

Order is still a tree walk: L children, node, R children.

Sort same-side children by UID.

(“alice”, 1)

(“bob”, 2)

(“bob”, 1)

(“bob”, 3)

a

b

c

d

18 of 35

CRDT Attempt 3 (Working)

Insert: Assign char a new node between its neighbors.

e

(“alice”, 1)

(“bob”, 2)

(“bob”, 1)

(“bob”, 3)

Broadcast (char, new UID, parent UID, side).

(“alice”, 2)

b

c

d

a

aebcd

19 of 35

CRDT Attempt 3 (Working)

Insert: Assign char a new node between its neighbors.

e

(“alice”, 1)

(“bob”, 2)

(“bob”, 1)

(“bob”, 3)

(“alice”, 2)

b

c

d

a

afebcd

(“dave”, 1)

f

Broadcast (char, new UID, parent UID, side).

20 of 35

CRDT Attempt 3 (Working)

Delete: Mark the node as deleted.

I.e., broadcast (“delete”, node’s UID).

(Need to keep the node around in case someone uses it as a parent concurrently.)

21 of 35

Text CRDT: Summary

Tree w/ left & right children.

Each node has UID (sender, counter).

Order: tree walk (UID as tiebreaker).

Insert: Assign char a new node between its neighbors. Broadcast (char, new UID, parent UID, side).

Delete: Mark the node as deleted. I.e., broadcast (“delete”, node’s UID).

afebcd

22 of 35

Further Topics

23 of 35

Text CRDTs, Plural

Numerous algs since 2008. See https://crdt.tech/papers.html.

All use “unique immutable positions”. Most use trees.

This alg: appears novel, but very similar to others.

  • “Double” RGA
  • Logoot w/o optimizations
  • Tree version of YATA mod by Seph Gentle

24 of 35

Interleaving Anomalies

“Interleaving anomalies in collaborative text editors” by Kleppmann et al.

25 of 35

Interleaving Anomalies

Using hypothetical real-number CRDT (not the tree alg I described):

“Interleaving anomalies in collaborative text editors” by Kleppmann et al.

26 of 35

Interleaving Anomalies

Luckily, our text CRDT avoids interleaving:

In general, contiguous insertions by one user end up in a new subtree.

-> Not interleaved with other new subtrees.

In particular, concurrent LtR / RtL insertions not interleaved*.

l

o

A

l

i

C

h

a

*To the extent permitted by law.

27 of 35

Memory Usage

Easily 100 bytes per node, including deleted nodes.

Times 100,000 chars + 100,000 deletions (e.g. 14 page LaTeX doc): 20 MB. GC-intensive.

Vs 0.1 MB for non-CRDT.

Can get much worse…

28 of 35

Memory Usage

How to reduce memory usage?

  • Constant factor / common case optimizations (x 10)
  • Find way to eliminate deleted nodes? (x 2+)

29 of 35

Memory Usage: Common Case Opts

Most text comes in LtR chunks typed by a single user.

“Compress” these as pair�(first UID, chunk of text).

NB: Theory/alg is the same; just changing how code reps it.

Good enough in practice (x 10).

l

o

A

l

i

C

h

a

30 of 35

Memory Usage: Eliminate Deleted Nodes?

Important for docs with long edit history.

If you type 100k chars and delete them all, it’s surprising to see memory usage not decrease.

Tombstone: permanent record of deleted thing. Common criticism of CRDTs.

Node (“alice”, 7) with parent (“bob”, 3)

31 of 35

Memory Usage: Eliminate Deleted Nodes?

Why do we need to remember deleted nodes?

Need to remember “ the” and “ gray”’s paths in the tree, so that we can sort them. But these paths are made of deleted nodes.

The cat jumped on table.

The cat jumped on the table.

The gray cat jumped on table.

gray the

gray the

gray the

32 of 35

Memory Usage: Eliminate Deleted Nodes?

Need to remember “ the” and “ gray”’s paths in the tree, so that we can sort them. But these paths are made of deleted nodes.

Idea: On insert, instead of sending just the node’s parent, send its whole path to the root.

Issue: Now network message size is prop. to path length.

But long paths are the common case (LtR insertions)...

Trying to avoid this leads to interleaving anomalies.

33 of 35

Memory Usage: Eliminate Deleted Nodes?

Also:

Theorem (Attiya et al. 2016).�For any “reasonable” text CRDT, for any # of deleted chars D, there is an execution in which some user’s state is one char long (i.e., only one non-deleted element) but they use memory 𝛺(D).

“Specification and Complexity of Collaborative Text Editing” by Attiya et al.

34 of 35

Memory Usage: Takeaways

Compressing LtR chunks (common case) works well enough in practice.

Eliminating tombstones increases network usage and is theoretically challenging.

Note: Compressing LtR chunks also reduces the # of tombstones, by reducing # of nodes. Or, if you use your CRDT in “no tombstone” mode (sending whole paths each time), compression reduces effective path lengths. So it’s a good idea all around.

35 of 35

Conclusion

Text CRDTs enable collaborative plain text editing.

Not too complicated if you pick the right one: binary-ish tree where each node has a UID.

The text CRDT presented here avoids interleaving anomalies.

Memory usage is a concern, but compressing LtR chunks of text makes it acceptable in practice.

Avoiding tombstones is hard.

Thanks!