-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: Layered Tables, Transactions
-
Storage Engines - Foundations
-
376.021
-
None
-
None
Issue Summary
When using layered cursors with prepared transactions, a fresh cursor opened on a layered table returns WT_PREPARE_CONFLICT immediately instead of returning the visible key from the stable table. Specifically, if key '1' is committed in the stable table and key '2' is prepared (but not committed) in the ingest table, calling next() on the cursor returns WT_PREPARE_CONFLICT rather than key '1'.
Context
- Table setup: layered table with key '1' in the stable table and key '2' in the ingest table (prepared, not committed).
- Test case: Opening a fresh cursor and calling next().
- Expected behavior: The cursor should return key '1' (from the stable table), and only return WT_PREPARE_CONFLICT when iterating to the prepared key.
- Actual behavior: The cursor immediately returns WT_PREPARE_CONFLICT.
- Analysis: This appears to be due to the layered cursor's internal logic, where encountering a prepared key in the ingest table causes an immediate conflict, preventing access to visible keys in the stable table. An ASC cursor does not exhibit this behavior in the same scenario.
Python Test Example
@disagg_test_class class test_layered_prepare_iterate_diff(wttest.WiredTigerTestCase): conn_base_config = 'precise_checkpoint=true,preserve_prepared=true,' conn_config = conn_base_config + 'disaggregated=(role="leader")' def test_layered(self): # Write '1' on the leader uri = 'table:lpid_layered' self.session.create(uri, 'key_format=S,value_format=S,block_manager=disagg,type=layered') with self.transaction(commit_timestamp=100): self.session.open_cursor(uri)['1'] = '1' self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(200)) # Pick up the stable table on the follower, then prepare '2' there. self.session.checkpoint() # Open a follower follow = self.wiredtiger_open('follower', self.extensionsConfig() + ',create,' + self.conn_base_config + 'disaggregated=(role="follower")') self.disagg_advance_checkpoint(follow) follower_session = follow.open_session() # Prepare an insert of key '2' on the given session. follower_session.begin_transaction() follower_session.open_cursor(uri)['2'] = '2' follower_session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(300) + ',prepared_id=' + self.prepared_id_str(1)) # Call next() on a fresh cursor. reader = follow.open_session() cursor = reader.open_cursor(uri) cursor.next() # Expected to return 1, but returns WT_PREPARE_CONFLICT
Proposed Solution
Review and update the layered cursor logic so that encountering a prepared key in the ingest table does not immediately return WT_PREPARE_CONFLICT if there are visible keys in the stable table. The cursor should return the visible stable key first, and only return WT_PREPARE_CONFLICT when iterating to the prepared key.
Original Slack thread: https://mongodb.slack.com/archives/C0520RCK0C9/p1781161878203469
This ticket was generated by AI from a Slack thread.