-
Notifications
You must be signed in to change notification settings - Fork 96
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 (ClientCursors 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 aTxnmanually. - Other things like replication are conceptually coupled with transactions, and could benefit from integration, particularly in the stack management.
- A stolen
TransactionStackhas no way to recover the state of theTransactions 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
Transactionobjects.
- You can, however, replace it and create nested transactions underneath that with new