LoginSignup
9
1

More than 1 year has passed since last update.

独自KVSデータベースを作ってみた

Last updated at Posted at 2021-12-04

初めに

オークファンの開発部に2021年新卒入社した@isodaです。
題名の通り、独自KVSデータベースの設計と開発(実装は途中)を行いました。

経緯

通信プロトコル、ファイルシステム、バイナリなどの理解。
Goを使用した開発や1から設計を行う経験の為、研修の一環として、今回の独自KVSデータベースの設計と開発を行わせて頂きました。

通信プロトコルの設計

通信はTCPを使用し、バイナリデータでのやり取りを行います。

  • DBサーバーへのリクエスト
    • 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の値と同じバイト数の任意の値

ファイルシステムの設計

ファイルに実際どのように、読み書きされるかを設計しました。
バイナリファイルの中身はこんなイメージです。

スクリーンショット 2021-11-26 17.55.53.png

  • バイナリファイルへの読み書きはビッグエンディアン
  • 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を使う機会もあるので勉強になりました。
設計でも実装でも先輩にレビューを頂いたり、レクチャーをして頂いたりしたのでスムーズに進めることができ、感謝しております。

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1