ガーベジデイ:パインコンが数十億のオブジェクトを安全かつ大規模に削除する方法
ベクターデータベース「Pinecone」は、大量のデータを安全に削除する新技術「Garbage Day」を発表した。これにより、大規模なデータ管理が可能になる。
Source Article
元記事を日本語で読む
本文に関係しない購読案内、埋め込み通知、サイト内プロモーションは除いています。
TLDR: Pinecone のデータプレーンは不変の blob ストレージを利用しています。すべての書き込みは既存ファイルを変更するのではなく、新しいファイルを生成します。これにより書き込みパスがクリーンに保たれますが、その結果として古くなったオブジェクトや孤立したオブジェクトが絶えず発生し、ストレージコストを増大させています。これを解決するために、私たちはJanitorというシステムを構築しました。これは到達不能なオブジェクトを特定し、安全性を検証し、完全な監査可能性を持って削除を行うために設計されたものです。
単一ノードのデータベースでデータを安全に削除することは容易です。その原則は明白です:まだ参照されている可能性があるものを決して削除しないこと、削除前に検証すること、そして監査証跡を保持することです。しかし、不変ストレージ上で構築するすべてのチームが最終的に同じ事実に直面します:もう必要なくなったデータこそが、支払いを止められない問題となるのです。
実世界のシステムでは、スケーラビリティの観点から削除処理が困難になります。なぜなら、「削除」という操作は単一の動作ではなく、長期間にわたる一連のサービス呼び出し、キャッシュ管理、リトライロジック、そして様々な障害モードを含む複雑な連鎖だからです。Pinecone では、この教訓を非常に高いコストをかけて学びました。当社のデータプレーンは、不変オブジェクトを基盤とした blob ストレージ上に構築されています。この設計は書き込みパスを清潔に保つ一方で、根本的な調整(コーディネーション)の問題を生み出しました。書き込みノードと読み取りノードが分離されているため、あるファイルが古くなった瞬間に書き込み側が即座にそのファイルを削除することはできません。なぜなら、別の読み取りノードがまだそのオブジェクトからリアルタイムのクエリを処理している可能性があるからです。この乖離により、自動的に消滅することのないファイルが絶え間なく蓄積され、これが静かにながら当社のインフラコスト明細書において最大項目の一つへと成長してしまいました。
これが削除税(デリーション・タックス)です。もはや不要なデータに対して、安全に削除できないために発生する増大するコストのことです。Janitor は、このコストを削減するために私たちが導入した仕組みです。

Janitor 導入前後の S3 ストレージコスト
なぜ不変性がガベージを生むのか
不変性は、分散システムや関数型プログラミングにおける古くからの原則です。これにより、競合状態の全体クラスが削減され、回復と監査可能性が大幅に簡素化されます。書き込みはファイルをその場で変更するのではなく、各書き込みで新しいファイルが生成され、メタデータは比較・スワップ (Compare-and-Swap: CAS) 操作によって自動的に更新され、最新の状態を指し示します。
その代償として生じるのが廃棄物です。古いバージョンはすべての書き込みの後に残存します。さらに悪いことに、バグの書き込みとメタデータのコミット処理の間にプロセスがクラッシュすると、ファイルは孤児化します — 保存領域には存在するものの、システム内のどの参照パスからも見えなくなります。
では、なぜ積極的に削除しないのでしょうか?それは伝播遅延のためです。システムの異なる部分(キャッシュ、ルーター、レプリカ)はすべて同時に更新されるわけではありません。古いデータへの参照は、データ自体よりも長く残存し続ける可能性があります。早すぎる削除を行うと、ストレージコストを削減する代わりに信頼性の問題を引き起こすことになります。
3 つの削除モード
すべての古くなったデータが同じ理由で古くなっているわけではなく、またすべてが削除される際に同じリスクを負うわけではありません。Janitor は「削除」を、それぞれ固有のリズムと障害プロファイルを持つ3つの異なる問題として扱います。
通常モードは、日常的に蓄積される不要ファイルの処理を担当します。これは、新しい書き込みによって置き換えられ、*マニフェスト*(現在アクティブなブロッブオブジェクトを追跡し、クエリがどのデータバージョンを見るべきかを管理するメタデータ構造)から到達できなくなったファイルです。不変ストレージを使用するすべてのシステムは、この種の廃棄物を絶えず生成します。
- シャードごとに 4 時間ごとにスコープを限定したクリーンアップを実行し、全シャードにわたる広範な週次パスも実行します。
- 主なリスクはタイミングにあります。古いブロッブへの参照がキャッシュに残存する可能性があるため、論理的に置き換えられた後でも、何らかのものがファイルを指し続ける可能性があります。
孤児モードは、より困難な問題を処理します。通常モードはシステムが認識しているが不要になったファイルをクリーンアップしますが、孤児モードはシステムが全く認識していないファイル、例えばメタデータのコミット前にクラッシュしたサービスによって書き込まれたブロッブなどをクリーンアップします。マニフェストエントリも参照パスもなく、ストレージ上に単にファイルが存在し続けるだけでコストが蓄積されます。
- ブロッブストレージ自体から開始して後方へ遡り、各オブジェクトが到達可能かどうかを証明する月次スキャンとして実行されます。
- 孤児は定義上通常の操作では不可視であるため、このモードが必要です。
顧客削除モードは、3 つのモードの中で最も運用上の繊細さを要します。顧客がインデックスまたは名前空間を削除した場合、データが消去されるという期待がありますが、「消える」と「即座に不可逆的に消える」は非常に異なる約束です。
- 制御プレーンに、解決日を 30 日後とする削除操作を挿入します。
- その期間中は、誤った削除も復元可能です。期限が過ぎると、データは完全に消去されます。
コアプロトコル:特定 → 検証 → 実行
各モードでは異なる種類の廃棄物を特定します。このプロトコルこそが、その除去を安全に行うための根幹です。
大規模な環境において重要なのは、「仮説(これは削除可能に見える)」と「許可(削除しても安全であると確信している)」の区別です。Janitor のワークフローはこの分離を中心に構築されており、意図的に保守的なアプローチを採用しています。どのモードであっても、各実行は常に同じ 3 つのフェーズに従います。
特定。 何も削除せずに候補セットを計算します。この候補セットは単なる仮説に過ぎず、まだ何にも手を加えていません。
- ノーマルモードでは、マニフェストとその履歴全体を走査し、現在到達可能であるものと、古くなって期限切れとなったものを判別します。
- オファンモード(孤児化オブジェクト検出モード)では方向が逆で、ブロッブストレージから始めてオブジェクトを列挙し、各オブジェクトが到達可能かどうかを証明するために遡って処理を行います。
- いずれの場合も、候補はローカルの SQLite データベースに書き込まれます。仮説を永続化することで、プロセスの監査可能性と再開可能性が確保されます。もしここで Janitor がクラッシュしても、何も失われません。
検証。 仮説を、世界に対する新鮮な視点(最新の状態)に対して再チェックします。ここではシステム内の異なる部分間の伝播遅延(propagation lag)への対応が行われます。
- 到達可能性を再計算し、すべての候補がまだ削除しても安全であることを確認する。
- いずれかの候補が現在到達可能なオブジェクトと重なっている場合の対応は、遅延させるか除外することである。
- 世界の状態が古くなっていると判断された場合、Janitor は待機する。誤検知(生きているものを削除してしまうこと)のコストは、見落とし(死んだものをもう一つのサイクルの間保持しておくこと)のコストに比べて圧倒的に高く、この非対称性を強制するのが検証ステップである。
実行。確認済みのものだけを削除する。大規模な環境ではプロセスが中断される可能性があるため、すべての実行は途中で停止した場合でもきれいに再開できるように設計されている。
- すべての削除操作は、安全に再試行可能な冪等性を持つ作業として扱われる。
- SQLite の状態管理により、どの対象を指定したか、どれが完了したかが正確に追跡されるため、ある実行が途中で停止した場合でも、次の実行ではどこから再開するかを知ることができる。
- ブロブストレージの保持および復元ツールリングは実用的な回復ウィンドウを提供するが、Janitor のチェックポイントこそが、問題発生時に何が起きたかを調査し、影響を定量化し、必要な場合に復旧を推進することを可能にするものである。
数秒で1か月の動作をテストする
これは少し脱線した内容だが、全体の中でも最も洗練された部分であり、あまりにも興味深いので省略できない。
清掃プログラムの最も厄介なバグは、単一の実行では現れません。それらは、繰り返される書き込み、再構築、部分的な失敗、顧客による削除、そしてスケジュールされたクリーンアップが重なり合う相互作用から浮上します。これらの中には 30 日間の期間にわたって展開されるものもありますが、テストの合格を確認するために 1 ヶ月待機する選択肢はありません。
そのテストを可能にするため、清掃プログラムはモッククロックを用いたプロパティベーステストを採用しています。実行可能なアクション(書き込み、再構築、クラッシュ、削除、清掃プログラムの実行)のセットを定義し、数百のランダムなシーケンスを生成して、実際の実装と簡略化された参照モデルの両方に通し、結果を比較します。モッククロックは数日や数ヶ月をサブ秒単位のテストに圧縮します。もし 2 つのモデルが一致しない場合、再現可能なバグを手にしたことになります。
本番環境からの教訓
大規模な安全な削除は、防御策を重ねることで実現されます。つまり、何かを削除する前に第 2 の到達可能性チェックを行い、各実行を検査可能かつ再起動可能にする永続的なチェックポイント、そして数週間にわたる動作を数秒に圧縮するテスト戦略です。清掃プログラム以前では、削除は不安の源でした。現在は背景で継続的に実行され、確実にクリーンアップを行い、監査証跡を生成するため、予期せぬことが起きた場合でも、その原因を理解するための道筋がすでに用意されています。
顧客にとって、これらすべては目に見えてはならず、それが目的です。ファイルが早すぎに削除されたためにクエリが失敗することはありません。誤ってインデックスを削除した場合でも、30 日間の期間内であれば復元可能です。ストレージコストは予測可能であり、静かに上昇し続けることはありません。Janitor は、チームの他のメンバーがごみについて一切考える必要がないようにするインフラストラクチャの一種です。
原文を表示
TLDR: Pinecone’s data plane utilizes immutable blob storage. Every write produces a new file rather than modifying an existing one. While this keeps the write path clean, it generates a constant stream of stale and orphaned objects that drive up storage costs. To solve this, we built Janitor: a system designed to identify unreachable objects, verify safety, and delete with full auditability.
Deleting data safely in a single-node database is easy. The principles are straightforward: do not delete anything that might still be referenced, verify before deletion, and keep an audit trail. But every team building on immutable storage eventually discovers the same thing: the data you no longer need becomes the problem you can't stop paying for.
In real-world systems, deletion gets hard at scale, because "delete" is rarely one operation. Instead, it's a long chain of services, caches, retries, and failure modes. At Pinecone, we learned this the expensive way. Our data plane is built on immutable objects in blob storage—a design that keeps the write path clean but creates a fundamental coordination problem. Because our writing nodes and reading nodes are separate, a writer cannot simply purge an old file the moment it's superseded; separate reading nodes may still be actively serving a live query from that exact object. This disconnect creates a steady stream of files that never go away on their own, which quietly became one of the largest line items in our infrastructure bill.
This is the deletion tax: the growing cost of data you no longer need but can't yet safely remove. Janitor is how we pay it down.

S3 Storage Costs Before and After Implementation of Janitor
Why immutability creates garbage
Immutability is an age-old principle in distributed systems and functional programming. It reduces whole classes of race conditions and makes recovery and audit-ability much simpler. Writes do not modify files in place. Instead, each write produces a new file and metadata is updated automatically with a Compare-and-Swap (CAS) operation to point to the latest state.
The tradeoff is waste. Old versions linger after every write. Worse, if a process crashes between writing a blob and committing its metadata, the file becomes an orphan — present in storage but invisible to every reference path in the system.
So why not just delete aggressively? Because of propagation lag. Different parts of the system (caches, routers, replicas) don't all update at the same time. References to old data can stick around longer than the data itself. Delete too early, and you trade storage costs for reliability problems.
The three deletion modes
Not all stale data is stale for the same reason, and not all of it carries the same risk when removed. Janitor treats "deletion" as three distinct problems, each with its own cadence and failure profile.
Normal mode handles the everyday accumulation. Files that have been superseded by newer writes and are no longer reachable from the *manifest* (the metadata structure that tracks which blob objects are currently live and which version of the data a query should see). Every system that uses immutable storage generates this kind of waste constantly.
- Runs scoped cleanup every four hours per shard, with a broader weekly pass across all shards.
- The main risk is timing: references to old blobs can linger in caches, so something might still be pointing to a file even after it's been logically replaced.
Orphan mode handles a harder problem. Normal mode cleans up files the system knows about but no longer needs. Orphan mode cleans up files the system doesn't know about at all, like blobs written by a service that crashed before committing metadata. No manifest entry, no reference path. Just a file sitting in storage, accumulating cost.
- Runs as a monthly scan starting from blob storage itself, working backward to prove whether each object is reachable.
- Required because orphans are invisible to normal operations by definition.
Customer deletion mode is the most operationally sensitive of the three. When a customer deletes an index or namespace, the expectation is that the data goes away, but "goes away" and "goes away immediately and irreversibly" are very different promises.
- Inserts a deletion operation into the control plane with a resolution date 30 days out.
- During that window, accidental deletes are recoverable. After it closes, the data is gone for good.
The core protocol: identify → verify → execute
Each mode identifies a different kind of waste. The protocol is what makes sure removing it is safe.
At scale, the important distinction is between hypothesis ("this looks deletable") and permission ("we are sure it is safe to delete"). Janitor's workflow is built around that separation, and it's intentionally conservative. Every run follows the same three phases, regardless of mode.
Identify. Compute a candidate set without deleting anything. The candidate set is just a hypothesis, nothing has been touched yet.
- In normal mode, this means iterating through the entire manifest and its history to determine what is currently reachable versus what has aged out.
- In orphan mode, the direction is reversed: start from blob storage, enumerate objects, and work backward to prove whether each one is reachable.
- Either way, candidates are written to a local SQLite database. Persisting the hypothesis makes the process auditable and restartable. If Janitor crashes here, nothing is lost.
Verify. Recheck the hypothesis against a fresh view of the world. This is where the propagation lag between different parts of the system gets handled.
- Recompute reachability and confirm every candidate is still safe to remove.
- If any candidate intersects something currently reachable, the response is to delay or drop it.
- If the world looks stale, Janitor waits. The cost of a false positive (deleting something live) is vastly higher than the cost of a false negative (keeping something dead around for another cycle), and the verification step is where that asymmetry is enforced.
Execute. Delete only what has been verified. At scale, processes get interrupted, so every run is built to be picked up cleanly if it stops halfway through.
- Every deletion is treated as idempotent work that can be retried safely.
- SQLite state tracks exactly what was targeted and what completed, so if a run dies halfway through, the next run knows where to resume.
- Blob storage retention and restore tooling provide the practical recovery window underneath, but Janitor's checkpoints are what make it possible to investigate what happened, quantify impact, and drive restoration when things go wrong.
Testing month-long behavior in seconds
This is a bit of an aside, but it's arguably the most sophisticated part of the whole build and too interesting to leave out
Janitor's hardest bugs don't show up in a single run. They emerge from the interaction of repeated writes, rebuilds, partial failures, customer deletes, and scheduled cleanups layered on top of each other. Some of which play out over a 30-day window, but waiting a month to find out a test passed isn't an option.
To make that testable, Janitor uses property-based tests with a mock clock. Define a set of possible actions (writes, rebuilds, crashes, deletes, Janitor runs), generate hundreds of randomized sequences, run them through both the real implementation and a simplified reference model, and compare outcomes. The mock clock collapses days and months into sub-second tests. If the two models ever disagree, we have a reproducible bug in hand.
Lessons from Production
Safe deletion at scale comes from layering defenses — a second reachability check before anything is removed, durable checkpoints that make every run inspectable and restartable, and a testing strategy that compresses weeks of behavior into seconds. Before Janitor, deletion was a source of anxiety. Now it runs continuously in the background, cleans up reliably, and generates an audit trail, so when something unexpected happens, the path to understanding it is already there.
For customers, none of this should be visible, and that's the point. Queries don't fail because a file was deleted too early. Accidental index deletes are recoverable within a 30-day window. Storage costs stay predictable instead of silently climbing. Janitor is the kind of infrastructure that ensures the rest of the team doesn't have to think about garbage at all.
関連記事
今日のまとめ
AIデイリーブリーフで今日の重要ニュースをまとめ読み