This post was originally published by Senior Engineer Zak Knill on his own blog zknill.io.
You don’t need CRDTs
You don’t need CRDTs for collaborative experiences.
First lets get the ‘what-about-ery’ out the way…
Things you do need CRDTs for:
1. Offline first – this is way harder to get useful behaviour with out CRDTs. If you don’t use them, you’re pretty much destined to have LWW (which is actually a CRDT behaviour), and one user is likely to overwrite the changes of another. This isn’t a great experience for anyone involved.
2. Text editing – everyone’s going to say “but hey, google docs uses operational transform not CRDTs”.. OK yes, but you are not Google. Martin Kleppmann has a great round-up of the various people who thought they implemented OT correctly, but actually didn’t. The reason that you need CRDTs for text editing collaboration is that it’s a really extreme example of collaboration. The nature of text editing is that any tiny errors in the placement of characters by the convergence algorithm is going to create incorrect words, and incorrect words are incredibly obvious. Text editing has a high rate of edits (as you type), and the edits need to interleave perfectly or you get incorrect words, and errors in the interleaving are super obvious (incorrect words)!
Hold, on.. that all sounds great, but..
Maybe I want to use a CRDT even if I don’t strictly need to? OK, yes, maybe you do.
But CRDTs are not without their downsides:
- Ever-growing state: For CRDTs to work well they need to keep a record of both what exists, and what has been deleted (so that the deletes aren’t accidentally added back in later). This means that CRDT state will continually expand. There’s a bunch of magic that CRDT library authors are doing with clever compression techniques to make this problem less-bad, but it’s basically in-escapable. The size of your CRDT state is not purely a function of the size of the state the CRDT represents, but also of the number of updates that state has gone through.
- Complex implementations: CRDTs are easy to implement wrong, so probably don’t roll your own. Instead you’re going to end up using one of the main libraries that are growing in maturity and popularity. These libraries solve the hard internal-CRDT problems for you, like converging updates, and compressing state, etc. But in using one of these, you’re locked into that technology (generally Javascript).
- Opaque state: Because the CRDT has to represent both the underlying state and the updates that led to that state, in order for it’s convergence algorithm to work properly, you’re generally left with an opaque blob of binary encoded data. You can’t inspect your model represented by the CRDT without using the CRDT library to decode the blob, and you can’t just store the underlying model state because the CRDT needs its change history also. You’re left with an opaque blob of data in your database. You can’t join on it, you can’t search it, you can’t do much without building extra features around that state blob.
So maybe you are convinced that CRDTs are not the be-all-and-end-all of collaboration, and that you aren’t in one of the two categories where you probably should use a CRDT, and you’ve made it this far in the post.
What you need instead of CRDTs
Now, let me explain why you don’t need CRDTs, and what you do need instead.
What you do need.
1. Contextual information: The ability to show where a collaborator is in the collaborative environment; who is engaging with the page, which element they’ve selected, and where their cursor is.
2. Locking for safety: The ability to lock them smallest possible individual components or elements of the page to stop conflicting updates from happening.
3. Small-scale updates: The ability to update only the information that has changed, and no more.
4. Realtime fan-out of updates: The ability to share the update a user has made, in realtime, with all the other collaborators in that environment.
Remind me again why this is better? Well, you get realtime collaborative experiences in a data format/structure that you own and control, without suffering any of the downsides of CRDTs. TL;DR - because it works!
Examples of collaboration without CRDTs
I’ll run through a bunch of broad categories of applications, and describe how to make use of these features.
Form builders; Google Forms, Typeform, Attest, etc.
Typically lots of smaller individual inputs. You’re unlikely to want two users collaborating on the same form-box at once.
How to make it collaborative:
- Lock the individual inputs/elements that a user is engaging with.
- Add Contextual information to show which user has locked which element.
- Add Contextual information to allow users to indicate to each other with their cursors.
- Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other.
- Have a mechanism to fan-out the update that each user has made to the other user’s in real-time, so that each can see the other’s changes as soon as the input is unlocked.
Task management; Jira, Shortcut, Trello, Linear
Typically lots of ‘cards’ or ’tasks’ with various properties: status, owner, title, description, project, epic, etc.
How to make it collaborative:
- Lock the individual inputs/elements that a user is engaging with.
- Add Contextual information to show which user has locked which element.
- Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other.
- Have a mechanism to fan-out the update that each user has made to the other user’s in real-time, so that each can see the other’s changes as soon as the input is unlocked.
Spreadsheets; Google sheets, Airtable
Typically a ui of rows and columns. Sometimes the records can expand to show inner-details of that row, similar to the task management apps.
How to make it collaborative:
- Lock the individual inputs/elements that a user is engaging with. For Google Sheets; the individual cells. For Airtable, the individual elements on the record.
- Add Contextual information to show which user has locked which element.
- Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other.
- Have a mechanism to fan-out the update that each user has made to the other user’s in real-time, so that each can see the other’s changes as soon as the input is unlocked.
Picture drawing, presentation and whiteboards; Miro, Figma, Google Slides
Typically free-form drawing combined with preset shapes, stickies, note and text boxes, and more.
How to make it collaborative:
- Lock the individual elements that a user is engaging with, it doesn’t really matter if two users draw over the same part of the canvas. It only matters if two users try and change the same element at once.
- Add Contextual information to show which user has locked which element.
- Add Contextual information to allow users to indicate to each other with their cursors.
- Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other.
- Have a mechanism to fan-out the update that each user has made to the other user’s in real-time, so that each can see the other’s changes as soon as the input is unlocked.
Design patterns
You can start to see a few things here;
- The pattern for building collaborative experiences is almost exactly the same across all of these types of applications that don’t need CRDTs,
- Many of the applications that you know from these broad categories operate in exactly this way. A few examples; cell locking in Google Sheets, post-it locking in Miro and Figma, element locking in Google Slides.