Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What specific exceptions represent a serialization failure when Django is using serializable transaction isolation level with postgresql?

Sometimes it's desirable to use a higher isolation level than the default "read committed" for database operations in Django. The docs warn that:

Under higher isolation levels, your application should be prepared to handle exceptions raised on serialization failures.

But which specific exceptions indicate a serialization failure, versus some other problem with the query or transaction?

A simple retry mechanism after a serialization failure might look like something like this:

for retries in range(0, 3):
    try:
        with transaction.atomic():
            MyModel.objects.update(foo='bar')
    except StuffHappened:
        continue
    else:
        break

What specific exceptions should replace StuffHappened so that only serialization failures, and not other exceptions, result in a retry?

Django has a variety of Database Exceptions and Transaction Exceptions. Might one/some of those represent serialization failures?

I'm specifically interested in postgresql for this.

like image 743
user85461 Avatar asked Sep 21 '18 00:09

user85461


1 Answers

Hmm, good question. The documentation implies that the appropriate exception would be a TransactionManagementError:

TransactionManagementError is raised for any and all problems related to database transactions.

However, the source code gives a strong clue that it isn't:

class TransactionManagementError(ProgrammingError):
    """Transaction management is used improperly."""
    pass

Note that this is a ProgrammingError, which is indeed used to indicate programmer error (i.e. "used improperly").

If we look at the documentation for psycopg (the Python adapter used for PostgreSQL support) we see that it will raise a psycopg2.extensions.TransactionRollbackError:

exception psycopg2.extensions.TransactionRollbackError (subclasses OperationalError)

Error causing transaction rollback (deadlocks, serialization failures, etc).

But what does Django do with that? Well, as documented here, it wraps the standard Python DB API 2.0 exceptions in Django equivalents, and sets the __cause__ attribute to the original exception. So the following is probably the most specific check you can make:

from django.db import OperationalError
from psycopg2.extensions import TransactionRollbackError

for retries in range(0, 3):
    try:
        with transaction.atomic():
            MyModel.objects.update(foo='bar')
    except OperationalError as e:
        if e.__cause__.__class__ == TransactionRollbackError:
            continue
        else:
            raise            
    else:
        break

Depending on the error details exposed by PostgreSQL (available via e.__cause__.diag) it may be possible to write an even more specific test.

Generally, though, the Python DB API 2.0 documentation states that OperationalError is indeed the correct exception type for transaction issues, so catching that hopefully would be a reasonably effective database-agnostic solution.

like image 138
Kevin Christopher Henry Avatar answered Sep 25 '22 12:09

Kevin Christopher Henry