初めに
オークファンの開発部に2021年新卒入社した@isodaです。
題名の通り、独自KVSデータベースの設計と開発(実装は途中)を行いました。
経緯
通信プロトコル、ファイルシステム、バイナリなどの理解。
Goを使用した開発や1から設計を行う経験の為、研修の一環として、今回の独自KVSデータベースの設計と開発を行わせて頂きました。
通信プロトコルの設計
通信はTCPを使用し、バイナリデータでのやり取りを行います。
- DBサーバーへのリクエスト
- KeyとValueを送る際に、どこまでがKeyでどこまでがValueなどを識別するために、識別文字を入れる方法もありますが、
今回はバイナリセーフにしたいためKeyとValueのサイズを通信に内包する方法にしました
- KeyとValueを送る際に、どこまでがKeyでどこまでがValueなどを識別するために、識別文字を入れる方法もありますが、
- DBサーバーからのレスポンス
- Create/Update/Deleteの際は正常を示す値(0x00) or 異常ならエラーコード
- Readの際は正常系はリクエストのKeyに対応するValueの値 or 異常ならエラーコードが返ってくる
Create/Update(Request)
CreateとUpdateは以下のよう定義します。
Mode + KeySize + Key + ValueSize + Value
具体例:key=hoge value=fugaでcreateを行った時のイメージ
echo "\x00\x05hoge\x00\x04fuga" | hexdump -C
00000000 00 05 68 6f 67 65 00 04 66 75 67 61 0a |..hoge..fuga.|
開始アドレス | サイズ(byte) | 値 | |
---|---|---|---|
Mode | 0x00 | 0x01 | 0x00(C) or 0x02(U) |
KeySize | 0x01 | 0x01 | 0x01 ~ 0xFF |
Key | 0x02 | 0x01 ~ 0xFF | KeySizeの値と同じバイト数の任意の値 |
ValueSize | 0x02 + Keyのサイズ | 0x02 | 0x0001 ~ 0xFEFD |
Value | 0x04 + Keyのサイズ | 0x0001 ~ 0xFEFD | ValueSizeの値と同じバイト数の任意の値 |
Read/Delete(Request)
ReadとDeleteは以下のよう定義します。
Mode + KeySize + Key
具体例:key=hogeでreadを行った時のイメージ
echo "\x01\x05hoge" | hexdump -C
00000000 01 05 68 6f 67 65 0a |..hoge.|
開始アドレス | サイズ(byte) | 値 | |
---|---|---|---|
Mode | 0x00 | 0x01 | 0x01(R) or 0x03(D) |
KeySize | 0x01 | 0x01 | 0x01 ~ 0xFF |
Key | 0x02 | 0x01 ~ 0xFF | KeySizeの値と同じバイト数の任意の値 |
ファイルシステムの設計
ファイルに実際どのように、読み書きされるかを設計しました。
バイナリファイルの中身はこんなイメージです。
- バイナリファイルへの読み書きはビッグエンディアン
- KeyとValueとそのサイズを1つのセットとして、1ブロック0x10000byteでファイルに書き込まれる
- KeyとValueも0埋めを行い固定長で保存
- KeySizeに0x00が入っている場合は論理削除とする
実装
Goで実装を行なっています。
- Client
- 入力した値を通信プロトコルに沿ったバイナリ形式に変換してServerへ送信
- ClientでServerから受け取ったレスポンスを表示
- Server
- 受け取った値をGolangの構造体に入れて、その構造体をバイナリファイルへ読み書きする処理を行い、Clientにレスポンスを返す。
ServerのCreate処理部分のソースコード
package crud
import (
"bytes"
"encoding/binary"
"fmt"
"os"
)
func (b *Block) Create() error {
// キーの存在確認
bf, err := b.Read()
if err != nil {
return err
}
if bf.Ks != nil {
return fmt.Errorf("key already exist")
}
buf := new(bytes.Buffer)
var data = []interface{}{
b.Ks,
b.Vs,
b.K,
b.V,
}
for _, v := range data {
err := binary.Write(buf, binary.BigEndian, v)
if err != nil {
fmt.Println("binary.Write failed:", err)
}
}
file, err := os.OpenFile(b.Cnf.FILE.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
return err
}
defer file.Close()
_, err = file.Write(buf.Bytes())
if err != nil {
return err
}
err = file.Close()
if err != nil {
return err
}
return nil
}
終わりに
今回の経験を通して、設計では、どの様に通信プロトコルを定義すれば、バイナリセーフになるのか。
どの様な形でバイナリファイルに書き込みば、読み書きがしやすいかなどを考える良い機会になりました。
また設計をするのは殆ど初めての経験だったのですが、とても大変だが大切な工程であると感じました。
実装ではGoでの開発が初めてなので、戸惑うことも多かったのですが、今後業務でGoを使う機会もあるので勉強になりました。
設計でも実装でも先輩にレビューを頂いたり、レクチャーをして頂いたりしたのでスムーズに進めることができ、感謝しております。