Resumable Remote Compaction Blog Post (#14759)

Summary:
**Summary:** as titled

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14759

Test Plan:
local server rendering test
<img width="914" height="764" alt="Screenshot 2026-05-19 at 2 18 41 PM" src="https://github.com/user-attachments/assets/0e5353d4-df41-476c-8902-28d7975330d6" />
<img width="914" height="764" alt="Screenshot 2026-05-19 at 2 18 59 PM" src="https://github.com/user-attachments/assets/a0f8692d-6ca8-4c67-b7a0-b47146080c57" />
<img width="895" height="477" alt="Screenshot 2026-05-19 at 2 19 20 PM" src="https://github.com/user-attachments/assets/7479dd54-5bc4-41da-99f3-f081a714ba7c" />

Reviewed By: jaykorean

Differential Revision: D105665739

Pulled By: hx235

fbshipit-source-id: 0701a627eb2b18b9bfd3dd22397aeae9553f1903
This commit is contained in:
Hui Xiao
2026-05-19 14:35:43 -07:00
committed by meta-codesync[bot]
parent 07a5a0a804
commit 367f2b0fdf
3 changed files with 217 additions and 0 deletions
@@ -0,0 +1,55 @@
---
title: Resumable Remote Compaction
layout: post
author: hx235
category: blog
---
## Background
RocksDB can offload compaction work to remote workers through the `CompactionService` API. In this model, the **primary RocksDB instance** selects the input files and sends a serialized `CompactionServiceInput` to a worker; the **remote worker** runs `DB::OpenAndCompact()`, writes output SSTs to `output_directory`, and returns a serialized `CompactionServiceResult` that the primary RocksDB instance installs into its LSM tree. See the [Remote Compaction wiki](https://github.com/facebook/rocksdb/wiki/Remote-Compaction) for the full architecture. This lets operators scale compaction throughput with stateless workers while keeping the primary RocksDB instance's CPU and I/O available for serving reads and writes. However, remote compaction jobs can be long-running—sometimes processing hundreds of gigabytes of input. When a worker crashes, gets preempted, or times out, the entire compaction must restart from scratch, wasting all output produced before the interruption and increasing compaction debt on the primary RocksDB instance.
## How Resumable Remote Compaction Works
Resumable remote compaction introduces a **checkpoint-and-resume** mechanism. During a compaction, the worker periodically saves its progress to the `output_directory`. If the compaction is interrupted, a subsequent call to `OpenAndCompact()` with the same output directory can pick up from the last checkpoint rather than starting over.
### Checkpointing
After each output SST file is completed, the worker persists a progress checkpoint to a **compaction progress file** in the output directory `output_directory`. The checkpoint records which internal key to resume from and the metadata of all completed output files. Progress records use **delta encoding**—each record only contains files completed since the last checkpoint—to keep serialization cost linear.
![Checkpointing overview](/static/images/resumable-remote-compaction/checkpointing-overview.svg)
The worker skips checkpointing at boundaries where resuming could be unsafe or requires complicated handling: when range deletions span the file boundary or when adjacent output files share the same user key. These constraints ensure that resuming produces the same results as if the compaction was not interrupted.
### Resuming
When `OpenAndCompact()` is called with `allow_resumption = true`, it scans the output directory for a valid progress file. If one is found, it loads the checkpointed state, seeks the input iterator to the recorded resume key, restores the output file state, and continues compaction from that point. If the progress file is corrupted or missing, the system falls back to a fresh compaction by cleaning the directory.
![Resume flow](/static/images/resumable-remote-compaction/resume-flow.svg)
## How to Enable It
On the primary RocksDB instance, set a `CompactionService` implementation on the DB options. On the remote worker, pass `allow_resumption = true` in `OpenAndCompactOptions` when calling `DB::OpenAndCompact()`. The `output_directory` must be the same across retries for resumption to work—each retry call with the same directory will automatically detect and resume from the previous checkpoint. The `REMOTE_COMPACT_RESUMED_BYTES` statistics ticker tracks the total bytes of output files reused from a previous interrupted run, giving visibility into how much work resumption saved.
```cpp
// Primary RocksDB instance
DBOptions db_options;
db_options.compaction_service = std::make_shared<MyCompactionService>();
// Remote worker
OpenAndCompactOptions options;
options.allow_resumption = true;
std::string result;
Status s = DB::OpenAndCompact(
options,
db_path, // source database path
output_directory, // where output SSTs and progress are stored
compaction_input, // serialized CompactionServiceInput
&result, // serialized CompactionServiceResult
override_options);
```
## Future Work
Today this feature targets remote compaction. The same checkpoint-and-resume mechanism could also support **local compaction** after a crash. The core persistence and resume logic is already in `CompactionJob`; the remaining work is to integrate it with local compaction scheduling and recovery.
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 390" style="background-color: #fafafa;">
<defs>
<marker id="cp-arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#355070" />
</marker>
<filter id="cp-shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="2" dy="2" stdDeviation="2" flood-color="#00000020"/>
</filter>
</defs>
<text x="500" y="36" font-family="Arial, sans-serif" font-size="22" font-weight="bold" text-anchor="middle" fill="#25364d">
Checkpointing
</text>
<g transform="translate(50,80)">
<rect x="0" y="0" width="360" height="220" rx="18" fill="#eef5ff" stroke="#9cb9dd" stroke-width="2" filter="url(#cp-shadow)"/>
<text x="180" y="38" font-family="Arial, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" fill="#25364d">
During DB::OpenAndCompact()
</text>
<g transform="translate(28,86)">
<rect x="0" y="0" width="90" height="72" rx="12" fill="#daf3e5" stroke="#4f9d69" stroke-width="2"/>
<text x="45" y="30" font-family="Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="middle" fill="#214e32">1.sst</text>
<text x="45" y="51" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#214e32">complete</text>
</g>
<g transform="translate(136,86)">
<rect x="0" y="0" width="90" height="72" rx="12" fill="#daf3e5" stroke="#4f9d69" stroke-width="2"/>
<text x="45" y="30" font-family="Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="middle" fill="#214e32">2.sst</text>
<text x="45" y="51" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#214e32">complete</text>
</g>
<g transform="translate(244,86)">
<rect x="0" y="0" width="90" height="72" rx="12" fill="#fff1d6" stroke="#d08a2e" stroke-width="2" stroke-dasharray="6 4"/>
<text x="45" y="28" font-family="Arial, sans-serif" font-size="15" font-weight="bold" text-anchor="middle" fill="#7a5015">3.sst</text>
<text x="45" y="49" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#7a5015">in progress</text>
</g>
</g>
<line x1="432" y1="190" x2="558" y2="190" stroke="#355070" stroke-width="3" marker-end="url(#cp-arrowhead)"/>
<text x="495" y="168" font-family="Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="#25364d">
After each completed SST
</text>
<g transform="translate(590,80)">
<rect x="0" y="0" width="360" height="240" rx="18" fill="#fff8e8" stroke="#d4a24a" stroke-width="2" filter="url(#cp-shadow)"/>
<text x="180" y="38" font-family="Arial, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" fill="#6e4a16">
Compaction Progress File
</text>
<g transform="translate(26,76)">
<rect x="0" y="0" width="308" height="72" rx="12" fill="#fffdf8" stroke="#d4a24a" stroke-width="1.5"/>
<text x="154" y="20" font-family="Courier New, monospace" font-size="12" text-anchor="middle" fill="#4e3a19">record 1</text>
<text x="154" y="38" font-family="Courier New, monospace" font-size="12" text-anchor="middle" fill="#4e3a19">resume_from = key_after_1.sst</text>
<text x="154" y="56" font-family="Courier New, monospace" font-size="12" text-anchor="middle" fill="#4e3a19">new_files += [1.sst metadata]</text>
</g>
<g transform="translate(26,158)">
<rect x="0" y="0" width="308" height="72" rx="12" fill="#fffdf8" stroke="#d4a24a" stroke-width="1.5"/>
<text x="154" y="20" font-family="Courier New, monospace" font-size="12" text-anchor="middle" fill="#4e3a19">record 2</text>
<text x="154" y="38" font-family="Courier New, monospace" font-size="12" text-anchor="middle" fill="#4e3a19">resume_from = key_after_2.sst</text>
<text x="154" y="56" font-family="Courier New, monospace" font-size="12" text-anchor="middle" fill="#4e3a19">new_files += [2.sst metadata]</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 720" style="background-color: #fafafa;">
<defs>
<marker id="rf-arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#355070" />
</marker>
<filter id="rf-shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="2" dy="2" stdDeviation="2" flood-color="#00000020"/>
</filter>
</defs>
<text x="500" y="36" font-family="Arial, sans-serif" font-size="22" font-weight="bold" text-anchor="middle" fill="#25364d">
Resuming a Remote Compaction
</text>
<g transform="translate(275,70)">
<rect x="0" y="0" width="450" height="64" rx="18" fill="#edf4ff" stroke="#9cb9dd" stroke-width="2" filter="url(#rf-shadow)"/>
<text x="225" y="28" font-family="Arial, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" fill="#25364d">
OpenAndCompact(allow_resumption = true)
</text>
<text x="225" y="48" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#5b6573">
Retry with the same output_directory
</text>
</g>
<line x1="500" y1="134" x2="500" y2="180" stroke="#355070" stroke-width="3" marker-end="url(#rf-arrowhead)"/>
<g transform="translate(275,190)">
<rect x="0" y="0" width="450" height="78" rx="18" fill="#edf4ff" stroke="#9cb9dd" stroke-width="2" filter="url(#rf-shadow)"/>
<text x="225" y="30" font-family="Arial, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" fill="#25364d">
Scan output_directory
</text>
<text x="225" y="52" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#5b6573">
Inspect the progress file and any existing SST files
</text>
</g>
<line x1="500" y1="268" x2="500" y2="316" stroke="#355070" stroke-width="3" marker-end="url(#rf-arrowhead)"/>
<polygon points="500,316 650,386 500,456 350,386" fill="#fff8e8" stroke="#d4a24a" stroke-width="2" filter="url(#rf-shadow)"/>
<text x="500" y="392" font-family="Arial, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" fill="#6e4a16">
Valid progress file?
</text>
<line x1="350" y1="386" x2="220" y2="386" stroke="#355070" stroke-width="3" marker-end="url(#rf-arrowhead)"/>
<text x="308" y="372" font-family="Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="#25364d">No</text>
<g transform="translate(40,344)">
<rect x="0" y="0" width="240" height="72" rx="18" fill="#fff0f0" stroke="#d97070" stroke-width="2" filter="url(#rf-shadow)"/>
<text x="120" y="30" font-family="Arial, sans-serif" font-size="17" font-weight="bold" text-anchor="middle" fill="#7f2323">
Start fresh
</text>
<text x="120" y="50" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#7f2323">
Clean output_directory
</text>
</g>
<line x1="650" y1="386" x2="780" y2="386" stroke="#355070" stroke-width="3" marker-end="url(#rf-arrowhead)"/>
<text x="694" y="372" font-family="Arial, sans-serif" font-size="13" font-weight="bold" text-anchor="middle" fill="#25364d">Yes</text>
<g transform="translate(720,310)">
<rect x="0" y="0" width="230" height="106" rx="18" fill="#eef8ef" stroke="#7db28e" stroke-width="2" filter="url(#rf-shadow)"/>
<text x="115" y="30" font-family="Arial, sans-serif" font-size="17" font-weight="bold" text-anchor="middle" fill="#24503a">
Resume from checkpoint
</text>
<text x="115" y="52" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#24503a">
Load progress state
</text>
<text x="115" y="70" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#24503a">
Seek the iterator to the resume key
</text>
<text x="115" y="88" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#24503a">
Restore output file state
</text>
</g>
<line x1="160" y1="416" x2="160" y2="560" stroke="#355070" stroke-width="3" marker-end="url(#rf-arrowhead)"/>
<line x1="835" y1="416" x2="835" y2="560" stroke="#355070" stroke-width="3" marker-end="url(#rf-arrowhead)"/>
<line x1="160" y1="560" x2="355" y2="560" stroke="#355070" stroke-width="3"/>
<line x1="835" y1="560" x2="645" y2="560" stroke="#355070" stroke-width="3"/>
<g transform="translate(355,526)">
<rect x="0" y="0" width="290" height="50" rx="18" fill="#edf4ff" stroke="#9cb9dd" stroke-width="2" filter="url(#rf-shadow)"/>
<text x="145" y="31" font-family="Arial, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" fill="#25364d">
Continue compaction
</text>
</g>
<line x1="500" y1="576" x2="500" y2="646" stroke="#355070" stroke-width="3" marker-end="url(#rf-arrowhead)"/>
<g transform="translate(310,656)">
<rect x="0" y="0" width="380" height="44" rx="16" fill="#eef8ef" stroke="#7db28e" stroke-width="2" filter="url(#rf-shadow)"/>
<text x="190" y="28" font-family="Arial, sans-serif" font-size="17" font-weight="bold" text-anchor="middle" fill="#24503a">
Return CompactionServiceResult
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB