Skip to content

memtx: rework transaction rollback#7312

Merged
alyapunov merged 7 commits intotarantool:masterfrom
CuriousGeorgiy:memtx-rework-tx-rollback
Sep 28, 2022
Merged

memtx: rework transaction rollback#7312
alyapunov merged 7 commits intotarantool:masterfrom
CuriousGeorgiy:memtx-rework-tx-rollback

Conversation

@CuriousGeorgiy
Copy link
Member

@CuriousGeorgiy CuriousGeorgiy commented Jun 27, 2022

When we rollback a transaction statement, we relink its read trackers
to a newer story in the history chain, if present (6c990a7), but we do not
handle the case when there is no newer story.

In order to fix this, we need to retain stories added by rollbacked
transactions, because they store the reader list needed for conflict
resolution.

There are several nuances we need to account for:

Firstly, such stories must be impossible to read from an index: this is
ensured by memtx_tx_tuple_clarify.

Secondly, rollbacked transactions need to be treated as prepared with
stories that have add_psn == del_psn the stories they add need to be
'sunk' to the level of prepared transactions, so now we have the following
totally ordered set over tuple stories:
———————————————————————————————————————————————————————> serialization time
|— — — — — —|— — — — — |— — — — — — | — — — — — — — —
|Committed +|Prepared +| In-progress| One dirty story
|rollbacked |rollbacked|            | in index
|— — — — — —| — — — — —|— — — — — — |— — — — — — — — —

Last, but not least, we also need to consider the following shadowing
effect: if a rollbacked story is on top of the history chain and there are
older stories in the history chain, it can possibly shadow a story of a
prepared or committed tuple, which is not de jure deleted, so instead of
simply deleting the story's tuple from the index, we need to replaced with
the tuple of an older story. In order for this to work, though, we also
need to retain stories of committed and prepared tuples which are not
de jure deleted (have a zero del_psn), if there are newer stories in the
history chain which could possibly shadow them.

Closes #7343

memtx: fix transaction manager MVCC invariant violation

We hold the following invariant in MVCC: the story at the top of the
history chain is present in index.

If a story is subject to be deleted from index and there is an older story
in the history chain, the older story starts to be at the top of the
history chain and is not present in index, which violates our invariant:
explicitly check for this case when evaluating whether a story can be
garbage collected and add an assertion to check the invariant above is not
violated.

memtx_tx_story_full_unlink is called in two context: space deletion, in
which we delete all stories, and garbage collection step — the former case
can break the invariant described above, while the latter must preserve it,
hence add two different functions for the corresponding contexts.

Closes #7490

@CuriousGeorgiy CuriousGeorgiy self-assigned this Jun 27, 2022
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch 3 times, most recently from 24dcf44 to 426a340 Compare July 4, 2022 08:46
@CuriousGeorgiy CuriousGeorgiy marked this pull request as ready for review July 4, 2022 08:54
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch from 426a340 to d30b855 Compare July 4, 2022 08:54
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch 3 times, most recently from 595a5d9 to 6b6bdef Compare July 5, 2022 06:53
@CuriousGeorgiy CuriousGeorgiy requested a review from Korablev77 July 5, 2022 08:28
@CuriousGeorgiy CuriousGeorgiy added the bug Something isn't working label Jul 5, 2022
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch 3 times, most recently from 8b6e2e9 to 53794c6 Compare July 7, 2022 04:40
if (stmt->add_story != NULL)
memtx_tx_history_rollback_added_story(stmt);
if (stmt->del_story != NULL)
memtx_tx_story_unlink_deleted_by(stmt->del_story, stmt);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I'd move 'if' also to the function. Also comments outside scope (e.g. {}) start from /**

Copy link
Member Author

@CuriousGeorgiy CuriousGeorgiy Jul 14, 2022

Choose a reason for hiding this comment

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

I followed the existing style:

tarantool/src/box/memtx_tx.c

Lines 2315 to 2325 in 8f799cd

void
memtx_tx_history_prepare_stmt(struct txn_stmt *stmt)
{
assert(stmt->txn->psn != 0);
if (stmt->add_story != NULL) {
memtx_tx_history_prepare_insert_stmt(stmt);
} else {
assert(stmt->del_story != NULL);
memtx_tx_history_prepare_delete_stmt(stmt);
}

Could you please provide a reference to the style guide? I was not able to find this rule.

src/box/txn.c Outdated
stmt->space = NULL;
stmt->row = NULL;
}
txn->psn = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't get this change: why do you assign psn to txn_last_psn and then nullify it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because we rollback only part of the transaction (up to savepoint).

Copy link
Member Author

@CuriousGeorgiy CuriousGeorgiy Sep 27, 2022

Choose a reason for hiding this comment

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

Refactored this into a separate function with an appropriate comment.

@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch from 53794c6 to 2f5349a Compare July 20, 2022 06:55
@drewdzzz drewdzzz removed their assignment Jul 28, 2022
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch 4 times, most recently from 8c422c4 to a096f8a Compare September 23, 2022 18:04
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch 3 times, most recently from 4f6e5ef to fe955c8 Compare September 25, 2022 16:57
When a space is deleted, all transactions need to be aborted and all their
stories need to be removed immediately out of order: currently we
artificially rollback statements — instead call this statement
removal to logically distinguish it from rollback. It differs in the sense
that the whole space's tuple history is teared down instead — no more
transaction managing is going to be done as opposed to rollback of an
individual transaction.

Needed for tarantool#7343

NO_CHANGELOG=refactoring
NO_DOC=refactoring
NO_TEST=refactoring
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch 3 times, most recently from 63a43c9 to 15bfe84 Compare September 26, 2022 13:42
Copy link
Contributor

@alyapunov alyapunov left a comment

Choose a reason for hiding this comment

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

LGTM, but please check my comments.

/* The story is in chain, but not at top. */
continue;
}
if (link->in_index == NULL)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please and a simple test case that shows that rolled back story does not affect index:count()?

Copy link
Member Author

@CuriousGeorgiy CuriousGeorgiy Sep 27, 2022

Choose a reason for hiding this comment

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

Added two test cases for the correct handling of rolled back and read-view stories.

if ((tracker->index_mask & index_mask) == 0)
continue;
memtx_tx_handle_conflict(stmt->txn, tracker->reader);
for (struct memtx_story *read_story =
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems that it is an overkill.

Copy link
Member Author

@CuriousGeorgiy CuriousGeorgiy Sep 27, 2022

Choose a reason for hiding this comment

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

Removed this loop as redundant: we assume that readers of previously prepared transactions are conflicted by induction.

static void
txn_rollback_to_svp(struct txn *txn, struct stailq_entry *svp)
{
txn->psn = ++txn_last_psn;
Copy link
Contributor

Choose a reason for hiding this comment

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

This place need to be commented well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Refactored this into a separate function with an appropriate comment.

src/box/txn.c Outdated
void
txn_complete_fail(struct txn *txn)
{
txn->psn = ++txn_last_psn;
Copy link
Contributor

Choose a reason for hiding this comment

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

This place need to be commented well too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved this to txn_rollback, added an assertion here, and commented on this assignment.

`struct memtx_story` has a `space` field, which is basically used
to identify that a tuple is unlinked from the history chain in
`memtx_tx_index_invisible_count_slow` (though this can be determined by its
presence in the index) and is used to get the space's index in
`memtx_tx_story_link_top` (though it can be  retrieved from the older
story's link field): remove this redundant field.

Needed for tarantool#7343

NO_CHANGELOG=<refactoring>
NO_DOC=<refactoring>
NO_TEST=<refactoring>
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch from 15bfe84 to eee8611 Compare September 27, 2022 14:07
@CuriousGeorgiy
Copy link
Member Author

Filed a report on linter FP tarantool/checkpatch#42.

@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch from eee8611 to e6717e4 Compare September 27, 2022 14:32
@CuriousGeorgiy CuriousGeorgiy added the full-ci Enables all tests for a pull request label Sep 27, 2022
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch from e6717e4 to 6e292aa Compare September 27, 2022 14:41
@CuriousGeorgiy CuriousGeorgiy removed the full-ci Enables all tests for a pull request label Sep 27, 2022
When we rollback a transaction statement, we relink its read trackers
to a newer story in the history chain, if present (6c990a7), but we do not
handle the case when there is no newer story.

If there is an older story in the history chain, we can relink the
rollbacked story's reader to it, but if the rollbacked story is the
only one left, we need to retain it, because it stores the reader list
needed for conflict resolution — such stories are distinguished by the
rollbacked flag, and there can be no more than one such story located
strictly at the end of a given history chain (which means a story can be
fully unlinked from some indexes and present at the end of others).

There are several nuances we need to account for:

Firstly, such rollbacked stories must be impossible to read from an index:
this is ensured by `memtx_tx_story_is_visible`.

Secondly, rollbacked transactions need to be treated as prepared with
stories that have `add_psn == del_psn`, so that they are correctly deleted
during garbage collection.

After this logical change we have the following partially ordered set over
tuple stories:
———————————————————————————————————————————————————————> serialization time
|- - - - - - - -|— — — — — -|— — — — — |— — — — — — -|— — — — — — — -
| No more than  | Committed | Prepared | In-progress | One dirty
| one rollbacked|           |          |             | story in index
| story         |           |          |             |
|- - - - - - - -|— — — — — -| — — — — —|— — — — — — -|— — — — — — — —

Closes tarantool#7343

NO_DOC=bugfix
We hold the following invariant in MVCC: the story at the top of the
history chain is present in index.

If a story is subject to be deleted from index and there is an older story
in the history chain, the older story starts to be at the top of the
history chain and is not present in index, which violates our invariant:
explicitly check for this case when evaluating whether a story can be
garbage collected and add an assertion to check the invariant above is not
violated.

Rollbacked stories need to be handled in a special way: they are
present at the end of some history chains and completely unlinked from
others (which also implies they are not present in the corresponding
indexes).

`memtx_tx_story_full_unlink` is called in two contexts: space deletion, in
which we delete all stories, and garbage collection step — the former case
can break the invariant described above, while the latter must preserve it,
hence add two different functions for the corresponding contexts.

Closes tarantool#7490

NO_CHANGELOG=<internal bugfix not user observable>
NO_DOC=<bugfix>
@CuriousGeorgiy CuriousGeorgiy force-pushed the memtx-rework-tx-rollback branch from 6e292aa to 9f4e55e Compare September 27, 2022 14:48
@CuriousGeorgiy CuriousGeorgiy added the full-ci Enables all tests for a pull request label Sep 27, 2022
@coveralls
Copy link

Coverage Status

Coverage decreased (-0.01%) to 84.318% when pulling 9f4e55e on CuriousGeorgiy:memtx-rework-tx-rollback into 1d9645f on tarantool:master.

Comment on lines +1400 to 1409
if (link->older_story != NULL) {
assert(!story->rollbacked);
memtx_tx_story_set_status(story,
MEMTX_TX_STORY_USED);
return;
}
}
if (!rlist_empty(&link->nearby_gaps)) {
memtx_tx_story_set_status(story,
MEMTX_TX_STORY_TRACK_GAP);
Copy link
Contributor

Choose a reason for hiding this comment

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

If we have a story, that is used to track gap on primary index (it is not on top) and is on top of secondary index, then order of setting statuses is violated. We can iterate over the indexes twice, but I doubt it's worth it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I also thought about it, but I don't like having another loop here.

@alyapunov alyapunov merged commit c8eccfb into tarantool:master Sep 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working complicated full-ci Enables all tests for a pull request memtx mvcc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

memtx transaction manager MVCC tuple story list invariant violation Unserializable read tracked incorrectly after transaction rollback in memtx

5 participants