Hacker News new | ask | show | jobs
by jitl 955 days ago
The demo code for Loro looks very easy to use, I love how they infer a CRDT from an example plain JS object. I’ve played with a Zod schema <-> Yjs CRDT translator and found it kinda annoying to maintain. However this looks so easy I worry about apps building with too little thought about long term data modeling. Migrations on CRDTs are challenging, so it’s important to “get it right” at the beginning. I’m curious how this design works with longer term, more complex CRDT apps.
3 comments

The code in the blog is from Vue Pinia, a state management library, and not from Loro. It serves as an example demonstrating that CRDTs can be modeled similarly. Thus, you might expect to use Loro in a similar way.

Indeed, schema migration is challenging. There is a lot to explore, both in reducing the need for migration and in ensuring a smooth transition when it's required. We plan to tackle this issue in the future.

If you're looking at declaring schemas for CRDT docs in Yjs, I've found https://syncedstore.org/docs/ productive if you're open to using TypeScript.

Agreed on migrations!

> Migrations on CRDTs are challenging, so it’s important to “get it right” at the beginning.

Any tips or dos/don'ts you could share for how to "get it right" at the beginning? I'm hoping to build an application using CRDTs sometime in the future.

I don’t have anything better for you than “be careful” and “prototype a lot”. Adding new fields is fine, changing the meaning or type of existing fields is annoying.

You should try to brainstorm a lot of future feature improvements and ideas you might want in 2 years and consider if your v1 design would block or need a challenging migration to support the hypothetical v100 designing. A classic example is if you need a one-to-one relationship in v1, think about how you’d support that as a one-to-many or many-to-many. It might be better to start with a schema that supports many-to-many to avoid shenanigans down the line.

I linked the Cambria essay below which has a lot of food for thought.

I don’t see why migrations on CRDTs would be categorically different from migrations on other data types, though maybe there’s less open source tooling to leverage at the moment. I’ve got some basic code written on automerge to handle them in a side project
It’s more challenging because you need to accept updates in format v1 indefinitely even after you apply the v1->v2 migration, which makes it more like maintaining an API with backwards compatibility than the usual SQL migration pattern. For example, let’s say you start with a last-write-wins CRDT for an object of shape { name: string, bio: string } in v1, and then decide bio should use a convergent rich text data type like Peritext. If you do a naive migration and change the type of the field in-place, how do you handle updates from old peers doing a LWW set on bio, when now the data type you expect is a Peritext delta?

And if you’re really peer to peer, you’ll need older peers that don’t understand a new format in their user space software to avoid applying updates that break compatibility for them.

Geoffrey Litt documents the challenges in Cambria, see https://www.inkandswitch.com/cambria/

The lens solution is clever and, I’m glad to see lenses catching on outside the haskell ecosystem

I think the primary issue you raise comes down to whether or not the peers can be verified to be updated before applying the migration op. If this is the case, you can go the standard sql route of deploying a diff adding forward compatibility, applying migration op, deploying diff removing backward compatibility. This is the route I’ve gone since in my case the peers are web browsers so I’ve got a pretty reliable deployment system; though the lens solution is much better since it lifts this requirement