Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 37 additions & 35 deletions src/query/storages/fuse/src/retry/commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,48 +153,50 @@ async fn try_rebuild_req(
if let Some(txn_begin_timestamp) =
txn_mgr.get_table_txn_begin_timestamp(latest_table.get_id())
{
let Some(latest_snapshot_timestamp) = latest_snapshot.timestamp() else {
return Err(ErrorCode::UnresolvableConflict(format!(
if let Some(latest_snapshot) = latest_snapshot.as_ref() {
let Some(latest_snapshot_timestamp) = latest_snapshot.timestamp else {
return Err(ErrorCode::UnresolvableConflict(format!(
"Table {} snapshot lacks required timestamp. This table was created with a significantly outdated version that is no longer directly supported by the current version and requires migration.
Please contact us at https://www.databend.com/contact-us/ or email [email protected]",
tid
)));
};

// By enforcing txn_begin_timestamp >= latest_snapshot_timestamp, we ensure that
// vacuum operations won't remove table data (segment, blocks, etc.) that newly
// created in the current active transaction.

// In the current transaction, all the newly created table data (segments, blocks, etc.)
// has timestamps that are greater than or equal to txn_begin_timestamp, but the
// final snapshot which contains those data (and is yet to be committed) may have a timestamp
// that is larger than txn_begin_timestamp.

// To maintain vacuum safety, we must ensure that if the latest snapshot's timestamp
// (latest_snapshot_timestamp) is larger than txn_begin_timestamp, we abort the transaction
// to prevent potential data loss during vacuum operations.

// Example:
// session1: session2: session3:
// begin;
// -- newly created table data
// -- timestamped as A
// insert into t values (1);
// -- new snapshot S's ts is B
// insert into t values (2);
// -- using S as gc root
// -- if B > A, then newly created table data
// -- in session1 will be purged
// call fuse_vacuum2('db', 't');
// -- while merging with S
// -- if A < B, this txn should abort
// commit;

if txn_begin_timestamp < latest_snapshot_timestamp {
return Err(ErrorCode::UnresolvableConflict(format!(
};

// By enforcing txn_begin_timestamp >= latest_snapshot_timestamp, we ensure that
// vacuum operations won't remove table data (segment, blocks, etc.) that newly
// created in the current active transaction.

// In the current transaction, all the newly created table data (segments, blocks, etc.)
// has timestamps that are greater than or equal to txn_begin_timestamp, but the
// final snapshot which contains those data (and is yet to be committed) may have a timestamp
// that is larger than txn_begin_timestamp.

// To maintain vacuum safety, we must ensure that if the latest snapshot's timestamp
// (latest_snapshot_timestamp) is larger than txn_begin_timestamp, we abort the transaction
// to prevent potential data loss during vacuum operations.

// Example:
// session1: session2: session3:
// begin;
// -- newly created table data
// -- timestamped as A
// insert into t values (1);
// -- new snapshot S's ts is B
// insert into t values (2);
// -- using S as gc root
// -- if B > A, then newly created table data
// -- in session1 will be purged
// call fuse_vacuum2('db', 't');
// -- while merging with S
// -- if A < B, this txn should abort
// commit;

if txn_begin_timestamp < latest_snapshot_timestamp {
return Err(ErrorCode::UnresolvableConflict(format!(
"Unresolvable conflict detected for table {} while resolving conflicts: txn started with logical timestamp {}, which is less than the latest table timestamp {}. Transaction must be aborted.",
tid, txn_begin_timestamp, latest_snapshot_timestamp
)));
}
}
}
}
Expand Down
31 changes: 31 additions & 0 deletions tests/suites/0_stateless/01_transaction/01_03_issue_18705.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python3

import mysql.connector

# https://github.com/databendlabs/databend/issues/18705
if __name__ == "__main__":
# Session 1:
# Insert into empty table
mdb = mysql.connector.connect(host="127.0.0.1", user="root", passwd="root", port="3307")
mycursor = mdb.cursor()
mycursor.execute("create or replace table t_18705(c int)")
mycursor.fetchall()
mycursor.execute("begin")
mycursor.fetchall()
mycursor.execute("insert into t_18705 values (1)")
mycursor.fetchall()

# Session 2:
# Alter table in another session, so that the new table after alter operation will still be empty
mydb_alter_tbl = mysql.connector.connect(host="127.0.0.1", user="root", passwd="root", port="3307")
mycursor_alter_tbl = mydb_alter_tbl.cursor()
mycursor_alter_tbl.execute("alter table t_18705 SET OPTIONS (block_per_segment = 500)")
mycursor_alter_tbl.fetchall()

# Session 1:
# Try commit the txn in session one
mycursor.execute("commit")
mycursor.fetchall()

# Will not reach here, if `commit` failed
print("Looks good")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Looks good