Closed coodoo closed 8 years ago
Great question! For background, I have significant experience with React and Redux and am only getting into scuttlebutt
and CRDTs in general so I greatly appreciate feedback about this.
To me it was by default — researching how CRDTs (the Commutative kind) work I noticed that CmRDT "operations" are kinda like "actions", they're applied to the state. Redux time travel allows updates to be commutative since we can apply the update at any point in time. The underlying data structure is a pure result of the actions (
and a lot smaller/cheaper to sync over a network edit: not strictly true)
The "Conflict-free" part is up to your reducer. If you have conflict-free actions and reducers, you have a conflict-free app. This is not always the case — two peers might dispatch PICK_UP_ITEM
, which [precondition] requires an item to exist and then [result] removes it from the world. One of these peers/players will conflict and their action will be un-done. Conversely, INSERT_TEXT_AT_POSITION
is a conflict free action: you can always insert text at a string position, if the string exists.
Thanks for the explanation, mostly make sense to me 👍
As a side note, earlier this year I built a prototype along this train of thought but synced the underlying data model, scuttlebutt/Model
in that case, between clients, on top of that it updates the redux store, and in turn refreshes the app, the idea was taking data model as the single source of truth and let it handles conflict resolution and stuff, hence making redux store a passive actor.
Goal of that POC was offline-ready
, when clients went offline, changes they made could be persisted locally and guaranteed to sync and converge once online, scuttlebutt
and crdt
makes this really easy to implement.
I would assume in your approach you will persist multiple actions
while offline and batch apply those once online and synced, wondering how's it working so far (performance, correctness, scalability)?
Actions dispatched while offline are stored in the history all the same, and upon new connection are synced. This is all scuttlebutt
, we just return all "new" updates through the history(sources)
implementation.
We currently persist all actions
forever, and a snapshot of the state after each action was applied. This makes it more efficient to replay actions since we walk back through history until thisTimestamp > historyItemTimestamp
, reset the state to the snapshot, and replay all actions including the new one.
Otherwise: it's as fast as Redux/JS (very fast), actions/updates are emitted to peers at dispatch time (the scuttlebutt protocol is designed to be low latency), and there's very little "heavy lifting" going on, so it's efficient*
* With the exception of having to sync all actions on application start — It calls reduxStore.subscribe
handlers for every update. I'm fixing this up today.
Sounds good, is that project up somewhere to see?
We currently ensure ordering of actions in the reducer by adding a meta.@@scuttlebutt/TIMESTAMP
key to the action when it first enters the Dispatcher, and referring to this at reduce time. The reducer is here
Closing this as I think we've addressed the questions :)
I have significant experience with
scuttlebutt
and related data types (crdt
in particular), andredux-scuttlebutt
looks like a really interesting way to approach syncing among users, just wondering why you choose to sync the actions, instead of the underlying data structures, say thestore
orstate
itself? Wouldn't it be simpler to keep one single source of truth and sync it across users?