v3 engine: default InMemory object store with a persistent log_directory loses all metadata on restart, then refuses envoys with depot.corrupt ("record is lost")
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 48/100
調査の方向性
Start with engine/packages/config/src/config/object_storage.rs and engine/packages/engine-context/src/production/object_storage.rs to trace the default object store and how it is created. Then read engine/packages/depot-worker/src/tablet_maintainer_machine/state/filesystem.rs around record_lost; the report links this path to the permanent corruption error. Reproduce with a persistent log directory and no object storage, then confirm a startup warning or refusal clearly identifies the durability configuration problem.
索引モデルが issue の本文から書いたものです。
説明
Summary
On a single self-hosted engine (rivetdev/engine:3.0.0-preview.2), we set storage.log_directory to a Docker volume and left object_storage unset. The primary object store then defaults to InMemory.
The depot logs survive a restart; everything in the primary store does not: tablet manifests, tablet SlateDB cold stores (volume records), topology and leases. After any restart, the engine:
- comes back with
manifest_version=3; - forgets namespaces, pools and actors;
- for every volume whose data had already been offloaded to the (now empty) cold store, logs
a volume has no stored record but a replica reports a cold position above zero; the record is lost…; - refuses every envoy connect with
depot.corrupt: The volume's stored data failed a consistency check.from then on.
Another restart does not help, because the on-disk replicas keep reporting cold > 0 against a cold store that no longer exists.
The engine gives no warning at startup, and nothing documents that object_storage is required for durability. A persistent log_directory makes the install look durable. This combination (durable log, volatile cold store) turns every restart into data loss, and on a busy engine into a permanent depot.corrupt.
We first read the split-time warnings below as the cause. They are noise: the engine re-splits from one tablet to 2 × cores tablets after each restart, because the manifests are gone.
Versions
- Engine:
rivetdev/engine:3.0.0-preview.2(starting rivet version=3.0.0).v3.0.0-preview.3and3.xhave no changes underengine/packages/depot-*. - Single node, 32 cores (so
desired_tablets=64), Linux, Docker. Disk was 30% full. - Environment:
RIVET__STORAGE__LOG_DIRECTORY=/data/storage # a Docker volume RIVET__STORAGE__AUTO_PROVISION_PARTITIONS=1 RIVET__TELEMETRY__ENABLED=false RIVET__AUTH__ADMIN_TOKEN=… # no RIVET__OBJECT_STORAGE__*
Root cause
ObjectStoreBackendhas#[default] InMemory(config/src/config/object_storage.rs#L57-L60).create_object_storesbuildsInMemory::new()for it (engine-context/src/production/object_storage.rs#L135). Nothing checks this against a persistentstorage.log_directory.- The tablet cold store (SlateDB, holding
FsVolumeRecordrows), the tablet manifest and topology all use that primary store. The depot-log shards (/data/storage/shards) are on disk./data/storage/cold-cacheis only SlateDB's disk cache: the volume we kept hascold-cache/andshards/and no manifests. - After a volume's first record is flushed to the cold store (
write_to_cold_after_ms), its log is trimmed and the replica's cold position rises above 0. After a restart,record_lost(tablet_maintainer_machine/state/filesystem.rs#L523-L539) sees no stored record andcold.volume_seq > 0, and quarantines the incarnation. depot-client mapsVolumeRecordMissingtoFsReadError::Corrupt/depot.corrupt, which is permanent. Every later boot reaches the same verdict. - The restart in our case came from the designed fail-stop under disk pressure:
log shard failed; restart required reason="write to disk timed out"(write_to_disk_timeout_ms= 30 s), thenstate machine requested fail-stop; exiting process. Before that, the log had hours offailed to write index … filesystem_overloaded. A plaindocker restartdoes the same thing (see reproduction).
Evidence, from /depot/repair/v1/volume/inspect on a copy of the data volume: recordMissing: true, stored: null, the replica InSync with cold = appended = {volumeSeq: 20}, and placement.agree: true (manifest and maintainer agree, so this is not a stale grant). On every boot the manifest starts again at manifest_version=3.
Log excerpt (first incident, 2026-10-28)
22:49:22.839 level=error message="log shard failed; restart required" reason="write to disk timed out"
22:49:22.839 level=error message="state machine requested fail-stop; exiting process" machine=log_shard_machine seq=96731
22:49:38.459 level=info message="starting rivet" version=3.0.0
22:49:39.447 level=info message="tablet grant written" tablet_id=ed6733f2-… from=None grant_epoch=1 manifest_version=3
22:49:39.670 level=warn message="a volume has no stored record but a replica reports a cold position above zero; the record is lost, so the incarnation stays unregistered and its name is refused until the incarnation is deleted" key="VolumeLogKey(... partition_key: b\"\\x15\\x1c\\x02default\\0\" ...)" observed="Some(VolumeLogPosition { volume_seq: 4989, client_epoch: 55834574849 })"
22:49:42.143 level=error message="a volume holds a claim epoch of a newer grant; refusing new claims" grant_epoch=8 min=55834574929
22:49:59.599 level=warn message="no member holds the split parent's maintainer log; its ops are not imported" ...
22:49:59.601 level=warn message="unknown filter policy '_bf:p=depot-fs-formal-row-v2' in SST, skipping" ... step="CloneLeft { manifest_id: 9 }"
22:50:xx ... every envoy connect: depot.corrupt: The volume's stored data failed a consistency check.
64 incarnations were quarantined, including the engine's own volumes for the namespace and pools (\x02default\0\x02workspace\0…). A later restart, and a fresh engine on a copy of the volume, quarantine the same 64.
Reproduction (about 3 minutes)
-
Start a fresh engine with a persistent log directory and no object storage.
WRITE_TO_COLD_AFTER_MSonly speeds up the offload:docker network create repro-net docker run -d --name repro-engine --network repro-net --network-alias rivet-engine \ -e RIVET__AUTH__ADMIN_TOKEN=$TOKEN -e RIVET__STORAGE__LOG_DIRECTORY=/data/storage \ -e RIVET__STORAGE__AUTO_PROVISION_PARTITIONS=1 -e RIVET__STORAGE__WRITE_TO_COLD_AFTER_MS=5000 \ -v repro-engine-data:/data/storage rivetdev/engine:3.0.0-preview.2 curl -X PUT -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \ "http://rivet-engine:6420/pools/repro?namespace=default" -d '{}' -
Run a RivetKit worker with one actor:
state: { n: 0 }, and abumpaction that doesc.state.n += 1. Callbump20 times on each of 8 actors (getOrCreate(["repro-<i>"])). Every actor reportsn=20. -
Wait about 2 minutes, then
docker restart repro-engine. This is a graceful stop. -
Observe:
- the engine logs
manifest_version=3again; - envoys are refused with
not_found: The node pool does not exist., andGET /namespacesshowsdefaultre-created at the restart time; - after recreating the pool, the same actors return
n=0: their state is silently gone.
On a busier engine, where volumes (including the engine's own metadata volumes) had cold positions above 0 before the restart, step 4 becomes the permanent
depot.corruptdescribed above. - the engine logs
With RIVET__OBJECT_STORAGE__PRIMARY__FILE_SYSTEM__PATH=/data/storage/objects added, the same steps keep the manifest (no reset to 3) and all 8 actors read back n=20 after the restart. Our production engine uses S3 and comes back intact after restarts.
Suggestions
- Refuse to start, or at least log an error, when
storage.log_directoryis persistent andobject_storage.primaryisInMemory. Alternatively, default the primary store toFileSystemunderlog_directorywhen one is set. - Document that
object_storageis required for a durable self-hosted v3 engine. - When a record is missing on a tablet whose manifest was just bootstrapped (
manifest_versionstarting from scratch while replicas exist on disk), say so in the warning and indepot.corrupt. "failed a consistency check" sends people looking for disk corruption.
We still have the engine log (~15.8k lines) and the corrupt data volume (~154 MB) and can share them privately on request. We are not attaching them here because they may contain user data.
- 主要言語
- Rust
- スター
- 6.6k
- フォーク
- 304
- 平均マージ
- 18時間 5分
- マージ済み PR(30日)
- 31
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
rivet-dev/rivet のほかの issue
-
rivetkit client: ActorConn debug log prints action arguments再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 1/5 1時間未満 初心者へのやさしさ 80/100
rivet-dev/rivet#5955 · コメント 4 件 ·
メンテナーはふだん 1 日以内に返信
-
rivetkit 3.0: HTTP 400 overloaded on node pool upsert is treated as fatal and exits the processオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
rivet-dev/rivet#5933 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
effect: action on a missing actor instance fails as internal_error instead of actor.aborted対応中かも @kunalgoesbyken が 11 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
rivet-dev/rivet#5891 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Rust client drops connection-level errors when actionId is null (JSON/CBOR)対応中かも @DibbayajyotiRoy が 34 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
rivet-dev/rivet#5819 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Queue docs say completable messages are removed on receive, but they stay stored until complete()対応中かも @Saisharathchandranandnetha が 44 日前に担当しました。 オープン
難易度 1/5 1〜3時間 初心者へのやさしさ 91/100
rivet-dev/rivet#5762 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
rivet-dev/rivet の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
apache/datafusion-iceberg#100 ·
メンテナーはふだん 1 日以内に返信
-
[Bug]: cua-driver skill fails Agent Skills frontmatter validation対応中かも @Mearman が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
area/tools bug session-analysis
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
oracle/rust-oracledb#66 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信