I made some
changes to
Gearpad to make it work better on high-latency connections, like the one I use on my daily
commute.
The problem was in my implementation of
optimistic concurrency. This is what I used to store on the server for each note:
At client startup, Gearpad fetches the current content and version of the note from the server. Then, when an edit occurs, it sends the version it has, plus the new content to the server. The server checks to see that the version sent matches the one that it has. If it does, it makes the edit, increments the version atomically, and returns the new content to the client. If the versions don't match, then a different client must have updated the note and this is a conflict. Right?
Wrong. What happens if an edit occurs before a previous edit
from the same client returns? Here are a couple options:
- Wait for the first edit to return, then send the second edit. This will make things much slower, and more complex since the editor state and server state start diverging more and more when the user is faster than the connection to the server.
- Cancel the first edit and send the second one. This is what Gearpad does. But this means that sometimes the server gets the update and increments the version, but the new version doesn't make it back to the client. Then the second edit is sent and the server things a conflict has occurred.
Solution: the server needs to also store the last client that talked to it. It's only a conflict if the version is out of date *and* the update is from a new client. New server schema:
- Id
- Version
- Content
- LastClientId
- NextClientId
This change also involved migrating Gears database schemas, which can be tricky. I'll talk about that next time.