🔗 https://github.com/QuarkContainer/Quark/blob/main/doc/Hibernate.pdf
英語タイトル:Hibernate Container: A Deflated Container Mode for Fast Startup and High-density Deployment in Serverless Computing
0 要旨
サーバーレスコンピューティングは人気のクラウドパラダイムで、オンデマンドのユーザーリクエストを扱うために低い応答遅延が要る。応答遅延を減らす代表的な技術は二つある。完全に初期化したコンテナを生かし続ける(ホットコンテナ1)か、新しいコンテナの起動(コールドスタート)遅延を減らすか。本稿は第三のコンテナ起動モード、休眠コンテナ2を提案する。コールドスタートより起動が速く、ホットコンテナよりメモリが少ない。休眠コンテナは本質的に「痩せた」ホットコンテナだ。アプリメモリはディスクにスワップされ、解放メモリは回収され、ファイルベースの mmap メモリは捨てられる。痩せたメモリはユーザーリクエストに応えるときに膨らむ。アプリはすでに完全初期化済みなので応答遅延はコールドスタートより低く、アプリメモリが痩せているので消費はホットコンテナより少ない。さらに休眠コンテナがリクエスト処理のために「起こされる」と、起こされたコンテナの遅延はホットコンテナに近く、しかも膨らます必要のない痩せメモリがあるのでメモリはより少ない。休眠技術はオープンソースの Quark セキュアコンテナランタイムの一部として実装した。テストでは休眠コンテナのメモリはホットコンテナのおよそ 7% から 25%。これら全部で配備密度が上がり、遅延が下がり、システム全体の性能がはっきり良くなる。
1ホットコンテナとは、ホットスタート過程の一部として作られる完全初期化済みコンテナ
2休眠コンテナとは休眠コンテナモードを指す
1 はじめに
サーバーレスコンピューティング、Function as a Service(FaaS)とサーバーレスコンテナを含めて、ますます人気のクラウドパラダイムになっている。サーバーレス環境は通常、共有環境でマルチテナントワークロードを走らせ、オンデマンドのユーザーリクエストを扱う。主要クラウドが支える。AWS Lambda [1]/Fargate [2]、Google Function [3]/Cloud Run [4]、Azure Function[5]/Container Instance [6]。
共有環境でマルチテナントのユーザーアプリを載せるため、主要クラウドは runC/LXC のようなプロセスベースのランタイムではなく、VM ベースのセキュアコンテナランタイムを使う。VM ベースのセキュアランタイムは従来の VM と同じような隔離を出す。たとえば AWS は Firecracker[7]、GCP は gVisor[8]、アリババクラウドとファーウェイクラウドは Kata コンテナ[9] をサーバーレスに使う。ただしこれらの VM ベースのセキュアランタイムはメモリをより食い、プロセスベースより応答遅延が高い。
ユーザーリクエストの応答遅延はサーバーレスで極めて重要だ。遅延は三部分:コンテナランタイム起動、アプリ初期化、リクエスト処理。ランタイム起動はだいたい 100 ミリ秒、アプリ初期化は 10 ミリ秒から 10 秒、リクエスト処理は短く数ミリ秒から 10 秒が多い。処理時間に比べ、ランタイム起動とアプリ初期化の寄与はかなり大きい。この二つを減らすのがサーバーレス設計の鍵のひとつ。遅延を減らす最適化は普通二つ:
- ホットスタート最適化:よくある手法は実行ランタイムを短時間生かし続ける、つまりホットコンテナにして、同じリクエストの将来の呼び出しで再利用する。ホットスタートはコールドスタートのコストを減らすが、生かし続けると計算資源、とくにメモリを大量に食う。それがシステムの資源需要を押し上げる。ホットスタート効率を上げる努力は続いていて、ランタイムオーバーヘッド削減[10][8]やホットコンテナの keep-alive スケジューリング[11]など。
- コールドスタート遅延最適化:もちろん資源制限で全サーバーレスコンテナを生かし続けることはできない。もう一つの研究はコールドスタート遅延を減らすこと。ランタイム起動時間[10][8]とアプリ初期化遅延[12][13][14]。
本稿は伝統的なメモリスワップに基づく第三の起動機構を提案する。サーバーレスの起動時間を和らげるのに向いている。関連する考慮は:
- 高速スワップストレージ:高性能な二次記憶(SSD、NVM)がパブリッククラウドで商用に使えるようになり[15]、スワップ性能は大きく上がった。
- 軽量サーバーレスワークロード:高速起動は軽量でメモリ占有の小さいワークロードを求める。AWS [16] では関数の 47% がデフォルト最小の 128 MB。全体として AWS Lambda の 14% だけが 512 MB 超。Azure[17] ではアプリの 90% が 400MB を超えて消費したことがなく、サーバーレスアプリワークロードの 50% は最大 170MB。占有が小さいのでスワップコストは相対的に低い。
要するに、低遅延起動のためのスワップと、keep-alive コンテナの低メモリを組み合わせる余地は大きい。スワップを鍵にして、本稿は休眠コンテナを提案し実装する。痩せた keep-alive のホットコンテナだ。低遅延起動と低メモリのために次の最適化を使う:
- メモリ: 休眠コンテナのメモリはホットコンテナよりずっと少ない。アプリメモリをディスクにスワップし、空きメモリをホスト OS カーネルに返し、最後にファイルバックの mmap メモリを掃除してホスト OS に返すから。
- CPU: ユーザーアプリを完全に一時停止するので、システム CPU サイクルをまったく使わない。
休眠コンテナのユーザーリクエスト遅延はコールドスタートよりずっと低い。アプリが完全初期化済みで、ランタイムが次の keep-alive 資源を使っているからだ:
- ホスト OS オブジェクト: ランタイムの OS プロセス、cgroup、コンテナネットワーク、コンテナファイルシステム、プロセスなどを生かし続ける。OS オブジェクトはメモリをほとんど食わないが、生かし続けると再初期化コストを大きく省ける。
- ブロックされたランタイムスレッド:ホストスレッドはユーザーリクエスト待ちでブロックされる。CPU は食わないが、ホットコンテナと同様すぐ応答できる。
強調したいのは、起こされたコンテナの後続リクエスト遅延はホットコンテナとほぼ同じで、メモリはより少ないこと。リクエスト処理にすべての膨張メモリが要るわけではないからだ。
全体として配備密度が上がり、システム性能も良くなる。
主な貢献:
- 休眠コンテナモードをオープンソース Quark コンテナランタイムの一部として提案し実装した [18]。ホットコンテナよりメモリが少なく、コールドスタートより起動が速い。休眠から派生した起こされたコンテナもホットよりメモリが少なく、リクエスト遅延はほぼ同じ。
- 主なメモリスワップイン遅延は SSD のランダム読みだと分かった。REAP[16](記録とプリフェッチ)に触発され、休眠コンテナ膨張の一部としてバッチメモリアプリフェッチのスワップインを実装した。ページフォルトベースと REAP スワップインをベンチマークで比較した。
- 空きページをホスト OS カーネルに効率よく返す、回収志向の新しいメモリ管理を実装した。複雑なバルーニングは不要になる。
Ballooning は、ゲストが占めるメモリの中に風船(Balloon)を置くイメージだ。風船の中のメモリはホストが使える(ゲストは触れない)。ホストの空きが少ないとき、ゲストに割り当て済みメモリの一部を回収するよう頼む。ゲストは空きを解放し、足りなければ使用中も回収し、ゲストのスワップに追い出すこともある。風船が膨らみ、ホストはそのメモリを他プロセス(や他ゲスト)に使える。逆にゲストが足りないときは風船を縮めて中のメモリを返し、ゲストがより多く使える。
2 背景と動機
休眠コンテナは Quark ランタイムで実装した痩せたホットコンテナだ。痩せの過程は、ユーザーアプリの空きメモリ回収と、ユーザーメモリの二次記憶へのスワップ。本節は Quark セキュアランタイム設計、既存のゲスト OS 空きメモリ回収とスワップ、最後に動機と、今のサーバーレスを最適化する機会を述べる。
2.1 セキュアコンテナと Quark ランタイム
前述の通り、休眠モードはオープンソース Quark [18] の一部として実装した。ここでは最先端のセキュアランタイムを簡単に述べ、Quark アーキテクチャに入る。
サーバーレスは共有環境でマルチテナントワークロードを載せる。RunC/LXC のような伝統的ランタイムは向かない。マルチテナント級の隔離が出せないからだ。主要クラウドは VM 級のセキュアランタイムを使う。Kata[9]/Firecracker[19] と gVisor[8]。

Kata と Firecracker は Linux カーネルベースの VM で隔離する。汎用 Linux カーネルを使うので、サーバーレスでは起動遅延と資源オーバーヘッドがかなり高い。
Quark[18] と gVisor[8] はサーバーレス向けに設計されたもう二つの代表的セキュアランタイム。ユーザ空間 OS カーネルと軽量 VMM からなる。Linux 互換のシステムコールと CRI/OCI を出し、既存 Linux イメージを無変更で走らせる。サーバーレス向けにかなり最適化されているので、起動遅延と資源オーバーヘッドは Kata/Firecracker より低い。ただし Kata/Firecracker が明示的に Linux カーネルベースなのに対し、Quark と gVisor の Linux 互換はそれほど良くない。
図2は Quark のアーキテクチャ。伝統的 Linux VM に似る。Linux ホストカーネルの上で KVM を使う。Quark ランタイムプロセスは標準 Linux コンテナ内で、cgroup とネットワーク/ファイルシステム名前空間で隔離される。Quark は新しいユーザ空間 OS カーネル(QKernel)と VMM(QVisor)を含み、サーバーレス向けに大きく最適化されている。Linux システムコールをエミュレートする仮想化システムコールを出し、メモリ管理、プロセス管理、I/O など大半の Linux カーネル機能を実装する。
Quark はサーバーレス向けで、休眠コンテナモードなどのサーバーレス固有機能を組み込んでいる。
2.2 ゲスト OS が解放したメモリの回収
休眠モードの価値の一つは、ユーザーアプリが解放したメモリをホスト OS に返すこと。Linux のような汎用ゲスト OS では空き回収は簡単ではない。ゲストアプリがゲストカーネルにメモリを返すとき、理想的にはゲストカーネルがそれをホスト Linux カーネルに返し、他のホストプロセスに再割当できるべきだ。残念ながら Linux ゲストは解放メモリを自分のプールに残し、ホストに返さない。Linux はベアメタル向けで、ホストへの回収は要らないからだ。要するに伝統的仮想化では、ゲストが解放したメモリはホストに解放・回収されない。
Linux ゲストの空き回収には二つの方法がある:
- バルーニング[20]:ゲスト内の特別なバルーンドライバが VMM と協力し、VM メモリサイズを動的に変える。ホストが一部ゲストから未使用メモリを取り、他 VM と共有する技術。
- メモリプラグイン[21]:カーネルの物理メモリホットアド/ホットリムーブに依存。ホットリムーブは領域をユーザーから使えなくし、使用中ページを別領域へ移すページマイグレーションが要る。性能コストがある。VM メモリ収集 [22] で使われている。
Kata/Firecracker のような VM ベースランタイムも Linux ゲストなので同じ問題を抱える。知る限りバルーニングもメモリプラグインも使っていない。サーバーレスでは複雑すぎて採用しにくいからだ。
休眠コンテナの一部として、Quark に専用メモリ管理を実装し、サーバーレスでの回収効率を上げた。
2.3 ゲストアプリメモリのスワップ
休眠モードのもう一つの価値は、ユーザーアプリメモリのスワップアウト。
スワップは非アクティブページを一時的に二次記憶へ出し、ページテーブルの項目を不在にする。そのページが要ると VM 管理がページフォルトを起こしてスワップインする。
よくある仮想化ではホストのスワップは効率が悪い。非協力的だからだ。VSWAPPER[23] は仮想化での非協力スワップの非効率を調べた。静かなスワップ書き、古いスワップ読み、誤ったスワップ読みなど。VSWAPPER はゲスト非依存のメモリスワッパを実装してそれらを解く。一般的仮想化ではかなり良いが、サーバーレス向けではない。
サーバーレスでは、アイドルコンテナのメモリ全体をスワップアウトしたいので、より良いスワップの機会が多い:
- アプリページのバッチスワップアウト:普通のスワップではカーネルが非アクティブページを選ぶ。サーバーレスではアイドルコンテナのユーザーアプリメモリ集合全部を出す。メモリ管理コストが下がる。
- 一時停止アプリの競合なしスワップアウト:ページを出すときにアイドルのユーザープロセスを止められる。普通のスワップの複雑な競合処理を避けられる。
- ページスワップインのバッチ逐次ディスク読み:普通のスワップインはページフォルト駆動で、二次記憶はランダム読み。HDD でも SSD でもバッチ逐次読みはランダムより常に速い。REAP[14] は、同じ関数の呼び出し間で同じ安定したワーキングセットページに触れることを示した。ページ集合が分かればバッチ逐次読みでプリフェッチできる。ページフォルトベースに比べ、バッチスワップインはランダムページロードだけでなく、フォルト処理とゲスト/ホスト切替のコストも省ける。
動機は、上の最適化機会をすべて取り、サーバーレス向けにもっと効率の良いスワップを作ること。
3 設計と実装
以下は Quark セキュアランタイムの一部としての休眠コンテナのアーキテクチャと設計。
3.1 休眠コンテナの状態機械
休眠コンテナが到来リクエストにどう応えるか。
図3は到来リクエストをさばくコンテナ状態遷移。

到来リクエストでプラットフォームは1️⃣コールドスタートする。新しいホットコンテナができ、リクエストはそこに転送される。ホットコンテナがリクエストを受けると2️⃣ running になって処理し、終わると3️⃣ hot に戻る。
遅延を減らすため、プラットフォームはホットコンテナを短時間生かし続けることがある。その間に続くリクエストがあればホットコンテナが低遅延で処理できる。ただしアイドルでもアプリ固有の割当メモリは食う。メモリ圧迫時、プラットフォームはホットコンテナを追い出し、他関数コンテナにメモリを回すことがある。追い出したあと次のリクエストはより高いコールドスタート遅延を払う。要するにホットコンテナが多いほどリクエスト遅延は良い。
伝統的な状態に加え、次の三つの新しい状態を提案する:
休眠:休眠コンテナは膨張(正しくは収縮)したホットコンテナで、占有はホットより小さい。完全に追い出す代わりにホットを「収縮」して休眠にし、メモリを空けられる。
プラットフォームはホットに SIGSTOP を送り、4️⃣ ホットから休眠へ変えて収縮を始める。
休眠 running:休眠コンテナはユーザーリクエストを受けると、running のように7️⃣ 休眠 running へ移って処理することがある。
起こされた状態:休眠 running は処理後8️⃣ 起こされた状態に戻る。起こされたコンテナは次のリクエストで6️⃣ 再び休眠 running に入る。SIGSTOP を受けると9️⃣ 休眠に戻ることもある。起こされたコンテナの遅延はホットとほぼ同じで、メモリはより少ない。プラットフォームがリクエスト到来を予測するときは、SIGCONT を送って5️⃣ 休眠を起こされた状態に「起こし」、遅延を減らすこともある。
3.2 脱気過程の概要(Deflation Process Overview)
休眠コンテナは圧縮されたホットコンテナだ。ホットから次の四ステップで作る:
- ホットのユーザーアプリプロセスを一時停止し、ランタイムのホスト OS スレッドを「起こし」トリガ待ちでブロックする。
- 解放されたアプリページを回収し、ホスト Linux カーネルに返す。
- コミット済みアプリページをローカルディスクにスワップアウトする。
- madvise() に MADV DONTNEED を渡してファイルバックの mmap を掃除し、Linux カーネルに返す。
ステップ#1のあと CPU は使わない。#2、#3、#4でアプリ割当メモリはホストカーネルに返るので、ホットよりメモリがずっと少ない。#2、#3、#4の詳細は 3.3、3.4、3.5。
休眠はメモリ膨張とアプリ再開でホットに戻れる。起こすトリガは二つ:
- ユーザーリクエスト:プラットフォームはリクエストをまず起こさずに休眠コンテナへ直接転送できる。全体遅延を減らすため。休眠コンテナはランタイムスレッドを Posix ソケットの sys accept や sys read などでブロックして待つ。クライアント接続やソケットデータが来るとホストカーネルがスレッドを起こし、残りの起こし処理(スワップイン、アプリ再開)が始まる。
- サーバーレス制御プレーン:リクエストが来そうなとき明示的に起こせる。リクエスト前に膨張が一部終わるので、リクエスト駆動より遅延が低い。
3.3 回収志向メモリ管理
休眠コンテナはゲストアプリが解放したページを回収し、ホスト Linux カーネルに返す。VM バルーニングに似る。
QKernel は KVM VM 内で動き、ゲスト物理メモリはホスト Linux の仮想メモリ。ゲスト物理ページ(ホスト仮想ページ)は触られるまでホストカーネルにコミットされない。Quark は sys madvise() に MADV DONTNEED[24] を付けてコミット済みページをホストに返せる。成功した madvise() のあと、その範囲への後続アクセスは成功するが、匿名プライベートマッピングではゼロ埋めオンデマンドページになる。理想的にはゲストカーネルの空き領域を見つけて madvise() で回収する。
残念ながら元の Quark は解放メモリを簡単に回収できなかった。今は二分バディ割り当て[25]を使う。回収に向かない。空きブロックを線形リストで持ち、「next」ポインタが空きブロック自身に入っている。ベアメタル向けカーネルではうまくいく。ゲストカーネルで空きページブロックを madvise() すると、後続アクセスがゼロ埋めされ「next」が消え、空きリストが壊れる。だから既存バディはページ回収に向かない。代わりにビットマップページ割り当てを実装した。設計は次。
元の Quark のメモリ割り当ては二つの領域:
- ゲストユーザーアプリのアドレス空間割り当て:sys brk、sys mmap などでゲストカーネルからアドレス空間だけ取る。ページはページフォルトハンドラで触るまでコミットされない。
- Quark ランタイム大域ヒープ:Quark は Rust。カスタムヒープ割り当てをサポートし、バディベースを使う。QKernel のカーネルスタックなどのデータ構造はヒープから。元の Quark はページフォルトハンドラでユーザーアプリページも大域ヒープから取っていて、回収に不向き。そこで休眠向けに第三の割り当て、ビットマップページ割り当てを足した。

ビットマップページ割り当てはゲストユーザーアプリのページ管理用。ページフォルトハンドラでの固定 4KB ページ割り当てにだけ使う。
図4の通り、4MB ブロックで 4KB ページを割り当てる。開始アドレスは 4MB アライン、最初の 4KB を制御ページにする。制御ページは3フィールド:
- 「next」ポインタ:空きページのある 4MB ブロックは線形リストでつながる。next は制御ページに置く。
- 空きページビットマップ:4MB ブロックは 4KB ページ 1024 個。最初が制御、残り 1023 を割り当て可能。L2 ビットマップ(16×64bit、計1024bit)でページが割り当て済みかを示す。空き探索を速くするため L1 の 64bit 整数もあり、L2 の各 64bit がゼロかを示す。割り当てごとに 64bit を二つ触る:L1 と L2 の一つ。空きを探すときはまず L1 の最初の非ゼロビット。
それが第4ビットなら、第4の L2 ワードに空きがある。次にその L2 ワードの最初の非ゼロビットを探す。空き探索は O(2)。
- ページ参照カウント配列:クローン時など、カーネルページは複数ページテーブルから参照されうる。制御ページに 16bit アトミック整数配列[26]で参照カウントを持ち、メモリ効率と性能を上げる。
ページ割り当てと参照カウント増減:
- 割り当て:空きリスト先頭の 4MB ブロックから1ページ取り、制御ページのビットマップを更新。空きがなければリストから外す。リストが空なら大域ヒープ(大域バディ)から別の 4MB を取る。競合を避けるため大域ロック。
- 参照カウント増減:ゲストの clone/終了や COW で増減。制御ページに置くと性能が良い。4MB アラインなので、アドレスの下位22bitを落とせば制御ページ(4MB ブロック先頭)に届く。ルックアップテーブル不要。制御ページが見つかれば Rust のアトミック fetch_add / fetch_sub [26]、ロックフリー。ゼロになったらビットマップを更新して解放。ブロックの空き数がゼロから増えたらリストに戻す。空きが最大 1023 なら 4MB ブロックを大域ヒープに返せる。
Quark が休眠処理を始めるとき、解放ページをホスト Linux カーネルに返す必要がある。ビットマップ割り当てでは空きデータページは制御ページのビットマップで示され、バディの空きリストのようにページ内に next は無い。休眠時は madvise() で空きページをホストに返す。バルーニングよりずっと単純。
起こされて回収ページがゲストアプリに再割当されるとき、ホストカーネルがホストのページフォルトでコミットする。ゲストの Quark には透明で、明示的な再割当は不要。起こし遅延と複雑さが減る。
3.4 休眠コンテナのメモリスワップ

休眠コンテナはゲストアプリメモリを二次記憶にスワップアウトし、起こされたときにスワップインしうる。
スワップインは二種類:
- ページフォルトベース:普通の OS スワップインと同じ。スワップアウト済みページに触るとフォルトし、ハンドラがページを載せる。
- バッチ REAP スワップイン:REAP 過程で記録したページをすべてプリフェッチできる。
図5の通り、サンドボックスごとにページフォルト用スワップファイルと、バッチ REAP 用 REAP ファイルがある。スワップファイルはサンドボックス専用で共有せず、潜在的なセキュリティ問題を減らす。終了時に削除。
ページフォルトスワップインと REAP スワップアウトは次のように違う。
3.4.1 ページフォルトベースのスワップイン/アウト
この節はページフォルトスワップインと対応するスワップアウト。
プラットフォームはアイドルのホットに SIGSTOP を送って休眠を起こせる。そのあと図5のスワップマネージャが:
- ゲストアプリを止める:汎用 SIGSTOP ハンドラとして全ゲストアプリを止める。ユーザースレッドはブロックされページに触れないので、複雑な競合を扱わずに済む;
- ゲストページテーブルを走査して変更:全アプリのページテーブルを歩き、匿名ページを見つけて:
- 各匿名 PTE を不在にし、後続アクセスでフォルトさせる;
- PTE のカスタムビット#9 を立て、フォルトがスワップアウト由来だと示す;
- ページのゲスト物理アドレスをハッシュテーブルに入れ、複数テーブルが同じ GPA を指すときの重複排除。
- スワップファイルへ書く:Quark サンドボックスごとにスワップファイル。ハッシュを列挙してページを書き、ファイルオフセットをハッシュに戻す。
- ホスト Linux に返す:スワップアウトしたページを madvise()。
起こされると止めていたアプリを再開する。スワップアウトページに触るとフォルトしてスワップイン。
フォルト処理:
- スワップアウト由来か確認:Quark のページハンドラがカスタムビット#9 を見る。立っていればスワップアウトページで、ステップ#2からスワップイン。
- スワップファイルから載せる:フォルトした vCPU がゲストからホストへ抜け、スワップファイルから読む。
- PTE 更新:ビット#9 を下ろし、present にして再フォルトしない。
ページフォルトスワップインは高い。コストは:
- ページフォルト処理:ゲスト vCPU がユーザー空間からカーネル空間へ。汎用レジスタをメインメモリに退避。
- ゲスト/ホスト切替:高い。GPR だけでなく浮動小数点コンテキストも。テスト環境では約 15 マイクロ秒。
- SSD のランダム 4K 読み:休眠は SSD で測った。SSD のランダム 4KB は HDD よりずっと速いが、逐次バッチ読みより遅い。こちらの環境では 4K ランダム約 100MB/s、逐次バッチは 1GB/s 超。
観察では、ページフォルトスワップインはスワップアウトしたページの 30% から 90% しかリクエスト処理に載せない。Node.js Hello World 休眠では約 10MB 出して、処理は約 4MB だけ入れた。スワップアウトには初期化とリクエスト処理のページが混ざる。膨張時は初期化済みなので関連ページは触れられない。この観察と REAP[14] から、バッチ REAP プリフェッチスワップインを実装した。
3.4.2 REAP スワップアウトとバッチスワップイン(記録とプリフェッチ)
REAP の要点は、リクエスト処理中にゲスト物理ワーキングセットを記録し、次の起こしでバッチプリフェッチすること。ページフォルトスワップインの最適化。
ページフォルトスワップアウトに比べ、REAP は最初の休眠と起こしのあとに記録を足す。最初に休眠に入ったあと:
- サンプルリクエスト:プラットフォームがサンプルを送り休眠 running へ;
- 物理ワーキングセット記録:休眠 running ではリクエストプロセスのゲスト物理ワーキングセットがページフォルトスワップインでスワップファイルから載り、未タッチはファイルに残る;3. REAP 休眠:サンプル後、コンテナは起こされた状態に戻る。プラットフォームが SIGSTOP で起こされたコンテナを休眠させる。REAP スワップアウト:
- 全ユーザープロセスを止める。
- 全ページテーブルを歩き、アクティブな匿名ページを取る。
- ゲスト物理ページアドレスをハッシュ IO ベクトルに記録し、pwritev() のバッチ書きで図5の REAP スワップファイルへ。
- madvise() でゲスト物理ページを解放。
REAP スワップアウトはページフォルトスワップアウトと違う:
- PTE を変えないのでフォルトしない;
- 専用 REAP スワップファイルへ書くので、ディスクのバッチ逐次読みでスワップインを加速できる。
REAP スワップインはページフォルトより単純。
手順:
- REAP スワップアウトで作ったハッシュ IO ベクトルに従い、preadv() のバッチ逐次読みで REAP スワップファイルから全ページをプリフェッチ。
- ゲストアプリを再開。REAP がページフォルトより速い理由:
- ページフォルトなし:PTE を変えないのでスワップインでフォルトせず、フォルト処理とゲスト/ホスト切替がない。
- バッチファイル読み:全ページをバッチ読みでプリフェッチ。ランダムよりスループットが高い。
3.5 ファイルバックメモリの共有とセキュリティ
休眠コンテナはファイルバック mmap も madvise() で掃除し、ホスト Linux に返す。Quark は COW でコンテナ間にファイルバック mmap を共有しうる。共有されているなら、他コンテナが使うかもしれないので圧縮で掃除する必要はない。共有は起動遅延とシステム全体の占有を減らせる。サーバーレスには魅力的。
残念ながらマルチテナントでは、テナント間でファイルバック mmap を共有するとキャッシュサイドチャネル[27]などのリスクがある。本番のマルチテナントでは共有は推奨されない[7]。
セキュアコンテナ内のアプリでは、共有されうるファイルバックは主に二種類:
- ユーザー言語ランタイムバイナリ:Node.js や Python などの上にアプリがある。ユーザー空間にマップされアプリが直接触れる。テナント間共有は危険。
- セキュアランタイムバイナリ:ランタイムの実行ファイルとライブラリ。Kata の Linux ゲストカーネルなど。ユーザー空間にマップされずアプリは直接触れないので、言語ランタイムよりリスクは低い。RunD [10] は本番で Linux ゲストカーネルを共有し、コールドスタートと占有を減らしている。
休眠は Quark ランタイムバイナリの共有を有効にし、言語ランタイムバイナリの共有は無効にする。
ランタイムバイナリ共有はリクエスト遅延を大きく減らす。Node.js ランタイムバイナリ共有を測ると、有効時は休眠 Node.js hello-world の遅延が 25ms から 11ms に落ちた。言語ランタイム共有のリスク緩和[28] [29]もあり、本番でも使われる。Cloudflare Worker [29] は V8 isolate ベースで、マルチテナント隔離のリスクを一部飲んでいる。問題が解けたら、休眠はこれらの緩和でより多くのファイルバック共有を有効にし、性能を上げられるかもしれない。
3.6 休眠コンテナの実装
休眠のコードは Rust で、Quark セキュアランタイムの一部。Quark は 20 万行超の Rust の仮想ユーザ空間 OS。休眠は仮想メモリ、I/O、VMM など鍵パスを変える。スワップマネージャとビットマップ割り当てはゼロから。スワップマネージャ 780 行、ビットマップ 484 行。回収マネージャは Quark のファイルバックメモリとページテーブルを改変、およそ 500 行。シグナル処理と I/O に休眠トリガ用でおよそ 300 行。
4 評価
実験で休眠の性能を示す。マシン:1×12 コア Intel(R) Core(TM) i7-8700K @ 3.70GHz、64GB RAM、PM981 NVMe Samsung 512GB SSD、Ubuntu 20.04.4 Linux、カーネル 5.15.0-46-generic。
二組のマイクロベンチで、休眠がリクエスト遅延とメモリ占有に与える影響を見る。
- Python ベンチ:Function Bench[30] から、プロセスタイプ、メモリ、遅延の違うものを選んだ。
- 浮動小数点:メモリも処理遅延も小さい;
- 動画処理:OpenCV のグレースケールを動画に。メモリ 200 MB 超、処理遅延 1000 ミリ秒超。
- 画像処理:Python Pillow で変換。同じプログラムでデータサイズの影響を見るため画像サイズを2つ。
- 言語ランタイム hello-world:Python、Node.js、Golang、Java でランタイムの違いを見る。
4.1 ユーザーリクエスト応答遅延
休眠のリクエスト遅延はコールドスタートより低く、起こされたコンテナはホットに近いことを示す。ページフォルトと REAP スワップインの遅延も比較。
休眠はすでに起動して keep-alive なので、アプリ起動遅延ではなくリクエスト/レスポンスの端到端遅延を測る。コールドスタート以外は HTTP サービスとしてマイクロベンチを走らせ、HTTP リクエストで処理を起こし、HTTP 応答遅延を測る。

コールドスタート、ホット、ページフォルト/REAP スワップイン付き休眠、起こされた状態の遅延を取った:
- コールドスタート:HTTP トリガなしの、コンテナ起動とリクエスト処理の過程遅延;
- ホット:完全初期化後のリクエスト遅延;
- 休眠:休眠後の初回リクエスト遅延。ページフォルトと REAP も取る。
- 起こされた:起こされたコンテナのリクエスト遅延。
図6。結論:
- Hibernate の処理遅延はコールドスタートより低い:たとえば Hibernate REAP のリクエスト遅延はコールドスタート過程の 3%(Python/Golang Hello-world)から 67%(画像処理、2.6 MB)。Hibernate REAP は 296 ミリ秒(Golang Hello-world)から 2407 ミリ秒(動画処理)のコールドスタート過程遅延を省く。
- 起こされたコンテナの処理遅延はホットとほぼ同じ。
- ページフォルトスワップ付き Hibernate は REAP より遅い:大半のベンチで REAP が勝つ。例外は 2.6 MB の画像処理だけだが差は無視できる。
休眠の応答遅延はコールドスタートより低く、起こされた処理遅延はホットに近い。Python、Node.js、Golang、Java のランタイムにも効く。
4.2 コンテナメモリ消費
休眠とその派生の起こされたコンテナはホットよりメモリが少ないことを示す。Linux の pmap で PSS を取る:
- ホット:少数のユーザーリクエストを処理したあと。
- 休眠:ホットから休眠へ。
- 起こされた:ユーザーリクエストで起こされた休眠。
§3.4 の通り休眠は Quark ランタイムバイナリを共有するので、インスタンスが多いほど PSS は小さい。10 個の実行中ベンチインスタンスで PSS を取った。

図7から:
- 休眠のメモリはホットよりずっと少ない:たとえばホットの約 7%(動画)から 25%(Golang Hello-world)。節約は 12 MB(計 16 MB、Golang Hello-world)から 252 MB(計 281 MB、画像 2.6 MB)。
- 起こされたメモリもホットより少ない:ホットの 28%(Node.js Hello-world)から 90%(画像 2.6MB)。節約は 7MB(計 16 MB の Golang Hello-world)から 151 MB(計 226 MB の動画)。
休眠と起こされたはホットよりメモリが少ない。Python、Node.js、Golang、Java で同じ。
遅延とメモリから:
- 休眠と起こされたの共同配備はホットより高い密度にできる。
- 起こされたはメモリが少なく、リクエスト遅延はホットに近い。可能ならホットを休眠経由で起こされた keep-alive にするのは得。
5 関連研究
関連研究はいくつかあり、とくにコールドスタート最適化。コールドスタートは普通二つ:セキュアランタイム起動とユーザーアプリ起動。
次の小節で両方の進行中の最適化を述べる。
5.1 VM ベースのセキュアランタイム最適化
VM ベースのセキュア起動は Linux コンテナ環境(cgroup、コンテナネット、コンテナ FS)のセットアップと VM OS カーネル起動。RunD [10] は cgroup の事前作成、rootfs マップの最適化で環境セットアップを速くする。RunD は Kata テンプレートでマイクロ VM ごとのメモリと起動遅延を減らす。
Firecracker[19] は QEMU や CloudHypervisor の代わりに軽量 VMM を入れる。virtio ネットと単一ブロックデバイスなど少数デバイスだけ。セキュアコンテナのメモリとサンドボックス起動遅延に効く。
Quark[18] と gVisor[8] は新しいユーザ空間 OS カーネルとサーバーレス向け VMM で、占有と起動遅延を減らす。
5.2 アプリ起動最適化
それらのコールドスタート最適化の要点は、ホットにより「近い」状態から起動すること。[13][31] は gVisor や JVM のチェックポイント/リストア。[32] は回収待ちのホットを別イメージのホストに再利用する。
C/R の上に、Catalyzer [13] は初期化なし起動を実装。C/R 上の最適化はさらにあり、REAP はバッチプリフェッチで VMM イメージロードを速くする。Sock [12] は Zygote を拡張し、事前インポート済みパッケージ付きのヘルパーをフォークして新コンテナを起動し、インポートコストを省く。Catalyzer はサンドボックスフォーク(sfork)を導入し、既存ホットから完全なアプリ状態でフォークし、親子でメモリを共有する。
結論
低遅延のコンテナ起動はサーバーレスのユーザー体験に不可欠。今のサーバーレスのコールドスタートとホットスタートに加え、本稿は第三のモード、休眠コンテナを提案する。本質的に痩せたホットコンテナだ。
実験では休眠のメモリはホットよりずっと少なく、リクエスト遅延はコールドスタートより低い。起こされたときもメモリはホットより少なく、同じ時間に同じリクエスト遅延。これら全部で配備密度が上がり、リクエスト遅延が下がり、システム全体の性能がはっきり良くなる。