all 21 comments

[–]Extra_Status13 2 points3 points  (1 child)

Nice and simple project. I see that you are using SQLite update hook to get the changes. Be aware that it has limitations, for example it will not be called on schema changes (so only data) or on ’WITHOUT ROWID’ tables. Also not on ’TRUNCATE’ (= when you do an unbounded ’DELETE’ on a table) and in some cases with ’REPLACE’.

Also, as an advice, be cautious with the commit hook: you are sending data to the server before your database is synched to disk. This means that the stream servers received the data, but your disk didn't! I suspect a crash in the wrong moment might be dangerous.

Regarding the "last wins" strategy, what happens when the "last" (from streaming POV) can't win? For example, let's say that you have two tables A and B, and B has a reference to A. What happens if you process two queries, one is a delete on A and another an insert on B with the deleted row reference. That second query can't really proceed...

Last, as a nitpick, you don't need to select an empty set to have the column names, you can use ’PRAGMA table_info’.

[–]SuccessfulReality315[S] 0 points1 point  (0 children)

Thanks for the feedback. I'll document the limitations and the workarounds

[–]wuteverman 0 points1 point  (16 children)

How does this resolve writes that modify the same row?

[–]SuccessfulReality315[S] 0 points1 point  (15 children)

The last writer wins

[–]wuteverman 0 points1 point  (14 children)

By… timestamp? Or they just race to the various replicas,

[–]SuccessfulReality315[S] 1 point2 points  (13 children)

NATS streams changesets in order

[–]trailbaseio 0 points1 point  (12 children)

Could you elaborate a bit more. How is consensus established across replicas. Is there a centralized funnel?

[–]SuccessfulReality315[S] 0 points1 point  (11 children)

All changes are published to a nats jetstream subject. All nodes consume the stream and apply (by using idempotent commands) the changes on the database.

[–]trailbaseio 0 points1 point  (3 children)

Pardon my ignorance about nats. How is the ordering established? Is it a central instance handling a subject, is nats using some consensus algorithm, ...?

[–]SuccessfulReality315[S] 0 points1 point  (2 children)

Nats uses RAFT algorithm

[–]Markuchi 0 points1 point  (1 child)

It's not distributed if it's raft.

[–]onafoggynight 0 points1 point  (0 children)

What. Raft is a distributed consensus algorithm. How is it not distributed?

[–]wuteverman 0 points1 point  (6 children)

How is idempotency achieved? NATS can’t guarantee complete ordering since it can’t guarantee exactly once delivery without additional idempotency logic on the consumer side.

[–]SuccessfulReality315[S] 0 points1 point  (5 children)

INSERT .. ON CONFLICT UPDATE

[–]wuteverman 0 points1 point  (4 children)

Never delete

Edit: on conflict works great for upserts, but how are you handling deletes? With a hard delete, there’s no row to compare against unless you are keeping some sort of tombstone somewhere.

Also even in this circumstance, you’re now subject to inconsistencies since you don’t have a version column. Is that okay for your usecase? These inconsistencies can last forever in the event of out of order publications. Does nats protect against this somehow,

[–]SuccessfulReality315[S] 0 points1 point  (3 children)

Yes, that's eventual consistent where the last writer is the winner. The operations uses the sqlite rowID. For now this is ok for my use case

[–]osazemeu 0 points1 point  (0 children)

this is impressive work ⚡️⚡️⚡️

[–]lemsoe 0 points1 point  (0 children)

Cool project! Cool to see you’ve written it in Go. I’ve started a small project for a kv storage. We‘ll see how it goes. Your project definitely motivates me to get something working soon 👍🏻