Ditto version 4 (V4) will be a significant internal database upgrade compared to past versions, with performance and design improvements. This blog post covers the most consequential change to the CRDT that powers the database.
V4 introduces AddWins CRDT merge behavior to replace the existing RemoveWins merge behavior. This is no easy feat, as we will explain below, but we believe this change is critical for our customers to build best-in-class experiences.
In V4, the merge logic for the remove
operation in the CRDT will change, and the new CRDT Version 4 is incompatible with Version 2.
This is not something we take lightly, and we want to stress that we don't foresee a huge breaking change like this happening
again. We know getting all mobile users to update to the latest version of an application is challenging.
4.0.0
in your
language package manager.disableSyncWithV3()
.
For more information, continue reading.Ditto version 2 (V2) uses a CRDT with a RemoveWins merge strategy. RemoveWins applies when a peer (including the Big Peer) concurrently removes a document while another peer updates the same document. In RemoveWins, the remove operation persists, instead of the update operation. In other words, the concurrent update is lost.
Let's explore a RemoveWins example to understand why this behavior causes unexpected problems. Assume two peers have synced the same
document with the following shape at T=1 in the products
collection:
{
"_id": "abc123",
"productName": "Peanuts",
"price": 4.00,
"meta": {
"updated_at": "2023-01-17T00:16:40.890Z"
}
}
Both devices go offline, and each makes a concurrent change at the same logical timestamp T=2:
products
.findById("abc123")
.remove()
products.upsert([
"_id": "abc123",
"productName": "Peanuts",
"price": 4.99,
"meta": {
"updated_at": "2023-01-17T00:18:40.890Z"
}
])
When the devices go back online and sync, they converge to the same state. With the RemoveWins merge strategy,
the remove
call is destructive. The document with _id="abc123"
is no longer in the collection. As a reminder,
remove
deletes documents from the local device and all other peers.
There is one major problem with RemoveWins — it is nearly impossible to “undo” a remove. To “undo” the remove
with this
CRDT, the device performing the (”undo”) upsert must observe all “deletes” to win. If it misses any of them, the upsert will lose to any of the unobserved removes eventually. In production apps, it's impossible to coordinate all devices to re-add the document at a particular time. To illustrate this problem,
remember the scenario above. If Device A is offline for some time and connects back to the network, the tombstone will become
“resurrected” — i.e., active again, and will overwrite all other changes made.
In other words, if you call remove
on a document with a RemoveWins CRDT, your application should consider this document gone
forever throughout your system. In the above example, a device can never create a document with the same _id
in products
. To
make this even more complex, this behavior also applies to the Map type inside of each document. This behavior is both
destructive and unpredictable, especially in a disconnected environment where lots of devices go in and out of connectivity.
In an AddWins CRDT, any device can “undo” the remove by calling upsert
on the document with _id=abc123
again. Essentially,
the new AddWins merge strategy prevents documents from being erased with no recourse once any person calls remove
.
So I know what you're thinking now. RemoveWins sounds like a bug field! How do I get this awesome new AddWins functionality?
At Ditto, customer data and the continuity of the application experience are of utmost importance to us. Because of this, we've dedicated immense engineering resources to ensure customers can transition from RemoveWins to AddWins.
Below we cover each migration step in detail to ensure you can successfully make this change and begin to leverage the benefits of AddWins.
offlinePlayground
, and SharedKey
identities will continue to workIf you're on Version 1 or 2.0x, upgrade to 3.0x. For the full V3 migration guide, click here.
V3 is an intermediate version that knows how to synchronize with V2 or V4 — but not both simultaneously. V3 prepares the local data store for the AddWins migration in V4.
Ensure all devices are on Version 3.0.x, then continue to the next step of upgrading to version 4. For the full V3 migration guide, click here.
V4 can synchronize RemoveWins(V3) and AddWins changes, but it cannot sync with V2. Before you deploy the V4 SDK, ensure all devices on your application have been updated to V3. This is an epidemic that synchronizes to all peers with the same AppID.
Ensure all devices are on Version 4, then continue to the next step of enabling AddWins.
V4 has a feature flag that when called will disable RemoveWins and enable AddWins. This feature flag
is disableSyncWithV3()
. Once this feature flag is called, all devices in the system will transition to AddWins.
Because V3 only synchronizes RemoveWins changes, V4 clients will no longer be able to synchronize with V3 once this
flag is enabled. It's important to ensure that all of your end-user devices have updated to V4 before calling the
feature flag.
ditto.disableSyncWithV3()
Once all devices are on Version 4, enable the AddWins CRDT by calling ditto.disableSyncWithV3()
.
There are two parts to the migration: the local SDK, and the Big Peer. You handle the Local SDK, and we will handle the Big Peer.
find("abc123").remove()
in V3, and then call disableSyncWithV3()
, the document with _id=abc123
will still be marked as deleted as it would in a RemoveWins system. These old removes are not converted to AddWins.1. Can I skip a version?
We highly recommend that customers with production scenarios do not skip versions. As outlined above V4 is not compatible with V2 and skipping V3 could result in devices that cannot sync. If you are in development mode only then you can skip from V1/V2 directly to V4.
2. What versions are compatible?