Upgrade to Pro — share decks privately, control downloads, hide ads and more …

どうなってるの?Djangoのトランザクション

 どうなってるの?Djangoのトランザクション

Django Congress2019 での資料です。

Djangoのtranasctionモジュールを中心にコードの話やトランザクション分離レベルの話などをします。

Denzow

May 18, 2019
Tweet

More Decks by Denzow

Other Decks in Technology

Transcript

  1. 3 ➡ でんぞう (@denzowill, denzow) ➡ Python歴 6年 (仕事で1.5年) ➡

    scouty,inc シニアエンジニア(前職はDBサポートエンジニア) ➡ DBスペシャリスト(PostgreSQLが好き、•racleは得意だけど苦⼿) お前誰よ?
  2. 4 ➡ でんぞう (@denzowill, denzow) ➡ Python歴 6年 (仕事で1.5年) ➡

    scouty,inc シニアエンジニア(前職はDBサポートエンジニア) ➡ DBスペシャリスト(PostgreSQLが好き、•racleは得意だけど苦⼿) お前誰よ? LAPRAS
  3. Amazon
 DynamoDB Amazon ECS Amazon ECS Amazon ECS Amazon ECS

    Amazon
 SQS Elastic Load Balancing* AWS Lambda Amazon CloudWatch Amazon
 RDS Aurora
 (MySQL 5.7) Amazon 
 ElastiCache sns-activity
 watcher worker Ϋϩʔϧͨ͠
 ੜσʔλͷdiff ੔ܗ͞Εͨσʔλ event 
 (time-based) ᶃ ᶄ ᶆ ᶇ ᶅ ᶈ ᶈ ᶈ ᶉ ᶉ ᶉ インフラ構成図 crawler Amazon
 RDS (MySQL 8.0)
  4. Amazon
 DynamoDB Amazon ECS Amazon ECS Amazon ECS Amazon ECS

    Amazon
 SQS Elastic Load Balancing* AWS Lambda Amazon CloudWatch Amazon
 RDS Aurora
 (MySQL 5.7) Amazon 
 ElastiCache sns-activity
 watcher worker Ϋϩʔϧͨ͠
 ੜσʔλͷdiff ੔ܗ͞Εͨσʔλ event 
 (time-based) ᶃ ᶄ ᶆ ᶇ ᶅ ᶈ ᶈ ᶈ ᶉ ᶉ ᶉ インフラ構成図 crawler Amazon
 RDS (MySQL 8.0)
  5. Amazon
 DynamoDB Amazon ECS Amazon ECS Amazon ECS Amazon ECS

    Amazon
 SQS Elastic Load Balancing* AWS Lambda Amazon CloudWatch Amazon
 RDS Aurora
 (MySQL 5.7) Amazon 
 ElastiCache sns-activity
 watcher worker Ϋϩʔϧͨ͠
 ੜσʔλͷdiff ੔ܗ͞Εͨσʔλ event 
 (time-based) ᶃ ᶄ ᶆ ᶇ ᶅ ᶈ ᶈ ᶈ ᶉ ᶉ ᶉ インフラ構成図 crawler Amazon
 RDS (MySQL 8.0)
  6. 11 Django と scouty(2018/5) ➡ Django 1.11 ➡ Django Celery

    ➡ Django でDDD的な設計 ➡ Django Channels でのWebsocket(予定) ➡ daphneでの本番運⽤
  7. 12 Django と LAPRAS(2019/5) ➡ Django 2.1 ➡ Django Celery

    ➡ Django でDDD的な設計 ➡ Django Channels でのWebsocket ➡ daphneでの本番運⽤
  8. 36 トランザクション分離レベル 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する

    発⽣する READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣する SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない
  9. 38 ファジーリード BEGIN; BEGIN; UPDATE A... WHER ID=1; SELECT ...

    FROM A; COMMIT; SELECT ... FROM A; さっきとレコードの 内容が違う
  10. 39 ファントムリード BEGIN; BEGIN; INSERT INTO A...; SELECT ... FROM

    A; COMMIT; SELECT ... FROM A; ⾏が増えてる
  11. 40 トランザクション分離レベル 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する

    発⽣する READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣する SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない
  12. 46 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する 発⽣する

    READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣する SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない MySQLのREPETABLE READ
  13. 47 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する 発⽣する

    READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣しない SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない MySQLのREPETABLE READ
  14. 58 1.11までのMySQLの初期化 # django.db.backends.mysql.base.DatabaseWrapper#get_connection_params def get_connection_params(self): : options = settings_dict['OPTIONS'].copy()

    isolation_level = options.pop('isolation_level', None) if isolation_level: : : isolation_level = isolation_level.replace(' ', '-') self.isolation_level = isolation_level kwargs.update(options) return kwargs 指定されてなければ何も指定しない -> DB側のデフォルト値になる
  15. 59 2.0からのMySQLの初期化 # django.db.backends.mysql.base.DatabaseWrapper#get_connection_params def get_connection_params(self): : options = settings_dict['OPTIONS'].copy()

    isolation_level = options.pop('isolation_level', 'read_committed') if isolation_level: : : isolation_level = isolation_level.replace(' ', '-') self.isolation_level = isolation_level kwargs.update(options) return kwargs 指定されてなければ read_committedを設定する
  16. 62 PostgreSQLの初期化 # django.db.backends.postgresql.base.DatabaseWrapper#get_connection_params def get_new_connection(self, conn_params): connection = Database.connect(**conn_params)

    : options = self.settings_dict['OPTIONS'] try: self.isolation_level = options['isolation_level'] except KeyError: self.isolation_level = connection.isolation_level else: # Set the isolation level to the value from OPTIONS. if self.isolation_level != connection.isolation_level: connection.set_session(isolation_level=self.isolation_level) return connection 指定されてなければ サーバ⾃体のデフォルト値を使う
  17. 63 ちなみにOPTIONS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME':

    'docker', 'USER': 'docker', 'PASSWORD': 'docker', 'HOST': 'db', 'PORT': '5432', 'OPTIONS': { 'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE, } } }
  18. 67 Djangoでのトランザクション管理 LOG: duration: 1.111 ms statement: INSERT INTO "blog_author"

    ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.517 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (2, 'test_title') RETURNING "blog_blog"."id" BEGIN/COMMITがないので各SQL毎にCOMMIT
  19. 68 Djangoでのトランザクション管理 def start_blog_with_transaction(request): with transaction.atomic(): author = Author.objects.create(name='test') blog

    = Blog.objects.create( title='test_title', author=author, ) return HttpResponse(f'success create blog {blog.id}')
  20. 69 Djangoでのトランザクション管理 LOG: duration: 0.099 ms statement: BEGIN LOG: duration:

    1.111 ms statement: INSERT INTO "blog_author" ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.517 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (2, 'test_title') RETURNING "blog_blog"."id" LOG: duration: 4.009 ms statement: COMMIT 2つのSQLがBEGIN/COMMITで囲まれており トランザクションとして処理されている
  21. 71 transaction.atomic() # django.db.transaction.atomic def atomic(using=None, savepoint=True): if callable(using): return

    Atomic(DEFAULT_DB_ALIAS, savepoint)(using) else: return Atomic(using, savepoint)
  22. 72 transaction.atomic() # django.db.transaction.atomic def atomic(using=None, savepoint=True): if callable(using): return

    Atomic(DEFAULT_DB_ALIAS, savepoint)(using) else: return Atomic(using, savepoint) @transaction.atomicの場合 @transaction.atomic() or 普通に呼び出した場合
  23. 73 transaction.atomic() @transaction.atomic def start_blog(request): author = Author.objects.create(name='test') blog =

    Blog.objects.create( title='test_title', author=author, ) return HttpResponse(f'success create blog {blog.id}')
  24. 74 transaction.atomic() # django.db.transaction.Atomic class Atomic(ContextDecorator): def __enter__(self): connection =

    get_connection(self.using) if not connection.in_atomic_block: # Reset state when entering an outermost atomic block. connection.commit_on_exit = True connection.needs_rollback = False if not connection.get_autocommit(): connection.in_atomic_block = True connection.commit_on_exit = False if connection.in_atomic_block: if self.savepoint and not connection.needs_rollback: sid = connection.savepoint() connection.savepoint_ids.append(sid) else: connection.savepoint_ids.append(None) else: connection.set_autocommit(False, force_begin_transaction_with_broken_autocommit=True) connection.in_atomic_block = True
  25. 75 transaction.atomic() # django.db.transaction.Atomic class Atomic(ContextDecorator): def __enter__(self): connection =

    get_connection(self.using) if not connection.in_atomic_block: # Reset state when entering an outermost atomic block. connection.commit_on_exit = True connection.needs_rollback = False if not connection.get_autocommit(): connection.in_atomic_block = True connection.commit_on_exit = False if connection.in_atomic_block: if self.savepoint and not connection.needs_rollback: sid = connection.savepoint() connection.savepoint_ids.append(sid) else: connection.savepoint_ids.append(None) else: connection.set_autocommit(False, force_begin_transaction_with_broken_autocommit=True) connection.in_atomic_block = True DBとのコネクションをラップした DatabaseWrapperにin_atomic_blockフラグを⽴て autocommitをFalseにする
  26. 76 transaction.atomic() DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME':

    'djangodb', 'USER': 'hoge', 'PASSWORD': 'password', 'HOST': 'localhost', 'PORT': '5432', 'ATOMIC_REQUESTS': True, } }
  27. 77 transaction.atomic() DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME':

    'djangodb', 'USER': 'hoge', 'PASSWORD': 'password', 'HOST': 'localhost', 'PORT': '5432', 'ATOMIC_REQUESTS': True, } }
  28. 78 transaction.atomic() # django.core.handlers.base.BaseHandler#make_view_atomic def make_view_atomic(self, view): non_atomic_requests = getattr(view,

    '_non_atomic_requests', set()) for db in connections.all(): if db.settings_dict['ATOMIC_REQUESTS'] and db.alias not in non_atomic_requests: view = transaction.atomic(using=db.alias)(view) return view non_atomic_requestsに指定してないViewは transaction.atomic()で全体をラップして処理
  29. 79 Djangoでのトランザクション管理 def start_blog_with_nested_transaction(request): with transaction.atomic(): author = Author.objects.create(name='test') with

    transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) return HttpResponse(f'success create blog {blog.id}')
  30. 80 Djangoでのトランザクション管理 def start_blog_with_nested_transaction(request): with transaction.atomic(): author = Author.objects.create(name='test') with

    transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) return HttpResponse(f'success create blog {blog.id}')
  31. 81 Djangoでのトランザクション管理 LOG: duration: 0.072 ms statement: BEGIN LOG: duration:

    1.367 ms statement: INSERT INTO "blog_author" ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.070 ms statement: SAVEPOINT "s140129009637120_x1" LOG: duration: 1.071 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (4, 'test_title') RETURNING "blog_blog"."id" LOG: duration: 0.052 ms statement: RELEASE SAVEPOINT "s140129009637120_x1" LOG: duration: 2.085 ms statement: COMMIT
  32. 82 Djangoでのトランザクション管理 LOG: duration: 0.072 ms statement: BEGIN LOG: duration:

    1.367 ms statement: INSERT INTO "blog_author" ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.070 ms statement: SAVEPOINT "s140129009637120_x1" LOG: duration: 1.071 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (4, 'test_title') RETURNING "blog_blog"."id" LOG: duration: 0.052 ms statement: RELEASE SAVEPOINT "s140129009637120_x1" LOG: duration: 2.085 ms statement: COMMIT
  33. 86 Savepoint # django.db.transaction.Atomic class Atomic(ContextDecorator): def __enter__(self): connection =

    get_connection(self.using) if not connection.in_atomic_block: # Reset state when entering an outermost atomic block. connection.commit_on_exit = True connection.needs_rollback = False if not connection.get_autocommit(): connection.in_atomic_block = True connection.commit_on_exit = False if connection.in_atomic_block: if self.savepoint and not connection.needs_rollback: sid = connection.savepoint() connection.savepoint_ids.append(sid) else: connection.savepoint_ids.append(None) else: connection.set_autocommit(False, force_begin_transaction_with_broken_autocommit=True) connection.in_atomic_block = True 2回⽬以降に呼ばれたなら、savepointを発⾏ その識別⼦をリスト(スタック)に保持していく
  34. 87 Savepoint # django.db.backends.base.base.BaseDatabaseWrapper#savepoint def savepoint(self): if not self._savepoint_allowed(): return

    thread_ident = _thread.get_ident() tid = str(thread_ident).replace('-', '') self.savepoint_state += 1 sid = "s%s_x%d" % (tid, self.savepoint_state) self.validate_thread_sharing() self._savepoint(sid) return sid 実⾏スレッドIDをもとにSavepointのIDを決める
  35. 88 Savepoint # django.db.backends.base.base.BaseDatabaseWrapper#savepoint def savepoint(self): if not self._savepoint_allowed(): return

    thread_ident = _thread.get_ident() tid = str(thread_ident).replace('-', '') self.savepoint_state += 1 sid = "s%s_x%d" % (tid, self.savepoint_state) self.validate_thread_sharing() self._savepoint(sid) return sid savepointコマンドを発⾏する
  36. 89 Djangoでのトランザクション管理 def start_blog_with_nested_transaction_with_error(request): with transaction.atomic(): author = Author.objects.create(name='test') with

    transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) raise Exception('my error') return HttpResponse(f'success create blog {blog.id}')
  37. 90 Djangoでのトランザクション管理 def start_blog_with_nested_transaction_with_error(request): with transaction.atomic(): author = Author.objects.create(name='test') with

    transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) raise Exception('my error') return HttpResponse(f'success create blog {blog.id}')
  38. 91 Djangoでのトランザクション管理 def start_blog_with_nested_transaction_with_error(request): with transaction.atomic(): author = Author.objects.create(name='test') with

    transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) raise Exception('my error') return HttpResponse(f'success create blog {blog.id}')
  39. 92 Djangoでのトランザクション管理 LOG: duration: 0.130 ms statement: BEGIN LOG: duration:

    2.886 ms statement: INSERT INTO "blog_author" ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.054 ms statement: SAVEPOINT "s139681396160256_x1" LOG: duration: 0.546 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (5, 'test_title') RETURNING "blog_blog"."id" LOG: duration: 1.250 ms statement: ROLLBACK TO SAVEPOINT "s139681396160256_x1" LOG: duration: 0.045 ms statement: RELEASE SAVEPOINT "s139681396160256_x1" LOG: duration: 0.915 ms statement: ROLLBACK
  40. 93 Djangoでのトランザクション管理 LOG: duration: 0.130 ms statement: BEGIN LOG: duration:

    2.886 ms statement: INSERT INTO "blog_author" ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.054 ms statement: SAVEPOINT "s139681396160256_x1" LOG: duration: 0.546 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (5, 'test_title') RETURNING "blog_blog"."id" LOG: duration: 1.250 ms statement: ROLLBACK TO SAVEPOINT "s139681396160256_x1" LOG: duration: 0.045 ms statement: RELEASE SAVEPOINT "s139681396160256_x1" LOG: duration: 0.915 ms statement: ROLLBACK
  41. 94 Djangoでのトランザクション管理 def start_blog_with_nested_transaction_with_error2(request): with transaction.atomic(): author = Author.objects.create(name='test') try:

    with transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) raise Exception('my error') except: pass return HttpResponse(f'success create blog {blog.id}')
  42. 95 Djangoでのトランザクション管理 def start_blog_with_nested_transaction_with_error2(request): with transaction.atomic(): author = Author.objects.create(name='test') try:

    with transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) raise Exception('my error') except: pass return HttpResponse(f'success create blog {blog.id}')
  43. 96 Djangoでのトランザクション管理 def start_blog_with_nested_transaction_with_error2(request): with transaction.atomic(): author = Author.objects.create(name='test') try:

    with transaction.atomic(): blog = Blog.objects.create( title='test_title', author=author, ) raise Exception('my error') except: pass return HttpResponse(f'success create blog {blog.id}') try/exceptを追加
  44. 97 Djangoでのトランザクション管理 LOG: duration: 0.079 ms statement: BEGIN LOG: duration:

    1.264 ms statement: INSERT INTO "blog_author" ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.039 ms statement: SAVEPOINT "s140051465017088_x1" LOG: duration: 2.193 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (6, 'test_title') RETURNING "blog_blog"."id" LOG: duration: 0.086 ms statement: ROLLBACK TO SAVEPOINT "s140051465017088_x1" LOG: duration: 0.038 ms statement: RELEASE SAVEPOINT "s140051465017088_x1" LOG: duration: 3.101 ms statement: COMMIT
  45. 98 Djangoでのトランザクション管理 LOG: duration: 0.079 ms statement: BEGIN LOG: duration:

    1.264 ms statement: INSERT INTO "blog_author" ("name") VALUES ('test') RETURNING "blog_author"."id" LOG: duration: 0.039 ms statement: SAVEPOINT "s140051465017088_x1" LOG: duration: 2.193 ms statement: INSERT INTO "blog_blog" ("author_id", "title") VALUES (6, 'test_title') RETURNING "blog_blog"."id" LOG: duration: 0.086 ms statement: ROLLBACK TO SAVEPOINT "s140051465017088_x1" LOG: duration: 0.038 ms statement: RELEASE SAVEPOINT "s140051465017088_x1" LOG: duration: 3.101 ms statement: COMMIT ⼦トランザクションはROLLBACK 全体はCOMMIT
  46. 99 Djangoでのトランザクション管理 # django.db.transaction.Atomic#__exit__ def __exit__(self, exc_type, exc_value, traceback): connection

    = get_connection(self.using) : if connection.savepoint_ids: sid = connection.savepoint_ids.pop() : if connection.closed_in_transaction: ..... elif exc_type is None and not connection.needs_rollback: ..... else: connection.needs_rollback = False if connection.in_atomic_block: if sid is None: connection.needs_rollback = True else: : connection.savepoint_rollback(sid)
  47. 100 Djangoでのトランザクション管理 # django.db.transaction.Atomic#__exit__ def __exit__(self, exc_type, exc_value, traceback): connection

    = get_connection(self.using) : if connection.savepoint_ids: sid = connection.savepoint_ids.pop() : if connection.closed_in_transaction: ..... elif exc_type is None and not connection.needs_rollback: ..... else: connection.needs_rollback = False if connection.in_atomic_block: if sid is None: connection.needs_rollback = True else: : connection.savepoint_rollback(sid)
  48. 114 Djangoでのトランザクション管理 # https://github.com/django/django/blob/stable/1.11.x/django/db/backends/mysql/base.py def init_connection_state(self): assignments = [] :

    if self.isolation_level: assignments.append("TX_ISOLATION = '%s'" % self.isolation_level) if assignments: with self.cursor() as cursor: cursor.execute('SET ' + ', '.join(assignments)) 1.11でのMySQLの初期化
  49. 115 Djangoでのトランザクション管理 # https://github.com/django/django/blob/stable/2.2.x/django/db/backends/mysql/base.py def init_connection_state(self): assignments = [] :

    if self.isolation_level: assignments.append('SET SESSION TRANSACTION ISOLATION LEVEL %s' % self.isolation_level.upper()) if assignments: with self.cursor() as cursor: cursor.execute('; '.join(assignments)) 2.xでのMySQLの初期化
  50. 120 Celeryとトランザクション @shared_task def heavy_celery_task(hoge_id): hoge = Hoge.objects.get(hoge_id) hoge.attr +=

    1 hoge.save() def tasK_view(request): with transaction.atomic(): hoge = Hoge.objects.create() heavy_celery_task.delay(hoge.id) do_something() return HttpResponse('success')
  51. 121 Celeryとトランザクション @shared_task def heavy_celery_task(hoge_id): hoge = Hoge.objects.get(hoge_id) hoge.attr +=

    1 hoge.save() def tasK_view(request): with transaction.atomic(): hoge = Hoge.objects.create() heavy_celery_task.delay(hoge.id) do_something() return HttpResponse('success') celeryタスク View
  52. 126 Celeryとトランザクション def tasK_view(request): with transaction.atomic(): hoge = Hoge.objects.create() heavy_celery_task.delay(hoge.id)

    do_something() return HttpResponse('success') @shared_task def heavy_celery_task(hoge_id): hoge = Hoge.objects.get(hoge_id) hoge.attr += 1 hoge.save() ⾮同期
  53. 127 Celeryとトランザクション def tasK_view(request): with transaction.atomic(): hoge = Hoge.objects.create() heavy_celery_task.delay(hoge.id)

    do_something() return HttpResponse('success') @shared_task def heavy_celery_task(hoge_id): hoge = Hoge.objects.get(hoge_id) hoge.attr += 1 hoge.save() これなら問題ない
  54. 128 Celeryとトランザクション def tasK_view(request): with transaction.atomic(): hoge = Hoge.objects.create() heavy_celery_task.delay(hoge.id)

    @shared_task def heavy_celery_task(hoge_id): hoge = Hoge.objects.get(hoge_id) hoge.attr += 1 hoge.save() do_something() return HttpResponse('success') ワーストケースで 呼び出し元トランザクションが 終わる前にCeleryTaskが動く
  55. 130 Celeryとトランザクション @shared_task def heavy_celery_task(hoge_id): hoge = Hoge.objects.get(hoge_id) hoge.attr +=

    1 hoge.save() def tasK_view(request): with transaction.atomic(): hoge = Hoge.objects.create() # heavy_celery_task.delay(hoge.id) transaction.on_commit(lambda: heavy_celery_task.delay(hoge.id)) return HttpResponse(f'success')
  56. 131 Celeryとトランザクション @shared_task def heavy_celery_task(hoge_id): hoge = Hoge.objects.get(hoge_id) hoge.attr +=

    1 hoge.save() def tasK_view(request): with transaction.atomic(): hoge = Hoge.objects.create() # heavy_celery_task.delay(hoge.id) transaction.on_commit(lambda: heavy_celery_task.delay(hoge.id)) return HttpResponse(f'success') transactionのcommit時にceleryに タスクを投げることが保証される
  57. 132 Celeryとトランザクション # django.db.backends.base.base.BaseDatabaseWrapper#on_commit def on_commit(self, func): if self.in_atomic_block: #

    Transaction in progress; save for execution on commit. self.run_on_commit.append((set(self.savepoint_ids), func)) elif not self.get_autocommit(): raise TransactionManagementError('on_commit() cannot be used in manual transaction management') else: # No transaction in progress and in autocommit mode; execute # immediately. func()
  58. 133 Celeryとトランザクション # django.db.backends.base.base.BaseDatabaseWrapper#on_commit def on_commit(self, func): if self.in_atomic_block: #

    Transaction in progress; save for execution on commit. self.run_on_commit.append((set(self.savepoint_ids), func)) elif not self.get_autocommit(): raise TransactionManagementError('on_commit() cannot be used in manual transaction management') else: # No transaction in progress and in autocommit mode; execute # immediately. func()