概要
- Zigでの Redis互換 キーバリューサーバ「kv」開発記録
- 静的メモリ割り当て による安定性・保守性向上の実践
- 接続管理・コマンド解析・ストレージ 各段階の設計詳細
- 設定による リソース上限 の明確化とトレードオフ
- Zigの メモリアロケータ設計 活用例
Zigによる静的メモリ割り当て型キーバリューサーバ設計
- Zigで Redis互換 の小型キーバリューサーバ「kv」開発
- 目標は 本番運用可能 な最小限コマンド実装
- TigerBeetle や「TigerStyle」ガイドラインに影響を受けた設計思想
- 初期化時に全メモリ確保、実行中の動的割り当て・解放禁止
- パフォーマンスの 予測可能性・安定性、設計の 単純化・保守性 向上
静的メモリ割り当ての課題と利点
- システム設計時に「 どれだけメモリを確保するか」を明確化
- 最大接続数・各接続のバッファサイズ・想定データ量などを事前設定
- 設計時に 全リソース要求量を洗い出す ことで、プログラム理解が深まる
- Zigは 明示的なメモリアロケーション と多様なAllocatorインターフェースで設計容易
接続管理
- Connection 構造体で、各クライアントとの通信情報を保持
- io_uring 対応のため、リクエストライフサイクル中に必要な情報を管理
- 初期化時に以下の 3つのプール を確保
- Connection構造体用
- 受信バッファ用(recv_buffer)
- 送信バッファ用(send_buffer)
- 各プールは std.heap.MemoryPool やカスタムByteArrayPoolで実装
- ランタイムでは プールから切り出し・解放 するだけで動的確保不要
- 最大接続数 ・各接続のバッファサイズはConfig構造体で設定
- プール枯渇時はリクエスト拒否、サーバの 安定性・予測性 向上
- 設定例: 1000接続 程度が現実的、用途に応じて調整可能
コマンド解析
- Redis互換 のため、RESPフォーマットのコマンド解析を実装
- 受信バッファを イテレータ で走査し、CRLF区切りで分割
- parse 関数でバッファを解析し、コマンド内容を抽出
- 解析時の一時的なメモリ管理に FixedBufferAllocator を活用
- 初期化時にバッファ確保、リクエスト毎に reset で再利用
- シングルスレッド処理なら一つのAllocatorを使い回し可能
- 最大コマンド長・最大レスポンス長 を考慮してバッファサイズを設定
- 解析時は「 ゼロコピー」方式で効率化、必要最小限のコピーのみ
- コマンド実行後、レスポンスデータはsend_bufferへコピー
キーバリューストレージ
- 基本構造は ハッシュマップ (std.StringHashMapUnmanaged)
- Zigの「unmanaged」バージョンを利用し、 初期化時に容量確保
- 実行時は putAssumeCapacity で追加、追加時の動的確保不要
- 初期化時に 容量不足時はエラー で即停止、実行時の予期せぬOutOfMemoryを回避
- 最大キー数・最大バリューサイズ 等もConfigで事前設定
設計・運用上のトレードオフ
- 静的割り当て は柔軟性に欠けるが、 堅牢性・保守性 向上
- 設計段階で リソース上限を明確化 することで、システム全体の安定運用
- Zigの Allocator設計 や型安全性が、このような設計に非常に適している
- 本実装は 学習目的 であり、さらなる最適化や設計改善の余地あり
これらの設計思想・実装例は、 Zigやシステムプログラミングの学習 や 堅牢なサーバ設計 に役立つ知見と言える。