To implement replication, we will rely on the SWIM protocol, and on BadgerDB’s incremental backup feature.
The hashicorp/memberlist
package provides what they call a “TCP Push/Pull”. Periodically, each node computes their local state, send them to other nodes, which then merge the remote state with their local state.
We will trigger the replication during this “TCP Push/Pull” process.
We defined a node state as: the set of last known version of other nodes.
For example:
{
"node_id": "flowg-0",
"last_sync": {
"flowg-1": {
"auth": 1,
"config": 2,
"log": 324
},
"flowg-2": {
"auth": 0,
"config": 0,
"log": 0
}
}
}
When flowg-1
receives this state from flowg-0
, it will start an HTTP request to flowg-0
’s management interface and run (in parallel):
// POST http://<remote>/cluster/sync/auth
newAuthSince, err := authDb.Backup(
syncAuthRequestBodyWriter,
remoteState.LastSync["flowg-1"].Auth,
)
// send `X-FlowG-Since: <newAuthSince>` as HTTP trailer// POST http://<remote>/cluster/sync/config
newConfigSince, err := configDb.Backup(
syncConfigRequestBodyWriter,
remoteState.LastSync["flowg-1"].Config,
)
// send `X-FlowG-Since: <newConfigSince>` as HTTP trailer
// POST http://<remote>/cluster/sync/log
newLogSince, err := logDb.Backup(
syncLogRequestBodyWriter,
remoteState.LastSync["flowg-1"].Log,
)
// send `X-FlowG-Since: <newLogSince>` as HTTP trailer
On the node flowg-0
, the HTTP handler for /cluster/sync/...
will read the request body:
nodeId := request.Header.Get("X-FlowG-NodeID")
err := db.Load(syncLogrequestBodyReader, 1)
updateLocalState("log", nodeId, request.Trailer.Get("X-FlowG-Since"))
And voilà! We have actual replication.