Checkpoint cleanup dirties a fully deleted disaggregated internal page for a no-progress rewrite before its truncate is globally visible

XMLWordPrintableJSON

    • Storage Engines - Transactions
    • 188.853
    • SE Transactions - 2026-09-11
    • 3

      Summary

      Checkpoint cleanup must read a fully deleted disaggregated internal page: the internal image contains the child references and page IDs needed to evaluate each deletion and eventually reclaim the remote blocks. Until the truncate is globally visible, cleanup cannot remove any child.

      The problem was the side effect of that read. Page-in reconstructed the deleted address cells as WT_REF_DELETED refs and immediately dirtied the internal parent whenever the btree had been modified. If no deletion was globally visible, cleanup reclaimed nothing, but checkpoint still reconciled and rewrote the page. Once the clean page was evicted back to WT_REF_DISK, a later cleanup pass repeated the same read/dirty/reconcile/write cycle – the pattern the WT-18406 cursor skip was added to stop, paced by the cleanup thread's own interval (checkpoint_cleanup.wait, default 300s).

      Approach

      Keep the cleanup read, but suppress its automatic page-in dirtying only for a disaggregated btree in the dedicated checkpoint-cleanup session:

      • Internal page-in computes once per page whether rebuilding a deleted reference should dirty its parent; only the combination of a disaggregated btree and the checkpoint-cleanup session suppresses the dirtying. Both row-store and column-store reconstruction use this decision.
      • After page-in, cleanup scans each child through its existing state-locked path: a child whose deletion is globally visible dirties the parent in the same pass, so mixed-age children do not wait for the newest deletion. The parent stays clean only when no child made progress.
      • Traditional block-manager trees keep the original page-in behavior, including in the checkpoint-cleanup session.

      An earlier proposal gated the read on global visibility of the persisted newest stop point. It was rejected: that aggregate can prove all descendant deletions are visible, but not that none are, so one reclaimable older child would wait behind a newer untimestamped one, with continuing deletes repeatedly advancing the gate (head-of-line blocking). Reading the page and retaining the per-child decisions avoids both problems.

      Observability

      New data-source statistic checkpoint_cleanup_pages_deleted_no_reclaim: counts an internal parent once per cleanup pass when it holds deleted children but no deleted or on-disk child was reclaimed. The pending-reclamation backlog is visible in FTDC.

      Scope

      • src/btree/bt_page.c – the page-in dirty-parent decision for row-store and column-store internal pages.
      • src/btree/bt_sync_obsolete.c – per-child outcome tracking for the no-reclaim statistic.
      • dist/stat_data.py plus regenerated statistics files.
      • test/suite/test_disagg_fast_truncate03.py – pre-visibility steps (two cleanup passes: pages read, nothing reclaimed, page stays clean and is clean-evicted, checkpoint performs no no-progress rewrite) and post-visibility assertions.

      Out of scope: smoothing the burst when many subtrees become visible at once, and any change to non-disaggregated cleanup behavior.

            Assignee:
            Shoufu Du
            Reporter:
            Shoufu Du
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: