-
Notifications
You must be signed in to change notification settings - Fork 97
TokuMX Transactions
This wiki describes how transactions are represented and managed in TokuMX.
The ydb imposes some constraints on our usage of transactions:
- Transactions are associated with a thread.
- In fact, an entire stack of transactions from a root through all of its descendants, is associated with a thread.
- Each transaction may have zero or one live children at any time. You cannot have sibling transactions.
The class mongo::storage::Txn
(in mongo/db/storage/txn.{h,cpp}
) wraps the ydb-level DB_TXN
, knows how to call env->txn_begin
, txn->commit
, and txn->abort
.
Its destructor calls txn->abort
if the transaction is still live, when it dies, for exception safety.
You should use this class to get its contained DB_TXN
to pass to a ydb function.
You should not call commit
or abort
directly, you should let the TransactionStack
handle that.
The class mongo::Client::TransactionStack
(in mongo/db/client.h
and mongo/db/client_transaction.cpp
) maintains a stack of transactions.
It handles knowing what a transaction's parent is, and ensures that transactions are committed or aborted before their parents.
A TransactionStack
is owned by a Client
, which is a thread-local object (this accomplishes the first two requirements above).
The class mongo::Client::Transaction
(also in mongo/db/client.h
and mongo/db/client_transaction.cpp
) manages the lifetime of a transaction.
It doesn't actually own the transaction, but its constructor and destructor are proxies for calls in to the TransactionStack
.
You should usually use this class to create transactions, and to commit them at the end of a successful operation. This will create a transaction that may or may not be nested, and will limit its lifetime.
You should not use this class to access the DB_TXN
for ydb functions.
Some things in the code (ClientCursor
s are one) can steal a TransactionStack
away from a Client
.
They may do this in order to persist transactions across requests, for example, and will eventually be used to implement multi-statement transactions.
If something steals the TransactionStack
from the current Client
, the Transaction
destructor will notice this and understand it doesn't need to do anything.
/**
* Gets a storage::Txn from the current client, and uses it to call in to the ydb.
* Doesn't know who created the transaction, and doesn't care (except that there is one available).
*/
bool doTheInsert() {
const storage::Txn &txn = cc().txn();
...;
db->put(..., txn.db_txn(), ...);
...;
return true;
}
/**
* Just here to get in the way.
*/
bool dispatch() {
if (...) {
...;
} else if (...) {
return doTheInsert();
} else if (...) {
...;
}
}
/**
* Creates a transaction and dispatches to something that figures out what to do next.
*/
void higherLevelFunction() {
...;
{
Client::Transaction transaction(DB_SERIALIZABLE);
...;
bool ok = dispatch();
...;
if (ok) {
transaction.commit();
}
}
}
- Some of the api is too exposed, most annoyingly
storage::Txn::{Txn,commit,abort}
, which makes it tempting to try to manage aTxn
manually. - Other things like replication are conceptually coupled with transactions, and could benefit from integration, particularly in the stack management.
- A stolen
TransactionStack
has no way to recover the state of theTransaction
s that formed it, so once you steal a stack, you need to manage all of its transactions yourself (though you probably just want to commit or abort all of them at some point).- You can, however, replace it and create nested transactions underneath that with new
Transaction
objects.
- You can, however, replace it and create nested transactions underneath that with new