Skip to content

TokuMX Transactions

Leif Walsh edited this page May 31, 2013 · 2 revisions

This wiki describes how transactions are represented and managed in TokuMX.

Transaction stack management

Requirements

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.

Implementation

Basic wrapper

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.

TransactionStack

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).

Transaction

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.

Example

/**
 * 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();
        }
    }
}

Future Work

  • Some of the api is too exposed, most annoyingly storage::Txn::{Txn,commit,abort}, which makes it tempting to try to manage a Txn 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 the Transactions 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.
Clone this wiki locally