Altering write_timestamp_usage to never bypasses the fast-truncate history-store guard from WT-10013

XMLWordPrintableJSON

    • Type: Task
    • Resolution: Fixed
    • Priority: Major - P3
    • WT12.0.0, 9.1.0-rc0
    • Affects Version/s: None
    • Component/s: Truncate
    • None
    • Storage Engines, Storage Engines - Transactions
    • 0.011
    • SE Transactions - 2026-09-11
    • 1

      Summary

      WT-10013 added a guard in _wti_delete_page() (bt_delete.c) that limits an untimestamped fast truncate to pages whose content is already globally visible, specifically to avoid leaving stale, real-timestamped rows behind in the history store. That guard, and the commit-time ordered-timestamp-usage check in _wt_txn_timestamp_usage_check(), are both keyed off the table's current write_timestamp_usage policy (dhandle->ts_flags).

      WT_SESSION::alter can change write_timestamp_usage on a table at any time, including after the table has already accumulated real, timestamped history on disk and in the history store. Alter only rewrites metadata – it never inspects or rewrites the table's existing content. Once a table previously using timestamps is altered to write_timestamp_usage=never, _wt_txn_timestamp_usage_check() skips its ordered-consistency branch entirely (the WT_DHANDLE_TS_NEVER branch returns based only on whether the current commit carries a timestamp, never consulting the page's prior durable timestamp). An ordinary transaction – no no_timestamp=true needed, since that flag only exists as an escape hatch for ordered tables – can then fast-truncate a range containing genuinely timestamped, still-relevant history-store content. The commit succeeds, the fast path runs, and the per-key history-store clear (wti_rec_hs_delete_key(), normally reached via _rec_hs_handle_oldest_tombstone() for an ordinary no-timestamp tombstone) never executes, because fast truncate never visits individual keys.

      The result is a permanently orphaned, real-timestamped history-store row: exactly the defect WT-10013 was filed to prevent, reached through a path WT-10013's own fix does not cover.

      Reproduction

      1. Create a table with the default write_timestamp_usage (ordered).
      2. Write two versions of the same keys under real commit timestamps (e.g. 10, then 20).
      3. Checkpoint and evict so the superseded value is pushed into the history store under a real [10,20) window, and the newer value is on disk.
      4. session.alter(uri, 'write_timestamp_usage=never').
      5. Begin an ordinary transaction (no timestamp), truncate the range covering those keys, commit. The commit succeeds and the fast path is used (rec_page_delete_fast increments) with no history-store clearing (cache_hs_key_truncate stays flat).
      6. Checkpoint again so the now-globally-visible page_del gets discarded.
      7. The [10,20) history-store row is still present.

      A regression test reproducing this (test_truncate35.py) is attached in the linked PR. As a control, removing the alter step from the same test correctly fails the commit with EINVAL, confirming the alter step is what opens the gap rather than a general weakness in the test.

      Notes

      • This is not a regression introduced by WT-18300 (fast-truncate/disagg proxy-cell fix); WT-18300's changes don't touch this path. The gap predates it and remains open on develop.
      • Fix is not yet designed. Candidate directions: make the WT-10013 visibility gate in bt_delete.c unconditional on WT_TXN_TS_NOT_SET (currently it only engages when the transaction explicitly requests no_timestamp=true, which an ordinary "never" table commit does not do), and/or make the commit-time check consult the page's actual prior durable timestamp regardless of the table's current policy.

            Assignee:
            Chenhao Qu
            Reporter:
            Chenhao Qu
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: