Правильный способ отката при отказе соединения с БД в Django

Это больше вопрос дизайна, чем что-либо еще.

До недавнего времени я использовал Django с SQLite в своей среде разработки, но теперь я перешел на PostgreSQL для производства. Мое приложение развернуто на Heroku, и через несколько дней я понял, что они проводят случайное обслуживание БД и она падает в течение нескольких минут.

Например, модель с 3 таблицами, одна Procedure, каждая из которых указывает на ProcedureList, а ProcedureList может иметь более одной Procedure. ProcedureUser, который связывает ProcedureList и пользователя и устанавливает некоторые определенные переменные для пользователя на этом ProcedureList. Наконец, есть ProcedureState, который связывает Procedure с состоянием конкретного пользователя.

В моем приложении в одном из представлений есть функция, которая изменяет БД следующим образом:

user = request.user
plist = ProcedureList.objects.get(id=idFromUrl)
procedures = Procedure.objects.filter(ProcedureList=pList)

pUser = ProcedureUser(plist, user, someVariables)
pUser.save()

for procedure in procedures:
    pState = ProcedureState(plist, user, pUser, procedure, otherVariables)    
    pState.save()
    

Теперь я думаю, что если Heroku решит уйти на техническое обслуживание между этими вызовами object.save(), у нас возникнет проблема. Более поздние вызовы .save() будут неудачными, и БД будет повреждена. Запрос пользователя, конечно же, будет неудачным, и не будет возможности откатить предыдущие вставки, потому что соединение с БД невозможно.

Мой вопрос заключается в том, что в случае сбоя БД (вызванного обслуживанием Heroku, сетевой ошибкой или чем-то еще), как мы должны правильно откатить БД? Должны ли мы составить список вставок и ждать, пока БД снова поднимется, чтобы откатить их?

Я использую Python 3 и Django 4, но я думаю, что это скорее общий вопрос, чем специфический для какой-либо платформы.

в случае сбоя БД (вызванного обслуживанием Heroku, ошибкой сети или чем-то еще), как мы должны правильно откатить БД?

Это решается в базах данных с помощью атомарных транзакций [wiki]. атомарная транзакция - это набор запросов, которые фиксируются все или ни одного. Таким образом, невозможно, чтобы для такой транзакции применялись одни запросы, а другие не применялись.

Django предлагает transaction менеджер контекстов [Django-doc] для выполнения работы в транзакции:

from django.db import transaction

with transaction.atomic():
    user = request.user
    plist = ProcedureList.objects.get(id=idFromUrl)
    procedures = Procedure.objects.filter(ProcedureList=pList)
    
    pUser = ProcedureUser(plist, user, someVariables)
    pUser.save()
    
    ProcedureState.objects.bulk_create([
        ProcedureState(plist, user, pUser, procedure, otherVariables)
        for procedure in procedures
    ])

В конце контекстного блока будет фиксировать изменения. Это означает, что если в промежутке произойдет сбой базы данных, действия не будут зафиксированы, и блок вызовет исключение (обычно IntegrityError).


Note: Django has a .bulk_create(…) method [Django-doc] to create multiple items with a single database query, minimizing the bandwidth between the database and the application layer. This will usually outperform creating items in a loop.

Вернуться на верх