DB3: alexander-akhmetov/raft-kv

Arjun Sunil Kumar
Distributed Systems Engineering
9 min readJun 7, 2022

--

While the RheaKV work is unfinished, I was exploring study resources from other contributors. I got introduced to TinyKV & TinySQL offered by the PingCAP team. This was written in Go and apparently, Go had more market value than Rust. Learning Go was getting prioritized on my to-do list. I was still looking for a smaller code base to start with and see, how Go databases work. That's when RaftKV hit my path. I am about to dissect the codebase of RaftKV to help me understand it better.

Note: I have made some minor changes (updating logger to hclog & using go.mod) in my fork of Raft KV.

Main.go

This is the driver class.

1) Command Line Arguments

https://gobyexample.com/command-line-arguments

2.a) go-flags

This library provides similar functionality to the built-in flag library of go but provides much more functionality and more excellent formatting.

Package flags provide an extensive command-line option parser. The flags package is similar in functionality to the go builtin flag package but offers more options and uses reflection to provide a convenient and succinct way of specifying command-line options

2.b) go-flags syntax

https://github.com/jessevdk/go-flags
https://github.com/jessevdk/go-flags
https://github.com/jessevdk/go-flags
https://github.com/jessevdk/go-flags

3. a) Hashicorp RAFT

https://github.com/hashicorp/raft

The Go based and C based backend implementation for the raft

We are using raft-boltdb (for Go)

We could also use, raft-mdb (for C)

3.b) Protocol

https://github.com/hashicorp/raft
https://github.com/hashicorp/raft

3.c) raft-grpc-example

https://github.com/Jille/raft-grpc-example
https://github.com/Jille/raft-grpc-example

4.a) Imports

Let's start with the imports. We see, that we use

fmt: for printing

log: for logging

os: for getting args

time: for using time.Seconds

github.com/jessevdk/go-flags : for parsing CLI args

4.b) Reading CLI args

The Options are specified as ENV properties in the docker-compose.yaml

  • Node 1 Config contains BootStrap set to true.
  • It doesn’t contain Join env parameter.

Quoting the comments

  • Also, the leader node must start a web interface so other nodes will be able to join the cluster.
  • They send a POST request to the leader with their IP address, and it adds them to the cluster (Shown in Section: Cluster Join)

Something like this

We made Node 1 as a leader.

Node 2 & 3 are similar

They are dependent on Node 1. They have Join parameter.

4.c) Create a new Node

4.d) Passing Storage to a go-routine for a continuous status check

It would print something like this

https://github.com/alexander-akhmetov/raft-kv

4.e) Cluster Join

4.f) Start Gin Server

node/node.go

This is the class that will interact with the raft-boltdb.

1.a) Imports

  • bytes: convert text to bytes so that it can passed via POST call
  • encoding/json: returns json encoding of object
  • net: for resolving TCP Address
  • net/http: For making a POST call
  • path/filepath: Used to join two path names
  • github.com/hashicorp/raft: RAFT protocol
  • github.com/hashicorp/raft-boltdb: RAFT backend in Go
  • github.com/hashicorp/go-hclog: Used for

Here we are assigning a name (rbolt) for the raft-boltdb import.

1.b) Config struct

2.a) NewRStorage — Create RStorage and MakeDir

In the main.go, we are using the below code.

storage, err := node.NewRStorage(&config)

2.b) Create a new RAFT node

Here, you can see that

  • rbold is used to create a new Store having paths: “temp/raft-log.bolt”, “temp/raft-stable.bold
  • filepath is used to Join Directory paths.
  • hclog: We pass hc_logger and pass it to raftConfig.Logger = logger
  • raftTransport(): This function is invoked to create a TCP transport layer

2.c) Setting Bootstrap Config in RaftNode, and Setting that in RStorage

Ultimately we are creating RStorage. We will set the RStorage raftNode to be the updated RaftNode containing the Bootstrap configuration (ie Servers[]).

2.d) raftTransport()

Here we are creating a NewTCPTransportWithLogger

2.e)JoinCluster

  • Defer in GoLang
https://www.educative.io/edpresso/what-is-the-defer-keyword-in-golang
  • Defer body.close after receiving response
https://stackoverflow.com/a/62573623/1609570

2.f) GetClusterServers()

  • Reading configs from RaftNode
  • Used in http.go

2.g) AddVoter

/node/storage.go

Storage.go imports “raft” package and thereby implements the FSM interface for RStorage and FSMSnapshot interface for fsmSnapshot.

1.a) Imports

1.b) RStorage struct

1.c) Get

As you can see, we are reading from the fieldstorage map directly. ie from memtable (save in the application state)

1.d) Set

  • Here we have logEvent struct which is used to hold, the command
  • Inside Set(), we are using sync.Mutex variable, to lock.
  • We defer unlock, until the current task is completed.
  • Now we marshal the object to JSON.
  • and finally, we do, RaftNode.Apply(data, timeout)
  • The RaftNode, as seen the node.go contains raft-boltdb, which is used in the logStore & stableStore .

2.a) Interface

Now, we will sidetrack a bit and learn about Interface in Go.

https://www.callicoder.com/golang-interfaces/
https://www.callicoder.com/golang-interfaces/
https://www.callicoder.com/golang-interfaces/
https://www.callicoder.com/golang-interfaces/
https://www.callicoder.com/golang-interfaces/

3.a) FSM Interface

Apply()

Note, RaftNode.Apply() is not the same as this FSM Apply()

Snapshot()

Restore()

These functions are invoked at fsm.go inside the raft package.

4.a) FSM Implementation in storage.go

  • Apply()

It first locks using the mutex, then writes the value to the in-memory map

  • Snapshot()

Creates a copy of the in-memory map and returns it in the form of fsmSnapshot

  • Restore()

5.a) FSM Snapshot

This is the core element, which does the compaction.

6.a) fsmSnapshot in storage.go

Note that, we are using the first letter to a small case, to indicate that this will be used only within the storage.go

We are now implementing the FSMSnapshot interface.

  • Persist()

I think the sink here is backed by boltdb mentioned in the node.go

  • Release()

server/http.go

The main Gin server.

1.a) Imports

1.b) Setting router

1.c) Get Key View

Here we are reading the value from storage. We are using gin.Context to read the parameter, and return back a JSON response.

1.d) setKeyView

Below is the struct for setKeyData.

  • We are using ` for specifying metadata. in our case, the JSON field name.(TAG)
https://stackoverflow.com/a/37694779/1609570
  • Back Quote in String
https://stackoverflow.com/a/30681107/1609570
https://stackoverflow.com/a/66517683/1609570
https://stackoverflow.com/a/39025049/1609570

Below is the code for the setKeyView function

  • Here we are using the struct setKeyData.
  • we are reading parameter from gin.Context
  • we use BindJSON to read the POST request JSON into the setKeyData object
  • We use storage.set() to set the value.

1.e) JoinView

Here you can see that

  • storage.AddVoter() is taken from node.go, whereas
  • storage.Get() is taken from storage.go

So, how is it working?

Lets now, learn about Methods.

2.a) Methods vs Functions

https://www.sohamkamani.com/golang/functions-vs-methods/
https://www.sohamkamani.com/golang/functions-vs-methods/
https://www.sohamkamani.com/golang/functions-vs-methods/
https://www.sohamkamani.com/golang/functions-vs-methods/
https://www.sohamkamani.com/golang/functions-vs-methods/
https://www.sohamkamani.com/golang/functions-vs-methods/

3.a) Simple Example

server/http_test.go

4.a) Imports

os: used for removing files in the directory

net/http /httptest: httptest.NewRecorder() used for recording response

http: used for creating request

testing: used for testing

github.com/stretchr/testify/assert: used for assertion

4.b) Functions

  • init()
  • getLeaderNode()
  • setupNode()
  • TestGetValue()
  • PerformRequest and assertValue

Conclusion:

This completes a raft-kv, a simple raft based database written in Go. The Logs are saved in BoldDB and uses RAFT for replication. This was a great experience and counts towards my journey in learning Database Internal.

--

--

Arjun Sunil Kumar
Distributed Systems Engineering