Crsql – Multi-writer and CRDT support for SQLite
github.com
github.com
This would be better served as an INSERT, since it's a modifying operation. I suggest INSERT INTO crsql VALUES ('as_crr', 'foo'); (this involves creating an ephemeral table called crsql). Similarly, instead of select crsql_finalize() you should use INSERT INTO crsql VALUES ('finalize'). This is the preferred way of doing this kind of metadata operation in SQLite, see https://www.sqlite.org/fts5.html#special_insert_commands for some examples.
Does this support CRDTs other than last-write-wins registers?
I personally am working on this problem, but I am approaching it from ephemeral tables. This allows lots more flexibility at the cost of more complex syntax. For a simple last-write-wins register, you might use something like:
CREATE VIRTUAL TABLE foo USING crdt(col LWW);
INSERT INTO foo VALUES (1);
UPDATE foo SET col = crdt_set(2);
SELECT col, crdt_lww_timestamp(col_meta) FROM foo;
This weird update syntax allows you to use non-standard operations, though, which then allows you to implement things like ordered sequences (text) in a conflict-free manner (e.g. SET col = crdt_text_insert(col, 5, ', world')).I like the idea of moving the syntax to inserts and had not seen https://www.sqlite.org/fts5.html#special_insert_commands
Will give it a shot & thanks for the resources.
> I personally am working on this problem, but I am approaching it from ephemeral tables.
Would love to trade ideas if you're open to it. @tantaman on twitter.
> Does this support CRDTs other than last-write-wins registers?
Not yet. I was thinking of collaborating with Seph to bring DiamondTypes in for sequence crdt support. If that is even possible -- just an idea at the moment.
Notwithstanding, there are a lot of possible changes that can compose well, but with traditional databases it is hard to merge divergent datasets even if the changes are non-overlapping. That is where solutions like this improve the state of the art - by obviating a need for secondary sync/merge layer on top.
For the actual cell level conflicts the application developer will have to either write custom resolution or model the domain so that they can be resolved by user later.
It's not very clearly stated, but the links show it has "Last Write Wins" semantics for conflicting changes
https://bartoszsypytkowski.com/crdt-map/#crdtmapwithlastwrit... https://bartoszsypytkowski.com/operation-based-crdts-registe...
(I can have a look, I'm just not very familiar with go intricacies)
Indeed it can be argued that CRDTs are a natural fit for SQL because it is essentially a derivative of set theory
As I understand it, the only guarantee we get from all CRDTs is that all copies end up in the same state irrespective of merge order. And then there are some trivial examples, like counters, where this state is also logically consistent. But that is not guaranteed in general.
So what about the classical example of transferring an amount from account A to account B if account A has sufficient funds?
I think for a CRDT to support such cases (for arbitrary schemas) it would have to have some notion of atomicity and some way to specify preconditions.
https://en.wikipedia.org/wiki/Conflict-free_replicated_data_...
While SQLite extension authors are free to choose whatever license they wish, I wish more would follow the core’s example.
(I realise there are legal doubts about the validity of “public domain” in some jurisdictions - I think ultra-permissive licenses such as 0BSD are effectively equivalent to public domain. Apache is a lot more restrictive than 0BSD.)
I do actually like the sqlite blessing. It is really wholesome.
1) Does the CRDT require synchronized clocks? I always felt that was the weakness of stuff like Google's Spanner (and its main competitor, can't remember the name). There just has to be some other way to derive ordering, maybe using pseudorandom sequences generated from the same seed or using time deltas from the last update or something? Although those might be spoofable/cheatable.
2) Is there any workaround for the lack of foreign key constraints? Those are critical to what makes query builders like Laravel's Eloquent relations easy to reason about (especially for deletions and other cascade operations). For example, maybe a dirty flag on the row could get cleared once eventual consistency was reached and all FK constraints were met? I think the other limitation with uniqueness on only 1 primary key could be addressed through composite keys or a hash of other keys somehow, so is not a dealbreaker.
No. CRDT are Commutative. You can apply a set of updates in any order and achieve the same result. This is a core feature.
"Provably, replicas of any CRDT converge to a common state that is equivalent to some correct sequential execution. As a CRDT requires no synchronisation, an update executes immediately, unaffected by network latency, faults, or disconnection"
https://news.ycombinator.com/item?id=33694568
The secret sauce is the use of a Lamport timestamp (instead of a clock) as a logical time counter, which is a sequence number or local count of the number of writes. The sequence number gets sent by each peer for each write. Most of the time, the CRDT can derive which event happened first by comparing sequence numbers from each peer.
In pathological cases, like two users making a slightly different edit to the same item with the same last-known state on two different devices while they're in a tunnel disconnected from wireless internet, a tie-breaking rule determines which edit wins. That could be as simple as favoring the peer id which comes first alphanumerically.
In practice, peers write to the CRDT and then receive a series of update events that may oscillate or contradict the peer's notion of what happened, until eventual consistency is reached. So in a video game, both players might shoot and think they killed one another until the CRDT determines that one player shot first. As long as the game is stateless and can reflect the last known state received from the CRDT, then it will eventually come to correctly show that one player won. But it might have some undesired animation in the meantime showing the wrong player winning.
Also I suspect that a real game would have a more advanced tie-breaking heuristic that does include some notion of local time. For example, if the max ping is capped at 5 seconds or something, then the CRDT could use dead reckoning to place constraints on when each write could have happened. But it would only be used in rare cases when ordering can't be derived deterministically. It could also use a majority vote from each peer like with Raft/Paxos (I think), but that might stray into something too complex to implement or too vulnerable to cheating.
I started writing a postgres synchronizer.
I got as far as hashing the data In the database but I need to write a synchronization endpoint that does similar to a rolling hash to determine what data to send to other clients.
One problem I encountered is detecting the winning write.
I am hoping it does not get into the official releases. At least not for a couple of years.
I was trying to re-implement a JSON CRDT on top of SQLite for my notes app, but this just made the whole database a CRDT!
This would only apply to Postgres, if there is multi-master replication, and the writes for a particular object can go to multiple master databases. Multi-master replication means that there are multiple backend databases that can accept writes.
Typically you avoid this scenario by partitioning writes for an object to 1 master database. This way each master database is responsible for its own disjoint set of object and so there is no chance of conflicts.
Another way to avoid the issue is to just avoid multi-master replication, and stick to only having 1 master database to accept writes.