Automerge 2.0
automerge.org
automerge.org
I particularly enjoy the performance improvements. I benchmarked automerge 18 months ago and the benchmark took about 5 minutes to process the edits of a paper. Some single character inserts took as much as 2 seconds of cpu time. From the article it looks like this entire editing trace (of 260000 keystrokes) is down to 600ms. That’s a huge improvement. It means automerge is similar in performance to yjs, and in turn that makes automerge useful for a much broader set of applications.
One thing I really enjoy about the collaborative editing space is how much ideas are shared around. The highly compact binary encoding was done first in automerge, then copied and tuned in yjs and diamond types. The idea of using an internal list rather than a tree went the other way - yjs came up with the idea, and that approach has landed in all production sequence CRDTs that I know about.
There’s a bunch of work in the pipeline around non-interleaving, BFT properties, database interoperability and more performance tuning that we are (collectively) still figuring out. But the future of CRDTs seems bright. In a few years I’d love all new software to be built on local first fundamentals. Work like this is how we get there.
To everyone involved, great work! Keep it coming!
I'm building a mobile app with a server backend, and I was looking for resources to build them in an offline-first way (since unlike on the browser, people expect to use apps offline, if they can, such as fitness or habit trackers).
I found the concept of conflict-free replicated data types (CRDTS) interesting as it allows you to have fully offline experiences while also having a conflict-free syncing experience. I was looking for some good libraries and came across automerge [0] and yrs [1], but both had some rough APIs as they're primarily low-level Rust libraries that are wrapped by higher-level TypeScript APIs.
Autosurgeon wraps the low-level API of automerge to make it much more ergonomic, closer to the TypeScript experience, but in Rust of course. You can for example use `struct`s which autosurgeon will serialize and deserialize automatically, which is not present in base automerge, which focuses more on string keys and arbitrary values.
I am planning on using this together with Flutter and flutter_rust_bridge [2] in order to use this same Rust library everywhere. In this case, the server just becomes another (albeit more privileged) client.
[0] https://github.com/automerge/automerge-rs
CRDTs can best be thought of as a way to eliminate spurious and false conflicts, leaving only real errors. Without them anyone who has ever coded a data merge knows you tend to get a ton of noise.
So basically you have reduced the problem surface area.
Note that from a different user perspective, say a code document, such a conflict is actually correct and desired. So it's all about context.
Yjs (pure javascript?) is quoted on the paper benchmark at 1,074ms and 10,141,696 bytes of memory, compared to Automerge 2.0.2-unstable at 661ms and 22,953,984 bytes of memory. It looks like Automerge 2 latest is faster than Yjs, but still uses 2x more memory.
I wonder if this is comparing usage from JS via bindings, or directly comparing two different rust implementations, or comparing Automerge 2.0.2-unstable via Rust to Yjs via NodeJS.
I am still not sure which set of tools I would recommend; I believe Yjs is more actively deployed in production since the Automerge implementation was so far behind performance wise until now. However one of the Peritext authors (https://twitter.com/sliminality who is on my team at Notion) tells me that Automerge is better at text because it doesn't suffer from interleaved characters like Yjs does. So consider it instead of Yjs!
Adding this optimization to diamond types, in pure rust, improved performance by another order of magnitude (25ms for the same test with this tweak). It also dropped memory usage to about 2MB. The automerge engineers know about this trick (I’ve talked to them about it). So I assume it’s in the pipeline somewhere. And yjs is working on a rust reimplementation, which should bring its performance in line too.
Currently for this use case I've been using autosurgeon [1] so far which has a nice Rust API for structs, even if it might be slower than yjs (or yrs, its Rust implementation) or diamond-types.
I'll look at autosurgeon. Having similar APIs is good for everyone.
Its not a fair test if you'll be running this code in a browser, since the rust code will need to be compiled to wasm (and suffer a ~3x performance penalty), while the javascript code will run at the same speed. But whether that matters to you depends on what you're doing.
Some of that difference is a lack of auto-vectorization in wasm. Wasm SIMD is pretty new, and not well supported in wasm runtimes yet as far as I know.
I'm sure the use case matters a lot. But at least in come cases the result can be very close to native.
Automerge CRDT – Build local-first software - https://news.ycombinator.com/item?id=30881016 - April 2022 (8 comments)
Automerge: A JSON-like data structure (a CRDT) that can be modified concurrently - https://news.ycombinator.com/item?id=30412550 - Feb 2022 (69 comments)
Automerge: a new foundation for collaboration software [video] - https://news.ycombinator.com/item?id=29501465 - Dec 2021 (29 comments)
Automerge: A library [..] for building collaborative applications in JavaScript - https://news.ycombinator.com/item?id=24791713 - Oct 2020 (1 comment)
Automerge: JSON-like data structure for building collaborative apps - https://news.ycombinator.com/item?id=16309533 - Feb 2018 (98 comments)
- Local First Software [https://news.ycombinator.com/item?id=31594613 (28 comments)] by Martin Kleppmann (who works on Automerge at the company Ink and Switch, perhaps better known as the author of Designing Data Intensive Applications), which introduces Automerge
- CRDTs: The Hard Parts [https://news.ycombinator.com/item?id=23802208 (124 comments)], a video talk also by Kleppmann
- CRDTs go brrr, 5000x faster CRDT implementations [https://news.ycombinator.com/item?id=28017204 (151 comments)], by the creator of another CRDT in Rust library, Diamond Types [https://github.com/josephg/diamond-types]
For anyone not up to date with the world of CRDTs, Seph Gentle's two blog posts have become legendary:
* https://josephg.com/blog/crdts-are-the-future/
* https://josephg.com/blog/crdts-go-brrr/
these are also worth checking out:
* https://github.com/y-crdt/y-crdt (rust implementation started by the creator of Yjs, Kevin Jahns)
* https://github.com/y-crdt/ypy (python bindings for the rust implementation)
* https://github.com/josephg/diamond-types (Seph Gentle's rust implementation of YATA, the algorith behind Yjs)
CRDTs are the future https://news.ycombinator.com/item?id=24617542 312 comments, https://news.ycombinator.com/item?id=31049883 45 comments
Faster CRDTs: An Adventure in Optimization https://news.ycombinator.com/item?id=28017204 151 comments, https://news.ycombinator.com/item?id=33903563 22 comments
One can write custom conflict resolution and treat the data structures as a convenient baseline for event sourcing, but that requires a lot of work and potentially often user guided resolution.
I'd really love to see some research into deriving CRDT merge semantics from a formal description of application behaviour.
What we probably want from a distributed system is useful convergence properties that preserve the intent of the participants. A CRDT might not be a good fit for a bank account: if we can both withdraw the last $20 from my account, the bank will be upset. On the other hand, it's a pretty great way of combining independent observations into a list: it doesn't matter what order the observations arrive. Easy!
Most CRDTs aim to preserve causality: if I see your change, and then make my change, my new value will win. If we both make changes without knowing about each other, that's a conflict.
Of course if we both edit unrelated fields -- maybe it's not a conflict! At least, that's how we handle it in Automerge.
In the most conservative case, we should never merge data automatically. Most systems have unmodeled constraints. For example, sometimes a `git` merge will produce no conflicts but fail to compile anyway. Git's model (another CRDT) doesn't model program behaviour, nor do we expect it to. In this case, we rely on a combination of our experience, programming tools, and git's version history tooling to figure out what went wrong.
The conclusion I have is that a CRDT should give us robust tools for minimizing conflict, but also needs to be able to explain how things got to be the way they are and what you can do to make them how you want.
We've made a decent amount of progress on this in Automerge and have a paper coming up about this problem soon but I agree there's still more distance to go. If there are particular questions you have about merge semantics, I'm all ears! We'll continue to explore this space for the foreseeable future and I love to hear about new questions.
The last thing I want to add is that when you say "CRDTs are only really useful for a narrow subset of data", you're really drawing a lot of conclusions all at once about other people's needs and interests. From my perspective, CRDTs are useful for a lot of kinds of data. Not everything certainly, but from where I sit, perhaps more kinds of data than a limited single-node relational database and more kinds than a POSIX file which doesn't retain any history at all.
Yep I 100% agree.
I think the highest value uses for technology like this are in creative applications. I think about wikis, blogs, shared whiteboards, music production and video editing. In all of these cases, "referential integrity" (database constraints) don't really matter that much, and the working set is usually pretty small.
Sketch was outcompeted by Figma because figma used a CRDT as its backend, which enabled it to be collaborative. Sketch had an arguably better product, and was first to market. But it was stuck in the single-editor model because they didn't have a tool like automerge.
As for conflicts, increasingly my favorite CRDT for "general purpose" data (JSON trees) is MVRegisters. In the case of a conflict, a MV (Multi-value) register stores all of the conflicting values. But the application doesn't have to care - we can still treat it like a "single writer wins" register.
To make this work, the CRDT provides two APIs: a simple API and a complex API:
- The simple API just gives the application "the current value". In the case of concurrent edits, the system quietly chooses a winner. This is enough for most software most of the time. Its certainly enough to get started.
- The complex API returns all current values when a conflict has happened. Applications further along in their development lifecycle can use this API to present conflicts to the user and ask the user what should happen. (Or the application can resolve the conflict itself using application-specific logic).
The nice thing about this approach is that the data itself doesn't have to change. Its just an application / UI change to show conflicts. So collaborative applications can be written without caring about conflicts (at first). And later, when conflicts between multiple users cause problems, the applications can move to a richer API if they want to. (And remember, it all works like git under the hood anyway. We can store the full history so even when conflicts are resolved in a weird way, you still haven't lost the users' original edits.)
The CRDT examples I see are all oriented around human collaboration, are they a bad choice for something more akin to a distributed database?
1. Allow the user (of the CRDT library) to define a fitness function that should be minimised
2. When multiply valid merges are possible, pick the result according to the fitness function
I haven't kept track of CRDTs since I worked with them in ~2015 and having read the paper by Shapiro et al, but I thought a casual description would be more along the lines of "once we both receive each other's changes, we will agree on the final state"? Or does that no longer reflect current state of the art, or was I just mistaken at the time?
I'm no expert, but I believe synthesis approaches are a strong step in that direction: https://arxiv.org/abs/2205.12425
I'm learning web programming and this seems quite useful for what I'm doing. Any tips on how to do it in php with this?
Eventually you’ll want to implement this using web sockets for performance, using a system like Laravel Broadcasting.
You can also package the total state JSON as a compact binary and store it in your server app, but you wouldn’t use that file to keep clients synced.
What are my options for that? There's no PHP library for that, it seems. Is their goal to have someone build a PHP C extension (or FFI) to call down to their library? That seems not very fun, because it's somewhat less portable than having a pure PHP implementation (even if it might be less performant).
Otherwise I think you’d be looking at a headless browser in the test runner.
[0]: https://docs.yjs.dev/api/document-updates#syncing-clients
https://automerge.org/docs/cookbook/real-time/#changes-inter...
Local first in my mind means your users can still work even if your server is down for a small amount of time for any reason, which means the users can also jump on an airplane and decide to review/update their price table or check out their sales commissions if they want. It benefits both sides of the software economy (users and providers)[1].
I recommend the reading the paper from 2019 about local first it is not too academic and gives a good view of the challenges [2].
[1] https://mateusfreira.github.io/@mateusfreira-2022-12-04-my-t... [2] https://martin.kleppmann.com/papers/local-first.pdf
I built a personal project with automerge 1 recently because I liked the philosophy of offline-first from the team and because the docs were frankly much more approachable than yjs but ended up switching to yjs half way through because of performance issues and also for the rich text support (via the delta doc type).
A little bummed about 2.0 not working with react native because of webassembly but excited to see the peritext work progress for rich text coming soon.
CRDT is one of those technologies I assumed was “done” a decade ago and I was surprised to learn how much of the major progress was only just recently made when I dug in.
Definitely expecting to see some cool new multiplayer startups built on this tech.
It doesn't merge documents automatically - it deterministically picks a "winning" version of the data, but the winner is completely arbitrary. You need to look at the conflicting versions to properly resolve conflicts, otherwise you're pretty much just rolling a dice and saying "yeah that version of the data will do whatever". There's no actual merging going on.
Hint to whoever wrote this: except the offline thing, this has not been my experience at all. Automerge sounds cool enough as is, no need to make up reasons why Cloud Bad.
At least in theory.. anyone used this?
I’ve made a schema-aware CRDT previously that can chew thru concurrent JSON schema change, doing the same for SQL is certainly doable but a huge amount of work
What do you think a todo app should do when it detects a conflict/concurrent update?
Automerge... the same approach would work but Automerge advertises itself as storing the full history, so I think the history of the root object would leak the data. I am not sure if it's possible to erase such history with Automerge.
I like full history most of the time, but there are situations where hard delete is a... hard requirement.
AFAIK Google Docs uses OT, for example.
edit: there's no way to delete a submission? weird
dang: any way you'll see this and fix the URL?
* Our software canonizes URLs when it can. I suppose we could make sure the canonical doesn't 404 before doing that, but it's a bit tricky when there's an additional redirect etc.
This is not my understanding of what a CRDT is. From Wikipedia:
> [CRDTs] send their full local state to other replicas, where the states are merged by a function which must be commutative, associative, and idempotent. The merge function provides a join for any pair of replica states, so the set of all states forms a semilattice. The update function must monotonically increase the internal state, according to the same partial order rules as the semilattice.
For those people saying there is no such thing as conflict-free, there is! But only for datatypes that satisfy these constraints.
ref to my implementation: https://github.com/munhitsu/CRAttributes https://github.com/munhitsu/CRAttributesDemo
I can see how this would be used for 'live data sharing' but what about for more persistent information, like documents, designs etc?
Correct, this is what CRDTs are for, eventual consistency.
So as for documents etc, if you can find or come up with a CRDT where the automatic merging function will give you something that makes sense for a user - sure.