Share
Embed
どうなってるの?Djangoのトランザクション
Django Congress2019 での資料です。
Djangoのtranasctionモジュールを中心にコードの話やトランザクション分離レベルの話などをします。
Denzow
May 18, 2019
Transcript
135 slides
-
3 ➡ でんぞう (@denzowill, denzow) ➡ Python歴 6年 (仕事で1.5年) ➡
scouty,inc シニアエンジニア(前職はDBサポートエンジニア) ➡ DBスペシャリスト(PostgreSQLが好き、•racleは得意だけど苦⼿) お前誰よ? -
4 ➡ でんぞう (@denzowill, denzow) ➡ Python歴 6年 (仕事で1.5年) ➡
scouty,inc シニアエンジニア(前職はDBサポートエンジニア) ➡ DBスペシャリスト(PostgreSQLが好き、•racleは得意だけど苦⼿) お前誰よ? LAPRAS -
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) -
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) -
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) -
11 Django と scouty(2018/5) ➡ Django 1.11 ➡ Django Celery
➡ Django でDDD的な設計 ➡ Django Channels でのWebsocket(予定) ➡ daphneでの本番運⽤ -
12 Django と LAPRAS(2019/5) ➡ Django 2.1 ➡ Django Celery
➡ Django でDDD的な設計 ➡ Django Channels でのWebsocket ➡ daphneでの本番運⽤ -
24 ➡ そもそもトランザクション ➡ RDBMSでのトランザクション ➡ RDBMS毎の違い ➡ Djangoのトランザクション ➡
トランザクション周りの⾟かった話 アジェンダ -
36 トランザクション分離レベル 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する
発⽣する READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣する SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない -
37 ダーティーリード BEGIN; BEGIN; INSERT INTO A...; SELECT ... FROM
A; 未commitの INSERT結果が⾒える -
38 ファジーリード BEGIN; BEGIN; UPDATE A... WHER ID=1; SELECT ...
FROM A; COMMIT; SELECT ... FROM A; さっきとレコードの 内容が違う -
39 ファントムリード BEGIN; BEGIN; INSERT INTO A...; SELECT ... FROM
A; COMMIT; SELECT ... FROM A; ⾏が増えてる -
40 トランザクション分離レベル 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する
発⽣する READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣する SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない -
46 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する 発⽣する
READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣する SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない MySQLのREPETABLE READ -
47 分離レベル ダーティーリード ファジーリード ファントムリード READ UNCOMMITTED 発⽣する 発⽣する 発⽣する
READ COMMITTED 発⽣しない 発⽣する 発⽣する REPETABLE READ 発⽣しない 発⽣しない 発⽣しない SERIALIZABLE 発⽣しない 発⽣しない 発⽣しない MySQLのREPETABLE READ -
57 Djangoのトランザクションのデフォルト AutoCommit + REPETABLE READ AutoCommit + READ COMMITTED
AutoCommit + READ COMMITTED -
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側のデフォルト値になる -
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を設定する -
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 指定されてなければ サーバ⾃体のデフォルト値を使う -
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, } } } -
65 Djangoでのトランザクション管理 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}') -
66 Djangoでのトランザクション管理 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}') -
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 -
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}') -
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で囲まれており トランザクションとして処理されている -
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) -
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 普通に呼び出した場合 -
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}') -
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 -
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にする -
76 transaction.atomic() DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME':
'djangodb', 'USER': 'hoge', 'PASSWORD': 'password', 'HOST': 'localhost', 'PORT': '5432', 'ATOMIC_REQUESTS': True, } } -
77 transaction.atomic() DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME':
'djangodb', 'USER': 'hoge', 'PASSWORD': 'password', 'HOST': 'localhost', 'PORT': '5432', 'ATOMIC_REQUESTS': True, } } -
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()で全体をラップして処理 -
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}') -
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}') -
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 -
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 -
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を発⾏ その識別⼦をリスト(スタック)に保持していく -
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を決める -
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コマンドを発⾏する -
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}') -
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}') -
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}') -
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 -
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 -
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}') -
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}') -
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を追加 -
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 -
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 -
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) -
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) -
106 MySQLバージョンアップの罠 ➡ Django 1.11 ➡ ATOMIC_REQUEST: Trueで運⽤ ➡ バックエンドはMySQL
5.6 ➡ READ COMMITTEDに変更 -
107 MySQLバージョンアップの罠 ➡ Django 1.11 ➡ ATOMIC_REQUEST: Trueで運⽤ ➡ バックエンドはMySQL
5.6 ➡ READ COMMITTEDに変更 ロックがだいぶましになった -
108 MySQLバージョンアップの罠 ➡ Django 1.11 ➡ ATOMIC_REQUEST: Trueで運⽤ ➡ バックエンドはMySQL
5.7 ➡ READ COMMITTEDに変更 何も問題はない -
109 MySQLバージョンアップの罠 ➡ Django 1.11 ➡ ATOMIC_REQUEST: Trueで運⽤ ➡ バックエンドはMySQL
8.0 ➡ READ COMMITTEDに変更 -
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の初期化 -
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の初期化 -
116 MySQLバージョンアップの罠 ➡ Django 2.1 ➡ ATOMIC_REQUEST: Trueで運⽤ ➡ バックエンドはMySQL
5.7 ➡ READ COMMITTEDに変更 -
117 MySQLバージョンアップの罠 ➡ Django 2.1 ➡ ATOMIC_REQUEST: Trueで運⽤ ➡ バックエンドはMySQL
8.0 ➡ READ COMMITTEDに変更 -
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') -
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 -
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() ⾮同期 -
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() これなら問題ない -
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が動く -
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') -
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に タスクを投げることが保証される -
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() -
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()