世界を動かす技術を、日本語で。

Zigによる静的メモリ割り当て

2025年12月30日原文(nickmonad.blog)

概要

  • 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やシステムプログラミングの学習堅牢なサーバ設計 に役立つ知見と言える。

Hackerたちの意見

すべてのメモリは起動時に静的に割り当てられなければなりません。初期化後に動的にメモリを割り当てたり(解放して再割り当てすることも含めて)、することはできません。これにより、パフォーマンスに大きく影響を与える予測不可能な動作を避けられ、使用後解放(use-after-free)も防げます。さらに言えば、これにより、すべての可能なメモリ使用パターンを設計の一部として事前に考慮した設計と比べて、より効率的でシンプルな設計が実現でき、パフォーマンスも向上し、メンテナンスや理解がしやすくなるという経験があります。 タイガースタイル 30年以上業界で知られている技術が「タイガースタイル」とか、こういうグル的なものに再パッケージされるのは本当に不思議だよね。

そうだね。埋め込みコードを書く仕事をしている私たちは、これを「コードを書く」と呼んでるよ。

知られてはいるけど、埋め込みプログラミング以外ではあまり使われていないね。データベースを書くときに必要ないのにこれを使っているのを見ると、みんな注目しちゃうよね。じゃあ、なんで彼らはこの意識的な選択をしたんだろう?人を小さく見せたくなる気持ちもわかるけど、ここでは必要ないと思う。TigerBeetleは素晴らしいものを作り上げたと思うし、彼らのプログラミングへのアプローチがそれを生み出したんだと思う。

これは、タイガービートルのスタイルに関するドキュメントの一部みたいだね。いわゆる「Googleスタイルガイド」のコード版と似たような感じ。これらは新しいことが書かれていることは少ないけど、特定のプロジェクトや組織がコードスタイルに関してどうしているかを文書化しているんだ。

静的アロケーションは昔からあるけど、意味がある場面でも考慮されることは少ないよね。純粋な静的アロケーションを使ったデータベースエンジンをいくつか設計したことがあるけど、開発者はこのモデルに対して不満を持つことが多い。アロケーションを委譲する方が簡単に見えるからだけど、実際には複雑さを隠してるだけなんだよね。アロケーションを除けば、多くの最適化はソフトウェアが瞬時のリソース制限にどれだけ近いかを正確に把握することが必要だから、パフォーマンスエンジニアリングの一般的な良いプラクティスだよ。ほとんどの人はやってないけど(ほとんどのオープンソース実装を見てみて)、これを推進することは悪くないと思う。

こういう皮肉で見下したようなコメントは誰の役にも立たないし、極端な場合には全体のグループを悪いイメージでステレオタイプ化することもあるよね。もっと建設的な現実は、ゲームや組み込みシステムのような特定の業界で一般的な技術が、なぜもっと広く採用されるのが難しいのかを議論することだと思う。そして、多くの文脈で良いアイデアが今広がっていることを祝うこと!また、他の業界が見逃しているかもしれないアイデアを共有すること(そして、なぜそれが存在しないのかを批判的に問いかけること)も大事だよね。アイデアは広がるためにマーケティングが必要だから、ポジティブな意味でのマーケティングってそういうことだし(ネガティブな場合は色々とドロドロしてるけど!)。もし企業が使っているコーディングスタンダードがこのアイデアが生き延びて成長するためのマーケティングなら、全然アリだよね、「タイガースタイル」で行こう!人間ってそんなもんだよ。

「業界で30年以上知られている技術の一つが、次世代との知識共有だ。まず、どこでそれを見つけるかなんてわからない。どの本?どの先生?本がたくさんありすぎて、全部読む必要があるの?もし同僚がそれを知らなかったら、どうやって共有するの?それに、昔からの言い伝えがあるけど、得意なことは決して無料でやるなって。これは正確にはトレードシークレットではないけど、どれだけの人が自分の知っている高度な技術やトリックについてブログを書いている?私はLuaのクロージャから本物のC関数ポインタを作る方法についてブログを書いたけど、それは自分の製品を宣伝するためのもので、実際にはトレードシークレットとして隠しておくべきだったかもしれない(そのブログ記事からは0件の売上しかなかったし)。誰が個人的な利益もなく「虎スタイル」の知識を新しい世代と共有したがるの?秘密裏に使ったり、本に書いたり、広告のためにブログに書いたりするインセンティブがあるんじゃないの?」

もっと文脈を加えると、TigerStyleは単なる静的割り当て以上のもので、実際に以前の作業に明示的に言及しているんだ。> 「NASAのPower of Ten — Safety Critical Codeを開発するためのルールは、あなたのコーディングの仕方を永遠に変えるだろう。」詳しく言うと:* https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TI... * https://spinroot.com/gerard/pdf/P10.pdf

すべてのメモリは起動時に静的に割り当てられなければならない。初期化後に動的にメモリを割り当てたり、解放したり再割り当てしたりすることはできない... 業界で30年以上知られている技術が再パッケージされているのは驚きだね。これがGPUシェーダープログラミングの仕組みでもある。ヒープ割り当てや一般的なポインタに相当するものはなく、できるだけローカルメモリや事前に割り当てられた共有バッファを使って作業することが求められる。だから、この技術は今でもかなり関連性があると思うよ。かなり長い歴史があるけどね。

今のシステムがほとんどすべて動的に割り当てている中で、特に関連性が高いと思う。中には各オブジェクトがそれぞれ独自のランタイム割り当てになっているものもあって、Javaなんかがその代表だね。一見すると素晴らしいけど、無限のスケールと完璧に一般的な感じがする。システムは何でも扱える。でも、すべてを扱う必要があるのかな?そして、すべての可能なシナリオを扱うことの複雑さのコストはどうなるの?

Tigerbeetleの開発者による関連の最近の投稿: https://matklad.github.io/2025/12/23/static-allocation-compi...

今、Redisの代わりにNATSでまさにこのパターンをやってるよ。似たような戦略を取っている人がいるのを見るのは面白いね。Zigのエコシステムが標準ライブラリのパターンに従ってアロケータインターフェースを渡すことで、イディオマティックなコードを書くのがめっちゃ簡単になるし、呼び出し元でアロケーション戦略を決められるのがいいよね。もちろん、他の言語でも何十年も前からやられてきたけど、このパターンに従いながらlibcのような既存のエコシステムを活用するのは簡単じゃないし、呼び出される側は通常、使われているアロケーション戦略について何かを知っておく必要がある(その戦略に従わない標準関数を避けるためだけでも)。

Hacker Newsで議論の続きを見る