Thanks for the response!
Scuttlebutt because it’s a proven way of dealing with peer syncing, and seamless connection to multiple peers (so we can upgrade to a direct connection to another peer, as well or instead of a server). It’s also a message queue in itself, and will forward the subset of updates a peer needs as it discovers/connects to those peers.
It uses Primus at the moment for ease of demonstration, we has previously used Swarm.js, also can use socket.io and socket.io-p2p. Any transport can be used, just call Scuttlebutt.createSocket() and pipe the new peer’s socket to and from it.
Scaling would have to be on (vertical) subsets of the overall model. As long as a Redux action has a scope or room, and that action does not modify state outside of that scope, we can fetch and broadcast those subsets of the actions/updates/state. In a true mesh topology we may have to rebroadcast scopes we’re not interested in, but in a more traditional one we can simply ignore those scopes.
Secure-scuttlebutt encrypts all messages by default (and provides the pubkey for “public” scopes), while maintaining encryption for “private” scopes. Peers rebroadcast all updates, regardless of whether they can access it or not. We’ll definitely have to implement something similar.
The other option, horizontal scaling, is tackled with event horizon garbage collection — we know the latest timestamp our peers have seen, so we can GC history we’ll never rewind to, and new peers will receive an initial snapshot of the state and all actions which occurred after that. SoundCloud’s Roshi CRDT kinda does this (it’s a lot more forgetful though)
The CRDT actions themselves are up to your Redux implementation. It’s trivial to fire INSERT_STRING_AT_POSITION instead of UPDATE_STRING_ENTIRELY actions. I’d love to provide a module which generates proper CRDT ops in the action creator and applies those CRDT ops in the reducer, but it’d be outside of redux-scuttlebutt itself.
IRT persisting to a database, you would run a peer on your server or dedicated peer, which:
- streams actions to disk as an “action log”— restarting the app simply loads and replays the actions, eventuating in the full state. Either as they happen (time is ensured during replay) or after the event horizon (no older actions will come in, out of order, after this time)
- wait until the “event horizon” (after which older actions won’t come in, so we know the action is committed swarm-wide) then save whatever persistent data to a LevelDB or a structured database. Starting the server would recreate the state from your back-end representation.
Most of the nitty-gritty depends on the use case: a real-time game will probably store everything in a Redux store with an action log for persistence, while a not-so-real-time document editor would update the backing document’s state as changes come in (as this is all it needs to recover).
This is a op-based CRDT model (or CmRDT), where the ops are Redux actions. I’m hoping we can solve the “replaying 100,000 actions on launch” hurdle with event horizon roll-up/commit. Otherwise there’s trivial features (recover from replay error), and theres Great Big Challenges like update encryption/chaining (to ensure Amy can’t broadcast a message as Bob), and other classic CRDT issues. You may notice the secure-scuttlebutt project implements many-many-many modules across its system, and we aim to provide a simpler solution to this directly addressing the Redux state replication use case.
Cheers and feel free (anyone) to shoot though more questions!