Syncing documents without conflict….

I’ve been very interested in an “old” blog post by Figma’s CTO Evan Wallace about how Figma manages to sync documents between clients and resolve any conflicts that occur. After looking at various CRDT libraries and frameworks (which still interest me greatly) this blog post and it’s simplicity really caught my eye.

I won’t repeat the post here, but simply highlight the key parts:

  • The server stores properties about an object/document. Each property is stored/processed separately to all others
  • When a property is changed on the client, the client first adds a object/property tuple to a unconfirmed change list (for later reference) and then sends the change to the server
  • The server receives the change, saves to persistent storage and then sends the change out to all other clients working on the same object
  • The client receives the change from the server and compares to the change list:
    • If there is no object/property match in the change list, then apply change to client
    • If there IS an object/property match to the change list but it did NOT originate from this client, then this is considered a conflict and we discard the change. This is due to we are expecting our OWN change to be returned by the server in the near future and would then overwrite this change.
    • If there IS an object/property match and it DID originate from this client, then remove it from the change list

The biggest gotcha/surprise with this approach is that obviously the server HAS to send all the changes to all the clients in the exact same order. To keep the simplicity this means that an object can only exist on a single server at a given time. No horizontal scaling for us. This isn’t as problematic as it appears due to the idea that the number of clients subscribed and manipulating the same object is expected to be low. Or at least low enough to not cause a problem for a single server. Though (some limited) testing, this doesn’t appear to cause a problem. If the server dies, another can be started extremely quickly, the clients reconnect and all the user noticed might be a tiny stall.

Now, if you wanted to make sure the server can horizontally scale then the problem of guaranteeing order regardless of which server responds needs to be resolved. But it’s not on my todo list 🙂

I have implemented cross platform versions of the server and client . These are both Go implementations although I’m planning on a some other client libs (maybe C#/Python/Rust?) in the future.

The key details of the server are in the readme

Overall the server is small and simple (which is the whole idea).

There is a quick video of the demo client running on youtube