Toy, leaderless, distributed key-value store.
gradle clean fatjarmkdir kaukus1
mkdir kaukus2
mkdir kaukus3
./kaukus --system-store-path `pwd`/kaukus1/system &
./kaukus --system-store-path `pwd`/kaukus2/system --system-port 21001 --webserver-port 3001 &
./kaukus --system-store-path `pwd`/kaukus3/system --system-port 21002 --webserver-port 3002 &➜ ~curl --request POST --data 'bar' localhost:3000/kaukus/namespace1/foo
➜ ~curl localhost:3001/kaukus/namespace1/foo
bar%
➜ ~curl localhost:3002/kaukus/namespace1/foo
bar%Right now you need to issue a 'q' command to the nodes to terminate them "cleanly". If you don't, you'll need to run them with a 'reset' command to clear their state.
Key-value pairs are stored using an LSM-Tree; WAL file for crash recovery.
Real loose, tenative plan:
- ServerAgent binds and listens to a socket
- Default starting port with incrementing or arbitrary selection on conflict
- Doesn't really need to be known provided discovery can work (via IP multicast)
- DiscoveryAgent listens on a multicast group
- ClientAgent broadcasts and says "Hi!"
- DiscoveryAgents receive "Hi!" message and
- Could just issue a connection to the new client?
- Could respond with a broadcast of their own (probably not this)
- Clients/Server periodic heartbeats
- Clients periodically send out their client "phonebooks"
- Phonebooks basically consist of known clients/info, latency times, other metadata
- Other metadata might mean suspected dead clients, suspected faulty/questionable clients
- Given heartbeats clients can propose a leader vote
- When no leader / dead leader
- Node volunteers as leader if they know of > 2 other nodes (requires at least 3 total nodes)
- Proposal includes an epoch number (their last known value + 1), a clout number (random number)
- Other nodes respond yes if they know of > 2 other nodes (including the proposer)
- Conflicting vote proposals
- Proposer with the greatest clout wins (Agreement)
- Conflicting random number (Agreement)
- Nodes conscede to the proposer with the lexicographically greater node ID
- Nodes who share their random number reset their random generator, seeded with the current time
- Might fail on same system - could seed based on time + port?
- Nodes vote
- Nodes who know of a leader with a higher epoch respond with no and the current epoch
- Nodes who have responded yes to a previous proposal with a higher epoch or clout number respond with a no and that epoch/clout, and the proposal counts they've responded to
- Proposer nodes who receive a proposal with a higher epoch/clout abort their proposal
- Nodes otherwise respond yes
- Proposers wait (possibly forever) to receive a majority of yes or nos
- Proposers who receive a majority of yes acknowledgements sends out a message saying they're the leader
- Nodes who receive the leader message update their epoch/leader mapping
Pretty naive implementation that uses a two-ish layer approach:
- In-memory hash map of key/values
- WAL log for all actions not persisted to disk
- Cascading, append-only SSTables
In-memory for hot access (though reads aren't transferred over yet; only writes).
- WAL is appended with a new key/value
- In-memory hash map is updated with key/value
- Current in-memory hash map is written out to disk as a successive SSTable
- WAL is cleared