def test_rollback_to_stable(self): nrows = 1000 # Create a table without logging. uri = "table:rollback_to_stable07" ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format="S", config='log=(enabled=false)') ds.populate() # Pin oldest and stable to timestamp 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) value_a = "aaaaa" * 100 value_b = "bbbbb" * 100 value_c = "ccccc" * 100 value_d = "ddddd" * 100 # Perform several updates. self.large_updates(uri, value_d, ds, nrows, self.prepare, 20) self.large_updates(uri, value_c, ds, nrows, self.prepare, 30) self.large_updates(uri, value_b, ds, nrows, self.prepare, 40) self.large_updates(uri, value_a, ds, nrows, self.prepare, 50) # Verify data is visible and correct. self.check(value_d, uri, nrows, 20) self.check(value_c, uri, nrows, 30) self.check(value_b, uri, nrows, 40) self.check(value_a, uri, nrows, 50) # Pin stable to timestamp 50 if prepare otherwise 40. if self.prepare: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(50)) else: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(40)) # Perform additional updates. self.large_updates(uri, value_b, ds, nrows, self.prepare, 60) self.large_updates(uri, value_c, ds, nrows, self.prepare, 70) self.large_updates(uri, value_d, ds, nrows, self.prepare, 80) # Checkpoint to ensure the data is flushed to disk. self.session.checkpoint() # Verify additional update data is visible and correct. self.check(value_b, uri, nrows, 60) self.check(value_c, uri, nrows, 70) self.check(value_d, uri, nrows, 80) # Simulate a server crash and restart. simulate_crash_restart(self, ".", "RESTART") # Check that the correct data is seen at and after the stable timestamp. self.check(value_b, uri, nrows, 40) self.check(value_b, uri, nrows, 80) self.check(value_c, uri, nrows, 30) self.check(value_d, uri, nrows, 20) stat_cursor = self.session.open_cursor('statistics:', None, None) calls = stat_cursor[stat.conn.txn_rts][2] hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] keys_restored = stat_cursor[stat.conn.txn_rts_keys_restored][2] pages_visited = stat_cursor[stat.conn.txn_rts_pages_visited][2] upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] stat_cursor.close() self.assertEqual(calls, 0) self.assertEqual(keys_removed, 0) self.assertEqual(keys_restored, 0) self.assertGreaterEqual(upd_aborted, 0) self.assertGreater(pages_visited, 0) self.assertGreaterEqual(hs_removed, nrows * 4) # Simulate another server crash and restart. simulate_crash_restart(self, "RESTART", "RESTART2") # Check that the correct data is seen at and after the stable timestamp. self.check(value_b, uri, nrows, 40) self.check(value_b, uri, nrows, 80) self.check(value_c, uri, nrows, 30) self.check(value_d, uri, nrows, 20) stat_cursor = self.session.open_cursor('statistics:', None, None) calls = stat_cursor[stat.conn.txn_rts][2] hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] keys_restored = stat_cursor[stat.conn.txn_rts_keys_restored][2] pages_visited = stat_cursor[stat.conn.txn_rts_pages_visited][2] upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] stat_cursor.close() self.assertEqual(calls, 0) self.assertEqual(keys_removed, 0) self.assertEqual(keys_restored, 0) self.assertGreaterEqual(pages_visited, 0) self.assertEqual(upd_aborted, 0) self.assertEqual(hs_removed, 0)
def test_rollback_to_stable36(self): nrows = 1000 # Create a table. uri = "table:rollback_to_stable36" ds = SimpleDataSet( self, uri, 0, key_format=self.key_format, value_format=self.value_format, config=self.extraconfig) ds.populate() if self.value_format == '8t': value_a = 97 else: value_a = "aaaaa" * 100 # Pin oldest and stable timestamps to 1. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1) + ',stable_timestamp=' + self.timestamp_str(1)) # Write some baseline data to table 1 at time 10. cursor1 = self.session.open_cursor(ds.uri) self.session.begin_transaction() for i in range(1, nrows + 1): cursor1[ds.key(i)] = value_a if i % 109 == 0: self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(10)) self.session.begin_transaction() self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(10)) cursor1.close() # Mark it stable. self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(10)) # Reopen the connection so nothing is in memory and we can fast-truncate. self.reopen_conn() # Truncate most of the table. # Commit the truncate at time 20. self.session.begin_transaction() err = self.truncate(ds.uri, ds.key, 50, nrows - 50) self.assertEqual(err, 0) self.session.commit_transaction('commit_timestamp=' + self.timestamp_str(20)) # Make sure we did at least one fast-delete. For columns, there's no fast-delete # support (yet) so assert we didn't. stat_cursor = self.session.open_cursor('statistics:', None, None) fastdelete_pages = stat_cursor[stat.conn.rec_page_delete_fast][2] if self.key_format == 'r' or self.trunc_with_remove: self.assertEqual(fastdelete_pages, 0) else: self.assertGreater(fastdelete_pages, 0) stat_cursor.close() # Checkpoint. self.session.checkpoint() # Roll back, either via crashing or by explicit RTS. if self.crash: simulate_crash_restart(self, ".", "RESTART") else: self.conn.rollback_to_stable() # Currently rolling back a fast-truncate works by instantiating the pages and # rolling back the instantiated updates, so we should see some page instantiations. # (But again, not for columns, yet.) stat_cursor = self.session.open_cursor('statistics:', None, None) read_deleted = stat_cursor[stat.conn.cache_read_deleted][2] if self.key_format == 'r' or self.trunc_with_remove: self.assertEqual(read_deleted, 0) else: self.assertGreater(read_deleted, 0) stat_cursor.close() # Validate the data; we should see all of it, since the truncations weren't stable. self.check(ds, value_a, nrows, 15) self.check(ds, value_a, nrows, 25)
def test_rollback_to_stable(self): nrows = 1000 # Create a table. self.pr("create/populate tables") uri_1 = "table:rollback_to_stable10_1" ds_1 = SimpleDataSet(self, uri_1, 0, key_format=self.key_format, value_format=self.value_format) ds_1.populate() # Create another table. uri_2 = "table:rollback_to_stable10_2" ds_2 = SimpleDataSet(self, uri_2, 0, key_format=self.key_format, value_format=self.value_format) ds_2.populate() if self.value_format == '8t': value_a = 97 value_b = 98 value_c = 99 value_d = 100 value_e = 101 value_f = 102 else: value_a = "aaaaa" * 100 value_b = "bbbbb" * 100 value_c = "ccccc" * 100 value_d = "ddddd" * 100 value_e = "eeeee" * 100 value_f = "fffff" * 100 # Pin oldest and stable to timestamp 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) # Perform several updates. self.pr("large updates") self.large_updates(uri_1, value_d, ds_1, nrows, self.prepare, 20) self.large_updates(uri_1, value_c, ds_1, nrows, self.prepare, 30) self.large_updates(uri_1, value_b, ds_1, nrows, self.prepare, 40) self.large_updates(uri_1, value_a, ds_1, nrows, self.prepare, 50) self.large_updates(uri_2, value_d, ds_2, nrows, self.prepare, 20) self.large_updates(uri_2, value_c, ds_2, nrows, self.prepare, 30) self.large_updates(uri_2, value_b, ds_2, nrows, self.prepare, 40) self.large_updates(uri_2, value_a, ds_2, nrows, self.prepare, 50) # Verify data is visible and correct. self.check(value_d, uri_1, nrows, None, 21 if self.prepare else 20) self.check(value_c, uri_1, nrows, None, 31 if self.prepare else 30) self.check(value_b, uri_1, nrows, None, 41 if self.prepare else 40) self.check(value_a, uri_1, nrows, None, 51 if self.prepare else 50) self.check(value_d, uri_2, nrows, None, 21 if self.prepare else 20) self.check(value_c, uri_2, nrows, None, 31 if self.prepare else 30) self.check(value_b, uri_2, nrows, None, 41 if self.prepare else 40) self.check(value_a, uri_2, nrows, None, 51 if self.prepare else 50) # Pin stable to timestamp 60 if prepare otherwise 50. if self.prepare: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(60)) else: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(50)) # Create a checkpoint thread done = threading.Event() ckpt = checkpoint_thread(self.conn, done) try: self.pr("start checkpoint") ckpt.start() # Sleep for sometime so that checkpoint starts. time.sleep(2) # Perform several updates in parallel with checkpoint. # Rollbacks may occur when checkpoint is running, so retry as needed. self.pr("updates") self.retry_rollback( 'update ds1, e', None, lambda: self.large_updates( uri_1, value_e, ds_1, nrows, self.prepare, 70)) self.retry_rollback( 'update ds2, e', None, lambda: self.large_updates( uri_2, value_e, ds_2, nrows, self.prepare, 70)) self.evict_cursor(uri_1, nrows, value_e) self.evict_cursor(uri_2, nrows, value_e) self.retry_rollback( 'update ds1, f', None, lambda: self.large_updates( uri_1, value_f, ds_1, nrows, self.prepare, 80)) self.retry_rollback( 'update ds2, f', None, lambda: self.large_updates( uri_2, value_f, ds_2, nrows, self.prepare, 80)) self.evict_cursor(uri_1, nrows, value_f) self.evict_cursor(uri_2, nrows, value_f) finally: done.set() ckpt.join() # Simulate a server crash and restart. self.pr("restart") simulate_crash_restart(self, ".", "RESTART") self.pr("restart complete") # Check that the correct data is seen at and after the stable timestamp. self.check(value_a, uri_1, nrows, None, 50) self.check(value_a, uri_1, nrows, None, 80) self.check(value_b, uri_1, nrows, None, 40) self.check(value_c, uri_1, nrows, None, 30) self.check(value_d, uri_1, nrows, None, 20) # Check that the correct data is seen at and after the stable timestamp. self.check(value_c, uri_2, nrows, None, 30) self.check(value_a, uri_2, nrows, None, 50) self.check(value_a, uri_2, nrows, None, 80) self.check(value_b, uri_2, nrows, None, 40) self.check(value_d, uri_2, nrows, None, 20) stat_cursor = self.session.open_cursor('statistics:', None, None) calls = stat_cursor[stat.conn.txn_rts][2] hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] hs_sweep = stat_cursor[stat.conn.txn_rts_sweep_hs_keys][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] keys_restored = stat_cursor[stat.conn.txn_rts_keys_restored][2] pages_visited = stat_cursor[stat.conn.txn_rts_pages_visited][2] upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] stat_cursor.close() self.assertEqual(calls, 0) self.assertEqual(keys_removed, 0) self.assertEqual(keys_restored, 0) self.assertGreaterEqual(upd_aborted, 0) self.assertGreater(pages_visited, 0) self.assertGreaterEqual(hs_removed, 0) self.assertGreater(hs_sweep, 0)
def test_rollback_to_stable(self): nrows = 1000 # Create a table without logging. uri = "table:rollback_to_stable23" ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format=self.value_format) ds.populate() # Pin oldest and stable to timestamp 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) value_a = "aaaaa" * 100 value_modQ = mod_val(value_a, 'Q', 0) value_modR = mod_val(value_modQ, 'R', 1) value_modS = mod_val(value_modR, 'S', 2) value_modT = mod_val(value_modS, 'T', 3) # Perform a combination of modifies and updates. self.large_updates(uri, value_a, ds, nrows, self.prepare, 20) self.large_modifies(uri, 'Q', ds, 0, 1, nrows, self.prepare, 30) self.large_modifies(uri, 'R', ds, 1, 1, nrows, self.prepare, 40) self.large_modifies(uri, 'S', ds, 2, 1, nrows, self.prepare, 50) self.large_modifies(uri, 'T', ds, 3, 1, nrows, self.prepare, 60) # Verify data is visible and correct. self.check(value_a, uri, nrows, None, 20) self.check(value_modQ, uri, nrows, None, 30) self.check(value_modR, uri, nrows, None, 40) self.check(value_modS, uri, nrows, None, 50) self.check(value_modT, uri, nrows, None, 60) # Pin stable to timestamp 60 if prepare otherwise 50. if self.prepare: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(60)) else: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(50)) # Checkpoint the database. self.session.checkpoint() # Simulate a server crash and restart. simulate_crash_restart(self, ".", "RESTART") # Check that the correct data is seen at and after the stable timestamp. self.check_with_set_key(ds, value_a, uri, nrows, 20) self.check_with_set_key(ds, value_modQ, uri, nrows, 30) self.check_with_set_key(ds, value_modR, uri, nrows, 40) self.check_with_set_key(ds, value_modS, uri, nrows, 50) stat_cursor = self.session.open_cursor('statistics:', None, None) hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] hs_restore_updates = stat_cursor[ stat.conn.txn_rts_hs_restore_updates][2] upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] stat_cursor.close() self.assertEqual(hs_restore_updates, nrows) if self.prepare: self.assertGreaterEqual(upd_aborted, 0) else: self.assertEqual(upd_aborted, 0) self.assertGreaterEqual(hs_removed, nrows)
def test_checkpoint_snapshot_with_txnid_and_timestamp(self): ds = SimpleDataSet(self, self.uri, 0, key_format="S", value_format="S", config='log=(enabled=false)') ds.populate() valuea = "aaaaa" * 100 valueb = "bbbbb" * 100 # Pin oldest and stable timestamps to 10. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(10) + ',stable_timestamp=' + timestamp_str(10)) session1 = self.conn.open_session() session1.begin_transaction() self.large_updates(self.uri, valuea, ds, self.nrows, 20) self.check(valuea, self.uri, self.nrows, 20) session2 = self.conn.open_session() session2.begin_transaction() cursor2 = session2.open_cursor(self.uri) for i in range((self.nrows + 1), (self.nrows * 2) + 1): cursor2.set_key(ds.key(i)) cursor2.set_value(valuea) self.assertEqual(cursor2.insert(), 0) session1.timestamp_transaction('commit_timestamp=' + timestamp_str(30)) # Set stable timestamp to 40 self.conn.set_timestamp('stable_timestamp=' + timestamp_str(40)) # Create a checkpoint thread done = threading.Event() ckpt = checkpoint_thread(self.conn, done) try: ckpt.start() # Sleep for sometime so that checkpoint starts before committing last transaction. time.sleep(2) session2.commit_transaction() finally: done.set() ckpt.join() session1.rollback_transaction() #Simulate a crash by copying to a new directory(RESTART). simulate_crash_restart(self, ".", "RESTART") # Check the table contains the last checkpointed value. self.check(valuea, self.uri, self.nrows, 30) stat_cursor = self.session.open_cursor('statistics:', None, None) inconsistent_ckpt = stat_cursor[stat.conn.txn_rts_inconsistent_ckpt][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] stat_cursor.close() self.assertGreater(inconsistent_ckpt, 0) self.assertGreaterEqual(keys_removed, 0) simulate_crash_restart(self, "RESTART", "RESTART2") # Check the table contains the last checkpointed value. self.check(valuea, self.uri, self.nrows, 30) stat_cursor = self.session.open_cursor('statistics:', None, None) inconsistent_ckpt = stat_cursor[stat.conn.txn_rts_inconsistent_ckpt][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] stat_cursor.close() self.assertGreaterEqual(inconsistent_ckpt, 0) self.assertEqual(keys_removed, 0)
def test_rollback_to_stable_no_history(self): nrows = 1000 # Prepare transactions for column store table is not yet supported. if self.key_format == 'r': self.skipTest( 'Prepare transactions for column store table is not yet supported' ) # Create a table without logging. uri = "table:rollback_to_stable19" ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format="S", config='log=(enabled=false)') ds.populate() # Pin oldest and stable timestamps to 10. self.conn.set_timestamp('oldest_timestamp=' + timestamp_str(10) + ',stable_timestamp=' + timestamp_str(10)) valuea = "aaaaa" * 100 # Perform several updates and removes. s = self.conn.open_session() cursor = s.open_cursor(uri) s.begin_transaction() for i in range(1, nrows + 1): cursor[ds.key(i)] = valuea cursor.set_key(i) cursor.remove() cursor.close() s.prepare_transaction('prepare_timestamp=' + timestamp_str(20)) # Configure debug behavior on a cursor to evict the page positioned on when the reset API is used. evict_cursor = self.session.open_cursor(uri, None, "debug=(release_evict)") # Search for the key so we position our cursor on the page that we want to evict. self.session.begin_transaction("ignore_prepare = true") evict_cursor.set_key(1) evict_cursor.search() evict_cursor.reset() evict_cursor.close() self.session.commit_transaction() # Pin stable timestamp to 20. self.conn.set_timestamp('stable_timestamp=' + timestamp_str(20)) if not self.in_memory: self.session.checkpoint() if not self.in_memory: if self.crash: simulate_crash_restart(self, ".", "RESTART") else: # Close and reopen the connection self.reopen_conn() else: self.conn.rollback_to_stable() s.rollback_transaction() # Verify data is not visible. self.check(valuea, uri, 0, 20) self.check(valuea, uri, 0, 30) stat_cursor = self.session.open_cursor('statistics:', None, None) upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] self.assertGreater(upd_aborted, 0) self.assertGreater(keys_removed, 0)
def test_rollback_to_stable(self): nrows = 10 # Create a table without logging. uri = "table:rollback_to_stable26" ds = SimpleDataSet( self, uri, 0, key_format=self.key_format, value_format=self.value_format, config='log=(enabled=false)') ds.populate() if self.value_format == '8t': value_a = 97 value_b = 98 value_c = 99 value_d = 100 value_e = 101 else: value_a = "aaaaa" * 100 value_b = "bbbbb" * 100 value_c = "ccccc" * 100 value_d = "ddddd" * 100 value_e = "eeeee" * 100 # Pin oldest and stable to timestamp 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) self.large_updates(uri, value_a, ds, nrows, False, 20) self.large_updates(uri, value_b, ds, nrows, False, 30) if self.hs_remove: self.large_removes(uri, ds, nrows, False, 40) prepare_session = self.conn.open_session() prepare_session.begin_transaction() cursor = prepare_session.open_cursor(uri) for i in range (1, nrows + 1): cursor[i] = value_c if self.prepare_remove: cursor.set_key(i) self.assertEqual(cursor.remove(), 0) cursor.close() prepare_session.prepare_transaction('prepare_timestamp=' + self.timestamp_str(50)) # Verify data is visible and correct. self.check(value_a, uri, nrows, None, 20) self.check(value_b, uri, nrows, None, 30) self.evict_cursor(uri, nrows) # Pin stable to timestamp 40. self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(40)) # Create a checkpoint thread done = threading.Event() ckpt = checkpoint_thread(self.conn, done) try: ckpt.start() # Sleep for sometime so that checkpoint starts before committing last transaction. time.sleep(5) prepare_session.rollback_transaction() finally: done.set() ckpt.join() self.large_updates(uri, value_d, ds, nrows, False, 60) # Check that the correct data. self.check(value_a, uri, nrows, None, 20) self.check(value_b, uri, nrows, None, 30) self.check(value_d, uri, nrows, None, 60) # Simulate a server crash and restart. simulate_crash_restart(self, ".", "RESTART") stat_cursor = self.session.open_cursor('statistics:', None, None) hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] hs_restore_updates = stat_cursor[stat.conn.txn_rts_hs_restore_updates][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] stat_cursor.close() self.assertEqual(keys_removed, 0) self.assertEqual(hs_restore_updates, nrows) self.assertEqual(hs_removed, nrows) # Check that the correct data. self.check(value_a, uri, nrows, None, 20) self.check(value_b, uri, nrows, None, 30) self.large_updates(uri, value_e, ds, nrows, False, 60) self.evict_cursor(uri, nrows) # Check that the correct data. self.check(value_a, uri, nrows, None, 20) self.check(value_b, uri, nrows, None, 30) self.check(value_e, uri, nrows, None, 60)
def test_checkpoint(self): uri = 'table:checkpoint14' nrows = 10000 # Create a table. ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format=self.value_format, config=self.extraconfig) ds.populate() if self.value_format == '8t': nrows *= 5 value_a = 97 value_b = 98 value_c = 99 else: value_a = "aaaaa" * 100 value_b = "bbbbb" * 100 value_c = "ccccc" * 100 # Write some baseline data. self.large_updates(uri, ds, nrows, value_a) # Write this data out now so we aren't waiting for it while trying to # race with the later data. self.session.checkpoint() # Write some more data, and hold the transaction open. session2 = self.conn.open_session() cursor2 = session2.open_cursor(uri) session2.begin_transaction() for i in range(1, nrows + 1): cursor2[ds.key(i)] = value_b # Checkpoint in the background. done = threading.Event() if self.first_checkpoint is None: ckpt = checkpoint_thread(self.conn, done) else: ckpt = named_checkpoint_thread(self.conn, done, self.first_checkpoint) try: ckpt.start() # Wait for checkpoint to start before committing. ckpt_started = 0 while not ckpt_started: stat_cursor = self.session.open_cursor('statistics:', None, None) ckpt_started = stat_cursor[stat.conn.txn_checkpoint_running][2] stat_cursor.close() time.sleep(1) session2.commit_transaction() finally: done.set() ckpt.join() # Rinse and repeat. session2.begin_transaction() for i in range(1, nrows + 1): cursor2[ds.key(i)] = value_c # Checkpoint in the background. done = threading.Event() if self.second_checkpoint is None: ckpt = checkpoint_thread(self.conn, done) else: ckpt = named_checkpoint_thread(self.conn, done, self.second_checkpoint) try: ckpt.start() # Sleep a bit so that checkpoint starts before committing last transaction. time.sleep(2) session2.commit_transaction() finally: done.set() ckpt.join() # Other tests check for whether the visibility of a partially-written transaction # is handled correctly. Here we're interested in whether the visibility mechanism # is using the right snapshot for the checkpoint we're reading. So insist that we # not see the value_b transaction in the first checkpoint, or the value_c transaction # in the second checkpoint. If test machine lag causes either transaction to commit # before the checkpoint starts, we'll see value_b in the first checkpoint and/or # value_c in the second. But also, if we end up using the second checkpoint's snapshot # for the first checkpoint, we'll see value_b. So if this happens more than once in a # blue moon we should probably strengthen the test so we can more reliably distinguish # the cases, probably by doing a third transaction/checkpoint pair. # # If we end up using the first checkpoint's snapshot for reading the second checkpoint, # we'll most likely see no data at all; that would be a serious failure if it happened. # Read the checkpoints. self.check(ds, self.first_checkpoint, nrows, value_a) self.check(ds, self.second_checkpoint, nrows, value_b) # If we haven't died yet, pretend to crash, and run RTS to see if the # (second) checkpoint was inconsistent. Unfortunately we can't readily # check on both. simulate_crash_restart(self, ".", "RESTART") # Make sure we did get an inconsistent checkpoint. stat_cursor = self.session.open_cursor('statistics:', None, None) inconsistent_ckpt = stat_cursor[stat.conn.txn_rts_inconsistent_ckpt][2] stat_cursor.close() self.assertGreater(inconsistent_ckpt, 0)
def test_rollback_to_stable(self): nrows = 100 # Create a table without logging. self.pr("create/populate table") uri = "table:rollback_to_stable14" ds = SimpleDataSet( self, uri, 0, key_format=self.key_format, value_format=self.value_format, config='log=(enabled=false)') ds.populate() # Pin oldest and stable to timestamp 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) value_a = "aaaaa" * 100 value_modQ = mod_val(value_a, 'Q', 0) value_modR = mod_val(value_modQ, 'R', 1) value_modS = mod_val(value_modR, 'S', 2) value_modT = mod_val(value_modS, 'T', 3) value_modW = mod_val(value_modT, 'W', 4) value_modX = mod_val(value_modW, 'X', 5) value_modY = mod_val(value_modX, 'Y', 6) value_modZ = mod_val(value_modY, 'Z', 7) # Perform a combination of modifies and updates. self.pr("large updates and modifies") self.large_updates(uri, value_a, ds, nrows, self.prepare, 20) self.large_modifies(uri, 'Q', ds, 0, 1, nrows, self.prepare, 30) self.large_modifies(uri, 'R', ds, 1, 1, nrows, self.prepare, 40) self.large_modifies(uri, 'S', ds, 2, 1, nrows, self.prepare, 50) self.large_modifies(uri, 'T', ds, 3, 1, nrows, self.prepare, 60) # Verify data is visible and correct. self.check(value_a, uri, nrows, None, 20) self.check(value_modQ, uri, nrows, None, 30) self.check(value_modR, uri, nrows, None, 40) self.check(value_modS, uri, nrows, None, 50) self.check(value_modT, uri, nrows, None, 60) # Pin stable to timestamp 60 if prepare otherwise 50. if self.prepare: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(60)) else: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(50)) # Create a checkpoint thread done = threading.Event() ckpt = checkpoint_thread(self.conn, done) try: self.pr("start checkpoint") ckpt.start() # Sleep for sometime so that checkpoint starts. time.sleep(2) # Perform several modifies in parallel with checkpoint. # Rollbacks may occur when checkpoint is running, so retry as needed. self.pr("modifies") self.retry_rollback('modify ds1, W', None, lambda: self.large_modifies(uri, 'W', ds, 4, 1, nrows, self.prepare, 70)) self.evict_cursor(uri, nrows, value_modW) self.retry_rollback('modify ds1, X', None, lambda: self.large_modifies(uri, 'X', ds, 5, 1, nrows, self.prepare, 80)) self.evict_cursor(uri, nrows, value_modX) self.retry_rollback('modify ds1, Y', None, lambda: self.large_modifies(uri, 'Y', ds, 6, 1, nrows, self.prepare, 90)) self.evict_cursor(uri, nrows, value_modY) self.retry_rollback('modify ds1, Z', None, lambda: self.large_modifies(uri, 'Z', ds, 7, 1, nrows, self.prepare, 100)) self.evict_cursor(uri, nrows, value_modZ) finally: done.set() ckpt.join() # Simulate a server crash and restart. self.pr("restart") simulate_crash_restart(self, ".", "RESTART") self.pr("restart complete") stat_cursor = self.session.open_cursor('statistics:', None, None) calls = stat_cursor[stat.conn.txn_rts][2] hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] hs_sweep = stat_cursor[stat.conn.txn_rts_sweep_hs_keys][2] hs_restore_updates = stat_cursor[stat.conn.txn_rts_hs_restore_updates][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] keys_restored = stat_cursor[stat.conn.txn_rts_keys_restored][2] pages_visited = stat_cursor[stat.conn.txn_rts_pages_visited][2] upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] stat_cursor.close() self.assertEqual(calls, 0) self.assertEqual(keys_removed, 0) self.assertEqual(hs_restore_updates, nrows) self.assertEqual(keys_restored, 0) if self.prepare: self.assertGreaterEqual(upd_aborted, 0) else: self.assertEqual(upd_aborted, 0) self.assertGreater(pages_visited, 0) self.assertGreaterEqual(hs_removed, nrows) self.assertGreaterEqual(hs_sweep, 0) # Check that the correct data is seen at and after the stable timestamp. self.check(value_a, uri, nrows, None, 20) self.check(value_modQ, uri, nrows, None, 30) self.check(value_modR, uri, nrows, None, 40) self.check(value_modS, uri, nrows, None, 50) # The test may output the following message in eviction under cache pressure. Ignore that. self.ignoreStdoutPatternIfExists("oldest pinned transaction ID rolled back for eviction")
def test_rollback_to_stable(self): nrows = 1 # Create a table without logging. uri = "table:rollback_to_stable11" ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format=self.value_format) ds.populate() if self.value_format == '8t': value_a = 97 value_b = 98 value_c = 99 value_d = 100 else: value_a = "aaaaa" * 100 value_b = "bbbbb" * 100 value_c = "ccccc" * 100 value_d = "ddddd" * 100 # Pin oldest and stable to timestamp 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) # Perform several updates. self.large_updates(uri, value_a, ds, nrows, self.prepare, 20) self.large_updates(uri, value_a, ds, nrows, self.prepare, 20) self.large_updates(uri, value_a, ds, nrows, self.prepare, 20) self.large_updates(uri, value_b, ds, nrows, self.prepare, 20) # Verify data is visible and correct. self.check(value_b, uri, nrows, None, 20) # Pin stable to timestamp 28 if prepare otherwise 20. # large_updates() prepares at 1 before the timestamp passed (so 29) # and this is required to be strictly greater than (not >=) stable. if self.prepare: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(28)) else: self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(20)) # Checkpoint to ensure that all the updates are flushed to disk. self.session.checkpoint() # Simulate a server crash and restart. simulate_crash_restart(self, ".", "RESTART") # Check that the correct data is seen at and after the stable timestamp. self.check(value_b, uri, nrows, None, 20) # Perform several updates. self.large_updates(uri, value_c, ds, nrows, self.prepare, 30) self.large_updates(uri, value_c, ds, nrows, self.prepare, 30) self.large_updates(uri, value_c, ds, nrows, self.prepare, 30) self.large_updates(uri, value_d, ds, nrows, self.prepare, 30) # Verify data is visible and correct. self.check(value_d, uri, nrows, None, 30) # Checkpoint to ensure that all the updates are flushed to disk. self.session.checkpoint() # Simulate a server crash and restart. simulate_crash_restart(self, "RESTART", "RESTART2") # Check that the correct data is seen at and after the stable timestamp. self.check(value_b, uri, nrows, None, 20) self.check(value_b, uri, nrows, None, 40) stat_cursor = self.session.open_cursor('statistics:', None, None) calls = stat_cursor[stat.conn.txn_rts][2] hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] hs_sweep = stat_cursor[stat.conn.txn_rts_sweep_hs_keys][2] keys_removed = stat_cursor[stat.conn.txn_rts_keys_removed][2] keys_restored = stat_cursor[stat.conn.txn_rts_keys_restored][2] pages_visited = stat_cursor[stat.conn.txn_rts_pages_visited][2] upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] stat_cursor.close() self.assertEqual(calls, 0) self.assertEqual(keys_removed, 0) self.assertEqual(keys_restored, 0) self.assertEqual(upd_aborted, 0) self.assertGreater(pages_visited, 0) self.assertEqual(hs_removed, 4) self.assertEqual(hs_sweep, 0)
def test_rollback_to_stable(self): uri = 'table:test_rollback_to_stable29' nrows = 1000 if self.value_format == '8t': value_a = 97 value_b = 98 value_c = 99 value_d = 100 else: value_a = 'a' * 100 value_b = 'b' * 100 value_c = 'c' * 100 value_d = 'd' * 100 # Create our table. ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format=self.value_format) ds.populate() # Pin oldest and stable to timestamp 1. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(1) + ',stable_timestamp=' + self.timestamp_str(1)) self.large_updates(uri, value_a, ds, nrows, False, 10) # Pin oldest and stable to timestamp 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) old_reader_session = self.conn.open_session() old_reader_cursor = old_reader_session.open_cursor(uri) old_reader_session.begin_transaction('read_timestamp=' + self.timestamp_str(10)) self.large_removes(uri, ds, nrows, False, 30) self.large_updates(uri, value_b, ds, nrows, False, 40) self.check(value_b, uri, nrows, None, 40) self.session.checkpoint() self.evict_cursor(uri, nrows, value_b) self.large_updates(uri, value_c, ds, nrows, False, 50) self.check(value_c, uri, nrows, None, 50) self.evict_cursor(uri, nrows, value_c) # Insert update without a timestamp. self.large_updates(uri, value_d, ds, nrows, False, 0) self.check(value_d, uri, nrows, None, 10) self.check(value_d, uri, nrows, None, 40) self.check(value_d, uri, nrows, None, 50) self.check(value_d, uri, nrows, None, 20) self.session.checkpoint() # Simulate a crash by copying to a new directory(RESTART). simulate_crash_restart(self, ".", "RESTART") self.check(value_d, uri, nrows, None, 10) stat_cursor = self.session.open_cursor('statistics:', None, None) hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] stat_cursor.close() self.assertGreaterEqual(hs_removed, 0)
def test_rollback_to_stable_with_history(self): nrows = 1000 # Create a table without logging. uri = "table:rollback_to_stable19" ds = SimpleDataSet(self, uri, 0, key_format=self.key_format, value_format="S", config='log=(enabled=false)') ds.populate() # Pin oldest and stable timestamps to 10. self.conn.set_timestamp('oldest_timestamp=' + self.timestamp_str(10) + ',stable_timestamp=' + self.timestamp_str(10)) valuea = "aaaaa" * 100 valueb = "bbbbb" * 100 # Perform several updates. self.large_updates(uri, valuea, ds, nrows, 0, 20) # Perform several removes. self.large_removes(uri, ds, nrows, 0, 30) # Perform several updates and removes. s = self.conn.open_session() cursor = s.open_cursor(uri) s.begin_transaction() for i in range(1, nrows + 1): cursor[ds.key(i)] = valueb cursor.set_key(i) cursor.remove() cursor.close() s.prepare_transaction('prepare_timestamp=' + self.timestamp_str(40)) # Configure debug behavior on a cursor to evict the page positioned on when the reset API is used. evict_cursor = self.session.open_cursor(uri, None, "debug=(release_evict)") # Search for the key so we position our cursor on the page that we want to evict. self.session.begin_transaction("ignore_prepare = true") evict_cursor.set_key(1) self.assertEquals(evict_cursor.search(), WT_NOTFOUND) evict_cursor.reset() evict_cursor.close() self.session.commit_transaction() # Search to make sure the data is not visible self.session.begin_transaction("ignore_prepare = true") cursor2 = self.session.open_cursor(uri) cursor2.set_key(1) self.assertEquals(cursor2.search(), WT_NOTFOUND) self.session.commit_transaction() cursor2.close() # Pin stable timestamp to 40. self.conn.set_timestamp('stable_timestamp=' + self.timestamp_str(40)) if not self.in_memory: self.session.checkpoint() if not self.in_memory: if self.crash: simulate_crash_restart(self, ".", "RESTART") else: # Close and reopen the connection self.reopen_conn() else: self.conn.rollback_to_stable() s.rollback_transaction() # Verify data. self.check(valuea, uri, nrows, 20) self.check(valuea, uri, 0, 30) self.check(valuea, uri, 0, 40) stat_cursor = self.session.open_cursor('statistics:', None, None) upd_aborted = stat_cursor[stat.conn.txn_rts_upd_aborted][2] hs_removed = stat_cursor[stat.conn.txn_rts_hs_removed][2] # After restart (not crash) the stats for the aborted updates and history store removed will be 0, # as the updates aborted and history store removed will occur during shutdown, and on startup there # will be no updates to be removed. if not self.in_memory: if self.crash: self.assertGreater(hs_removed, 0) else: self.assertEqual(hs_removed, 0) self.assertEqual(upd_aborted, 0) else: self.assertGreater(upd_aborted, 0)