成長は回収ではない

サーバは 8 千コマンドで二語を残して死んだ: region OOM。言語のメモリモデルは「終わるプログラム」を軸に設計されていた —— 返却されることのない固定容量 region。短命のツールは全て耐え、サーバは耐えられなかった。即座の修正は region を abort でなく成長させること、そして byte arena の thread 間レースを止めること。正直だったのは、修正がしなかったことを書き残した点だ: サーバは生き延びるようになったが、そのメモリは今も、応じた全コマンドに比例して成長し続けていた。

merememory-modelregionsserverlanguage-design

クラッシュのメッセージは二語、region OOM で、それを取り巻くシステムには無い正直さがあった。 言語のメモリモデルは region ベースだ: 値は bump region に住み、スコープ付きの region ブロックは exit で解放し、それ以外の全ては、プロセスの終わりに回収される一つの program-lifetime region に 着地する。この設計は首尾一貫している —— 終わるプログラムにとっては。サーバがコマンドごとに触る 全ての値 —— parse された引数文字列、組み立て中の reply、それらを繋ぐ cons cell —— はその program-lifetime region に着地し、そこには固定 4 メガバイトの容量と、溢れたら即 abort という 振る舞いがあった。小さな文字列およそ 8 千コマンド分がそれを満たし、プロセスは止まった。 ここに謎めいたものは何もない。メモリモデルの中核の仮定 —— プロセスはすぐ終わる —— が、 終わらないことこそが本分であるただ一つのプログラムと出会っただけだ。

バグは abort であって容量ではない

最初の修理は、クラッシュが融かして一つにしていた二つのことを分離した: region の固定サイズと、 溢れたときの振る舞いだ。region は bump block のチェーンになった —— 現在の block が満ちたら、 幾何級数的に大きい block を繋ぐ。block は決して移動しないので、既に配られた全てのポインタは 有効なままだ。スコープ付きブロックは従来どおり exit でチェーン全体を解放する。成長は安価で 決定的で、当面の問題を解いた: 8 千コマンドで死んでいた同じ負荷試験が 8 万を越えて走り、メモリは 壁に当たる代わりに滑らかに広がった。同じ修理はすぐ隣の、より静かな危険も直した: descriptor 水準の FFI が使う平坦な byte arena は、一つの bump pointer を全 thread で無防備・無検査に共有していた —— spawn された handler たちはそこで競走し、終端を静かに書き越えられた。mutex と、大声で失敗する bounds check が付いた。どちらの変更も賢くはない。どちらも、「短命の呼び出し手が一人」を仮定する runtime と、寄りかかられることを想定する runtime との差分だ。

修正がしないことを書き残す

そのリリースの changelog で大事な一文は、直したことについてではなかった。こう書いた: 成長は回収ではない。サーバはもうクラッシュしないが、コマンドは今も一つごとに数百バイトを、 決して縮まない region へ置いていく —— メモリは総トラフィックに比例して、永遠に成長する。デモに とっては脚注だが、「この言語で実物のサーバを書く」という考えにとっては失格宣告であり、abort より 導火線が長いだけだ。通った負荷試験で回を締め、曲線には後で語らせることもできただろう。プロジェクト の規律 —— 全 dogfood repo に PAIN ファイルを置くのと同じもの —— は、修正と同じ息で残余の問題に 名前を付けることを要求し、名指されたその問題は、将来の本番譚で待ち伏せる驚きではなく、言語の 作業キューの先頭になった。

本当の問題の形

精密に名指すことは、問題の構造も明らかにした。コマンドごとの値は、reply が書かれた瞬間に論理的には 死ぬ —— 何もそれらを参照しない —— が、runtime にそれを知る術は無い。割当ての 99 パーセントと、 生き残らねばならない 1 パーセント(map に実際に store された値、thread 間を飛行中のメッセージ)が、 同じ region に混ざり合って積まれるからだ。言語にはスコープの exit で解放する region ブロックが 既にあった。欠けていたのは、第一に、普通の値 —— 文字列、リスト —— がそこに住む術。第二に、スコープ より長生きすべき値が安全に外へ出る道筋の説明だ。これはアロケータの問題ではない。所有の問題だ。 そしてそれは綺麗に二段階へ割れた —— まず container が中身を所有するようにし、それからスコープが 自分の割当てを回収するようにする —— のが次の二話だ。請求書を届けたサーバは、修理がいずれ突き付け られる物差しとして、無変更のまま待っていた。

← Back to Mere: 言語を作る