go in-memory database generator
go の型安全かつスレッドセーフなインメモリデータベース生成パッケージを作成しました。
simple
なDB
でsimdb
という名前にしました。
go1.18 に沸く昨今時代錯誤かもしれないですが、MIT で公開してるので気に入ったら使ってみて下さい。
こういうのと組み合わせても面白いかも...
やってること
単純に設定(config)ファイルから必要な情報を取得して排他制御付きのmap
を保持した型を生成します。同時にGet
やInsert
等の単純なメソッドの実装も生成します。
例えばベンチマークの計測に使った構造体は以下のような形です。
type (
// カラムを纏めた構造体
bench struct {
Name string
Email string
age uint
IsValid bool
}
// テーブル キーで括られたカラムのまとまりを持ちつつ、
// sync.RWMutexが埋め込まれているだけ
benchs struct {
data map[int]bench
sync.RWMutex
}
)
ただカラムを纏めた構造体を指定された型のキーでmap
にしてその構造体を持ちつつ、排他制御用にsync.RWMutex
を埋め込んであるだけです。
sync.Map
との違いは型安全なことと、無駄なキャストいらずで少しだけパフォーマンスが高いことです。
パフォーマンス
sync.Map
との簡単なベンチマーク比較です。生成されるテーブルのカラムの数などによってここは変わってくるとは思うのであくまで参考です。
一件インサート系
BenchmarkInsert-8 4487815 250.8 ns/op 246 B/op 0 allocs/op
BenchmarkInsert_SyncMap-8 2824500 432.6 ns/op 185 B/op 5 allocs/op
一件取得系
BenchmarkGet-8 12561126 116.3 ns/op 0 B/op 0 allocs/op
BenchmarkLoad-8 11806761 119.4 ns/op 0 B/op 0 allocs/op
BenchmarkLoad_SyncMap-8 7745020 177.0 ns/op 0 B/op 0 allocs/op
一件削除
BenchmarkDelete-8 11140561 125.1 ns/op 0 B/op 0 allocs/op
BenchmarkDelete_SyncMap-8 7712466 175.9 ns/op 0 B/op 0 allocs/op
生成の仕方
ザックリと使い方を説明します。
カラムとテーブルが定義された yaml や toml もしくは json 等を用意します。
デフォルトではsimdb
という名前の設定ファイルを見に行くので、yaml であればsimdb.yaml
という名前が無難です。一応生成時に--config=hoge
と変更できますが、面倒ですよね...
name: table_a
is_private: true # trueだとtypeが小文字で始まります
key_type: string
columns:
- name: name
type: string
- name: expired_at
type: int64
- name: isExpired
type: bool
基本的にtype
によるバリデーションは設けていないので独自に定義した型定義も使えます。
同階層に generate 用の go ファイルを適当に用意してあげます
生成されるファイル名はデフォルトでdb.go
になります
生成されるpackage
はデフォルトだと main
になります
package db
//go:generate go run github.com/maru44/simdb --package=db
再帰実行でもなんでもとにかく実行します
go generate ./...
そうすると...
こんな感じのが生成されると思います
// Code generated by "github.com/maru44/simdb/gen"; DO NOT EDIT.
package db
import (
"fmt"
"sync"
)
type (
tableA struct {
Name string
ExpiredAt int64
IsExpired bool
}
tableAs struct {
data map[string]tableA
sync.RWMutex
}
)
func NewTableAs() tableAs {
return tableAs{
data: map[string]tableA{},
}
}
func (t *tableAs) List() map[string]tableA {
t.RLock()
defer t.RUnlock()
return t.data
}
...
開発経緯
誰も興味ないと思うけど、適当に時系列で書いときます。
- 個人で作ろうと思った web サービスでワンタイムのランダムキーをとにかく頻繁に生成、破棄する仕組みが欲しい
-
redis
のような KVS を使うか、インメモリで処理するか、、、 - インメモリで処理したいなー
- 爆速インメモリ DB 生成機作ったろ
- スタック領域だけでできるだけ処理は済ませたいな
- カラムやキーに使える型を
bool
と~int
だけに限定しよう - あれ、これ排他制御できなくない? (値参照だけじゃ厳しいか?)
- 良くも悪くもスタックとヒープを勝手に割り当てる
go
の特徴を消してない? - スタック領域だけにこだわるならそもそもより低レイヤーな言語で作れば良くね?
- うーん、スタック領域限定爆速インメモリ DB 生成機はいったん置いといてそれっぽく仕上げちゃおう
こんな感じで紆余曲折はありつつも、なんとかそれっぽく作りあげたという感じでした。
まとめ
良かったら使ってみて下さい。
自分も個人的に使う予定です。
考え方的にもコード的にも拙いところや考慮が不足している部分等たくさんあると思うので、指摘してくださるととても助かります 😋
Discussion