Skip to content

Fix logical error with mutation and transaction - #90469

Merged
tiandiwonder merged 9 commits into
ClickHouse:masterfrom
tiandiwonder:mutate_parts_in_transaction
Dec 22, 2025
Merged

tiandiwonder merged 9 commits into
ClickHouse:masterfrom
tiandiwonder:mutate_parts_in_transaction

Conversation

@tiandiwonder

@tiandiwonder tiandiwonder commented Nov 20, 2025 •

Copy link
Copy Markdown
Contributor

Changelog category (leave one):

  • Bug Fix (user-visible misbehavior in an official stable release)

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

Fix logical error when mutation without transaction mutates parts in an active transaction, which is rolled back finally.

Documentation entry for user-facing changes

  • Documentation is written (mandatory for new features)

Details

Suppose we have a part all_1_1_0.

trx1:     begin
trx1:     update   all_1_1_0->all_1_1_1
mutation: update   all_1_1_1->all_1_1_2
trx1:     rollback all_1_1_1  dropped
mutation: continue all_1_1_0->all_1_1_2

But all_1_1_2 already exists, logical error happens.

Solution:
If a part has been created or removed in an active transaction, it's not visible to a mutation without transaction. The mutation should wait for the active transaction to commit or rollback.

Description:
Suppose we have a part all_1_1_0.
trx1:     begin
trx1:     update   all_1_1_0->all_1_1_1
mutation: update   all_1_1_1->all_1_1_2
trx1:     rollback all_1_1_1  dropped
mutation: continue all_1_1_0->all_1_1_2
But all_1_1_2 already exists, logical error happens.

Solution:
If a part has been created or removed in an active transaction, it's
not visible to a mutation without transaction. The mutation should
wait for the active transaction to commit or rollback.
@clickhouse-gh

clickhouse-gh Bot commented Nov 20, 2025 •

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [3a88c56]

Summary: ❌

job_name test_name status info comment
Stateless tests (amd_binary, ParallelReplicas, s3 storage, parallel) failure
02319_lightweight_delete_on_merge_tree FAIL cidb, issue ISSUE EXISTS
Stateless tests (amd_debug, AsyncInsert, s3 storage, parallel) failure
02319_lightweight_delete_on_merge_tree FAIL cidb, issue ISSUE EXISTS
Integration tests (amd_asan, db disk, old analyzer, 4/6) failure
test_backup_restore_s3/test.py::test_backup_to_s3_different_credentials[data_file_name_from_first_file_name-native_multipart] FAIL cidb, issue ISSUE EXISTS
BuzzHouse (amd_debug) failure
Logical error: 'Inconsistent AST formatting in Subquery: the query: (STID: 1941-1bfa) FAIL cidb, issue ISSUE CREATED

@clickhouse-gh clickhouse-gh Bot added the pr-bugfix Pull request with bugfix, not backported by default label Nov 20, 2025
@tiandiwonder tiandiwonder added the can be tested Allows running workflows for external contributors label Nov 20, 2025
@tavplubix tavplubix self-assigned this Nov 20, 2025

bool IMergeTreeDataPart::isInvolvedInTransaction() const
{
assert(!storage.data_parts_loading_finished || !version.creation_tid.isEmpty() || (state == MergeTreeDataPartState::Temporary /* && std::uncaught_exceptions() */));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!storage.data_parts_loading_finished

Does it mean that the function can return incorrect result if data parts loading is not finished?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, storage.data_parts_loading_finished should be true for mutations.
I've changed it to assert(storage.data_parts_loading_finished).

bool IMergeTreeDataPart::isInvolvedInTransaction() const
{
assert(!storage.data_parts_loading_finished || !version.creation_tid.isEmpty() || (state == MergeTreeDataPartState::Temporary /* && std::uncaught_exceptions() */));
bool created_in_transaction = !version.creation_tid.isPrehistoric() && version.creation_csn == Tx::UnknownCSN;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it supposed to be true only when it is being created by a running transaction? If the transaction is committed, then creation_csn will not be UnknownCSN

isInvolvedInTransaction and created_in_transaction sound like it should be truу for any transactions, not only running

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Is it supposed to be true only when it is being created by a running transaction?"
Yes.
"If the transaction is committed, then creation_csn will not be UnknownCSN"
Then created_in_transaction is true, otherwise it is false if the transaction is running.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally use version.isVisible to check(outdated parts, being created or removed parts in an active transaction).

@tuanpach

Copy link
Copy Markdown
Member

Can you add a test to reproduce the problem in the description?

trx1:     begin
trx1:     update   all_1_1_0->all_1_1_1
mutation: update   all_1_1_1->all_1_1_2
trx1:     rollback all_1_1_1  dropped
mutation: continue all_1_1_0->all_1_1_2

@tiandiwonder

Copy link
Copy Markdown
Contributor Author

Can you add a test to reproduce the problem in the description?

trx1:     begin
trx1:     update   all_1_1_0->all_1_1_1
mutation: update   all_1_1_1->all_1_1_2
trx1:     rollback all_1_1_1  dropped
mutation: continue all_1_1_0->all_1_1_2

Already have one in tests/queries/0_stateless/01168_mutations_isolation_2.sh

test update rollback

$CLICKHOUSE_CLIENT -q "create table mt (n int) engine=MergeTree order by tuple()"
$CLICKHOUSE_CLIENT -q "insert into mt values (1)"

tx 4 "begin transaction"
tx 4 "alter table mt update n=n+100 where 1"
$CLICKHOUSE_CLIENT -q "alter table mt update n=n+42 where 1"
tx 4 "rollback"

"Deadlock detected: mutation {} in transaction {} depends on earlier mutation {} "
"from the same transaction with intermediate mutations in between. ",
mutation_entry.file_name, mutation_entry.tid, earlier_mutation.file_name);
result.latest_fail_error_code_name = ErrorCodes::getName(ErrorCodes::LOGICAL_ERROR);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use LOGICAL_ERROR for runtime assertions, and this error code means that a bug was detected. In release build it's a normal exception, but in debug and sanitizer builds it aborts like assert would do

But looks like this scenario is possible with someuser inputs, so it's not a suitable error code. Btw, we have DEADLOCK_AVOIDED

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@tiandiwonder
tiandiwonder added this pull request to the merge queue Dec 22, 2025
Merged via the queue into ClickHouse:master with commit 67063c9 Dec 22, 2025
126 of 131 checks passed
@tiandiwonder
tiandiwonder deleted the mutate_parts_in_transaction branch December 22, 2025 01:33
@robot-ch-test-poll2 robot-ch-test-poll2 added the pr-synced-to-cloud The PR is synced to the cloud repo label Dec 22, 2025
"Deadlock detected: mutation {} in transaction {} depends on earlier mutation {} "
"from the same transaction with intermediate mutations in between. ",
mutation_entry.file_name, mutation_entry.tid, earlier_mutation.file_name);
result.latest_fail_error_code_name = ErrorCodes::getName(ErrorCodes::LOGICAL_ERROR);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tiandiwonder do we need to change the error code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-bugfix Pull request with bugfix, not backported by default pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants