Summary
Replayer::Replay (src/replayer.cpp) reconstructs a partition's page mapping from a manifest by deserializing the base snapshot and then replaying every incremental log record in a tight while (true) loop. That loop has no cooperative yield, and because the manifest is already read into an in-memory buffer before replay, the whole reconstruction is one uninterrupted synchronous CPU segment.
In module mode the shard worker thread also drives the TxProcessor on the same core, so a long non-yielding segment in Replay stalls foreground serving for its entire duration (tens of ms for a large manifest).
Where it bites
GC's AugmentRetainedFilesFromBranchManifests -> ProcessOneManifest -> Replayer::Replay, run once per file-GC pass (i.e. after each compaction). Note:
So the remaining gap is the non-active-branch / archive path: those branches have no trustworthy in-memory mapping, so GC must replay their disk manifests — and that replay does not yield.
Proposed fix
Add a cooperative yield inside the replay record loop (e.g. MaybeYieldForCompaction() / ThdTask()->YieldToLowPQ() every N replayed records) so a single manifest replay cannot hold the worker thread past the cooperative yield budget. Keep it cheap (a counter + periodic check) since Replay is also used on the recovery/startup path.
Notes
Summary
Replayer::Replay(src/replayer.cpp) reconstructs a partition's page mapping from a manifest by deserializing the base snapshot and then replaying every incremental log record in a tightwhile (true)loop. That loop has no cooperative yield, and because the manifest is already read into an in-memory buffer before replay, the whole reconstruction is one uninterrupted synchronous CPU segment.In module mode the shard worker thread also drives the TxProcessor on the same core, so a long non-yielding segment in
Replaystalls foreground serving for its entire duration (tens of ms for a large manifest).Where it bites
GC's
AugmentRetainedFilesFromBranchManifests->ProcessOneManifest->Replayer::Replay, run once per file-GC pass (i.e. after each compaction). Note:GetRetainedFiles(the mapping-table walk that follows the replay) already yields (every 256 pages).BuildRetainedFilesavoidsReplayentirely by walking the in-memory mapping.So the remaining gap is the non-active-branch / archive path: those branches have no trustworthy in-memory mapping, so GC must replay their disk manifests — and that replay does not yield.
Proposed fix
Add a cooperative yield inside the replay record loop (e.g.
MaybeYieldForCompaction()/ThdTask()->YieldToLowPQ()every N replayed records) so a single manifest replay cannot hold the worker thread past the cooperative yield budget. Keep it cheap (a counter + periodic check) sinceReplayis also used on the recovery/startup path.Notes
ProcessOneManifestis not invoked after PR perf(compaction/gc): cut worker-thread occupation that stalls foreground serving #455). Becomes relevant with multi-branch / archive / standby manifests on disk.