Transactions

MongoDB supports transactions if it’s configured as a replica set or a sharded cluster.

Because MongoDB transactions have some limitations and are not meant to be used as freely as SQL transactions, Django’s transactions APIs, including most notably django.db.transaction.atomic(), function as no-ops.

Instead, Django MongoDB Backend provides its own django_mongodb_backend.transaction.atomic() function.

Outside of a transaction, query execution uses Django and MongoDB’s default behavior of autocommit mode. Each query is immediately committed to the database.

Controlling transactions

atomic(using=None)

Atomicity is the defining property of database transactions. atomic allows creating a block of code within which the atomicity on the database is guaranteed. If the block of code is successfully completed, the changes are committed to the database. If there is an exception, the changes are rolled back.

atomic is usable both as a decorator:

from django_mongodb_backend import transaction


@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

and as a context manager:

from django_mongodb_backend import transaction


def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

Avoid catching exceptions inside atomic!

When exiting an atomic block, Django looks at whether it’s exited normally or with an exception to determine whether to commit or roll back. If you catch and handle exceptions inside an atomic block, you may hide from Django the fact that a problem has happened. This can result in unexpected behavior.

This is mostly a concern for DatabaseError and its subclasses such as IntegrityError. After such an error, the transaction is broken and Django will perform a rollback at the end of the atomic block.

You may need to manually revert app state when rolling back a transaction.

The values of a model’s fields won’t be reverted when a transaction rollback happens. This could lead to an inconsistent model state unless you manually restore the original field values.

For example, given MyModel with an active field, this snippet ensures that the if obj.active check at the end uses the correct value if updating active to True fails in the transaction:

from django_mongodb_backend import transaction
from django.db import DatabaseError

obj = MyModel(active=False)
obj.active = True
try:
    with transaction.atomic():
        obj.save()
except DatabaseError:
    obj.active = False

if obj.active:
    ...

This also applies to any other mechanism that may hold app state, such as caching or global variables. For example, if the code proactively updates data in the cache after saving an object, it’s recommended to use transaction.on_commit() instead, to defer cache alterations until the transaction is actually committed.

atomic takes a using argument which should be the name of a database. If this argument isn’t provided, Django uses the "default" database.

Performance considerations

Open transactions have a performance cost for your MongoDB server. To minimize this overhead, keep your transactions as short as possible. This is especially important if you’re using atomic() in long-running processes, outside of Django’s request / response cycle.

Performing actions after commit

The atomic() function supports Django’s on_commit() API to perform actions after a transaction successfully commits.

For convenience, on_commit() is aliased at django_mongodb_backend.transaction.on_commit so you can use both:

from django_mongodb_backend import transaction


transaction.atomic()
transaction.on_commit(...)

Limitations

MongoDB’s transaction limitations that are applicable to Django are:

  • QuerySet.union() is not supported inside a transaction.

  • Savepoints (i.e. nested atomic() blocks) aren’t supported. The outermost atomic() will start a transaction while any inner atomic() blocks have no effect.