Vercel Sandboxのスナップショット最適化
VercelはSandboxのスナップショット復元時間を40秒以上から1秒未満に高速化し、並列ダウンロード・並列解凍・ローカルキャッシュの導入によって開発者体験を大幅に改善した。
キーポイント
スナップショット復元の劇的な高速化
p75スナップショット復元時間が40秒以上から1秒未満に改善され、開発者体験が大幅に向上した。
並列処理によるパフォーマンス最適化
S3からのダウンロードをRange HTTPヘッダーで並列化し、解凍処理も複数ゴルーチンで並列実行することで、2-5倍の高速化を実現した。
ローカルキャッシュの導入
NVMeディスクの未使用領域を活用したLRUキャッシュを実装し、S3からの取得頻度を減らすことで高速パスを確立した。
エンドツーエンドの最適化
S3の範囲リクエストストリームを直接解凍処理にパイプし、中間ファイルのディスク書き込みを排除することで、さらに2倍の高速化を達成した。
影響分析・編集コメントを表示
影響分析
この最適化はクラウド開発環境の応答性を大幅に向上させ、開発者の生産性向上に直接寄与する。特に大規模プロジェクトでの開発体験改善に影響を与え、コンテナベース開発環境のパフォーマンス基準を引き上げる可能性がある。
編集コメント
信頼性確保後にパフォーマンス最適化に取り組む実践的なエンジニアリングアプローチが参考になる。開発者体験の向上に直結する具体的な改善事例として価値がある。
最近、Vercel Sandboxにファイルシステムスナップショット機能をリリースし、サンドボックスのファイルシステム全体の状態をキャプチャして復元できるようにしました。スナップショットは信頼性が高く、使いやすく、高速である必要があります。当初は信頼性に重点を置き、スナップショットの取得、復元、データ損失が決して発生しないことを確実にしました。しかし、安定していても極端に遅い製品は誰も使いません。p75のスナップショット復元には40秒以上かかっていました。現在では並列化とローカルキャッシュにより、1秒未満に抑えられています。
ディスク上のスナップショットの仕組み
Vercel Sandboxは、社内ビルド製品であるHiveと同じインフラストラクチャ上で動作します。各サンドボックスはFirecracker microVM内の隔離されたコンテナです。
スナップショットは、サンドボックスのディスクの圧縮コピーです。私たちは2種類のファイルを扱っています:
生のディスクイメージ(.img) - 数GBになる可能性があります
カスタムVHS形式(Vercel Hive Snapshot)の圧縮版 - これがS3にアップロードされ、S3からダウンロードされます
sandbox.snapshot()を呼び出すと、.imgを.vhsに圧縮してS3にアップロードします。スナップショットを指定してSandbox.create()を呼び出すと、.vhsをダウンロードして展開します。圧縮によりファイルサイズが削減され、ネットワーク経由で数百MBから数GBを転送する際に重要になります。
並列化を実現する
信頼性を確保した後、パフォーマンスに取り組みました。復元のプロセスは非常に直列的なものでした。単一のリクエストでS3から.vhsファイル全体をダウンロードし、完了を待ってから単一スレッドで解凍していました。
スナップショットは200MBから数GBの範囲であるため、S3からの単一ダウンロードだけで数秒から数十秒かかることがありました。代わりに、Range HTTPヘッダーを使用してチャンクを並列ダウンロードするようにしました。AWS Go SDKには、まさにこのためのtransfermanager APIが組み込まれています。様々な並列度とチャンクサイズをベンチマークした結果、ダウンロード速度が2〜5倍向上しました。
次に取り組んだのは解凍です。当社の.vhs形式は、ヘッダーと、ディスクイメージの割り当て済み領域ごとのフレームで構成されています。フレームを1つずつデコードして解凍するのではなく、1つのデコーダーとN個の解凍ゴルーチンに切り替えました。これにより、.vhsから.imgへの復元が、スナップショットサイズに応じて2〜4倍高速化されました。
ダウンロードと解凍の両方を並列化しても、まだ最適化の余地が一つ残っていました。完全なダウンロードが終わるのを待ったり、中間ファイルをディスクに書き込んだりすることなく、S3の範囲リクエストのストリームを直接解凍処理にパイプするようにしました。これにより、エンドツーエンドの復元時間がさらに2分の1に短縮されました。
キャッシュ…していなかった?
お気づきかもしれませんが、これまで話してきたのは、キャッシュミス時にS3からスナップショットを取得する必要がある「低速パス」の改善についてだけです。実際には「高速パス」が存在しなかったため、すべてがキャッシュミスでした。ええ、当初はパフォーマンスに全く焦点を当てていなかったのです。
当社のサンドボックスはNVMeディスクを搭載したメタルインスタンスで動作するため、数テラバイトの高速なローカルストレージのほとんどが未使用でした。
LRU(Least Recently Used)方式によるローカルディスクキャッシュを追加し、エントリ数ではなく合計ディスク容量でサイズを決定しました。圧縮された.vhsではなく、展開済みの.imgを直接キャッシュするため、キャッシュヒット時にはダウンロードと解凍の両方をスキップできます。キャッシュが満杯になると、最も最近使用されていないスナップショットが削除(エビクション)され、スペースが確保されます。
ほとんどのお客様は多くのサンドボックスで再利用する「ベース」スナップショットをお持ちであるため、95%のキャッシュヒット率を達成しています。キャッシュヒット時には、起動時間はmicroVMとコンテナの起動のみに制限されます。
40秒から1秒未満へ
p75は40秒から1秒未満に、p95は50秒から5秒に低下しました。当社のキャッシュヒット率により、ほとんどのサンドボックス起動では、ダウンロードと解凍のパイプラインを完全にスキップします。
さらに多くのアイデアを検討中です。キャッシュアフィニティを導入すれば、要求されたスナップショットが既にキャッシュされているメタルインスタンスにサンドボックスをルーティングし、人気のあるスナップショットに対する「コールドパス」を排除できる可能性があります。しかし、これはサンダリングハード現象や特定マシンのホットスポット化を招くリスクがあるため、慎重に進めています。
長期的には、キャッシュが必須条件ではなく付加価値となるほど、「コールドパス」を十分に高速化したいと考えています。
これらの最適化は、現在ベータ版のAutomatic Persistence(自動永続化)ですでに活用されています。名前付きサンドボックスを停止すると、そのファイルシステムは自動的にスナップショットされ、再開時に復元されます。1秒未満での復元により、このサイクルは瞬時に感じられます。
ファイルシステムスナップショットは、すべてのVercel Sandboxで本日より利用可能です。開始するにはSandboxドキュメントをご確認ください。
続きを読む
原文を表示
Recently, we shipped filesystem snapshots in Vercel Sandbox, letting you capture and restore a sandbox's entire filesystem state. Snapshots need to be reliable, easy to use, and fast. Initially we focused on reliability, making sure we never fail to snapshot, restore, or lose data. But no one will use a stable product that is also dead slow. p75 snapshot restores were taking over 40 seconds. Now through parallelization and local caching, we got that under 1 second.
What a snapshot looks like on disk
Vercel Sandbox runs on the same infrastructure as our internal builds product, Hive. Each sandbox is an isolated container inside a Firecracker microVM.
A snapshot is a compressed copy of the sandbox's disk. We're working with two different files:
The raw disk image (.img), which can be several GBs
A compressed version in our custom VHS format (Vercel Hive Snapshot), which is what gets uploaded to and downloaded from S3
When you call sandbox.snapshot(), we compress the .img into a .vhs and upload it to S3. When you call Sandbox.create() with a snapshot, we download the .vhs and decompress it back. The compression cuts the file size, which matters when you're moving hundreds of MBs to GBs over the network.
Parallelize you shall
With reliability in place, we turned to performance. The restore path was painfully sequential. We'd download the entire .vhs file from S3 in a single request, wait for it to finish, then decompress it in a single thread.
Snapshots range from 200MB to a few GBs, so that single S3 download alone could take several seconds to tens of seconds. We used the Range HTTP header to download chunks in parallel instead. The AWS Go SDK has a transfermanager API built for exactly this. After benchmarking various concurrency and chunk sizes, we ended up with 2-5x faster downloads.
Next up was decompression. Our .vhs format is composed of a header and a frame for each allocated region of the disk image. Instead of decoding and decompressing frames one by one, we switched to one decoder and N decompression goroutines. This made the .vhs to .img restore 2-4x faster, depending on snapshot size.
With both downloading and decompressing parallelized, we still had one remaining optimization. We piped S3 range request streams directly into decompression, without writing an intermediary file to disk or waiting for the full download to complete. That cut end-to-end restore time by another 2x.
We… didn't cache?
As you might have noticed, we so far only talked about improving the slow path, when we need to retrieve a snapshot from S3 on a cache miss. Well, we actually didn't have a fast path, so it was all cache misses. Yeah, we really didn't focus on performance at first.
Our sandboxes run on metal instances with NVMe disks, which means several terabytes of fast local storage that was mostly unused.
We added a local disk cache using LRU (least recently used) eviction, sized by total disk space rather than number of entries. We cache the decompressed .img directly rather than the compressed .vhs, so a cache hit skips both the download and the decompression. Once the cache fills up, the least recently used snapshots get evicted to make room.
Most customers have a "base" snapshot that they reuse across many sandboxes, so we're seeing a 95% cache hit rate. On a cache hit, boot time is bounded only by starting the microVM and container.
From 40 seconds to sub-second
p75 dropped from 40s to sub-second, and p95 went from 50s to 5s. With our cache hit rate, most sandbox boots skip the download and decompression pipeline entirely.
We're exploring more ideas. Cache affinity would route sandboxes to metal instances that already have the requested snapshot cached, potentially eliminating the cold path for popular snapshots. But this risks thundering herds and hotspotting certain machines, so we're being deliberate about it.
Long term, we want the cold path fast enough that caching is a bonus, not a requirement.
These optimizations already power Automatic Persistence, now in beta. When you stop a named sandbox, its filesystem is automatically snapshotted and restored on resume. Sub-second restore means that cycle feels instant.
Filesystem snapshots are available today for all Vercel Sandboxes. Check the Sandbox documentation to get started.
Read more
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み