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

排他制御のためだけに渋々 Redis 使ってませんか?

mpyw
November 11, 2022

排他制御のためだけに渋々 Redis 使ってませんか?

MySQL/Postgres におけるトランザクション分離レベル - Speaker Deck
https://speakerdeck.com/mpyw/postgres-niokerutoranzakusiyonfen-li-reberu

上記からの続編です。

排他制御のためだけに Redis 渋々使ってませんか?データベース単独でアドバイザリーロックできるよ!
https://zenn.dev/mpyw/articles/rdb-advisory-locks

上記の記事を解説する補佐的なスライドとして,株式会社ゆめみの社内勉強会にて発表しました。

mpyw

November 11, 2022
Tweet

More Decks by mpyw

Other Decks in Programming

Transcript

  1. データベースにおける排他制御の復習 • Strict 2-Phase Locking (S2PL) から始まった ◦ ロックを徐々に獲得していき,トランザクションのコミットで一気に開放する •

    トランザクション分離レベルの変遷と Multi-Version Concurrency Control (MVCC) の登場 ◦ READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE ◦ これらだけで説明しきれないアノマリーと新しい分離レベルのもろもろが登場 ◦ MVCC では読み取り専用のスナップショットとデータ本体を分離し,性能と部分的一貫性を両立 • MySQL/Postgres での REPEATABLE READ 以上の実装 ◦ MySQL は一貫して「予めロックしておく」悲観的制御だけでなんとかする ◦ Postgres は「競合したら失敗させる」楽観的制御が入ってくる 一方で,両者とも (REPEATABLE READ 以上にせずとも) SELECT … FOR UPDATE の Locking Read で悲観的ロック対象行を明示的にコントロール することができる
  2. データベースにおける排他制御の復習 • Strict 2-Phase Locking (S2PL) から始まった ◦ ロックを徐々に獲得していき,トランザクションのコミットで一気に開放する •

    トランザクション分離レベルの変遷と Multi-Version Concurrency Control (MVCC) の登場 ◦ READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE ◦ これらだけで説明しきれないアノマリーと新しい分離レベルのもろもろが登場 ◦ MVCC では読み取り専用のスナップショットとデータ本体を分離し,性能と部分的一貫性を両立 • MySQL/Postgres での REPEATABLE READ 以上の実装 ◦ MySQL は一貫して「予めロックしておく」悲観的制御だけでなんとかする ◦ Postgres は「競合したら失敗させる」楽観的制御が入ってくる 一方で,両者とも (REPEATABLE READ 以上にせずとも) SELECT … FOR UPDATE の Locking Read で悲観的ロック対象行を明示的にコントロール することができる 今回はこの文脈のお話
  3. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC id=2 のレコードの 大文字小文字を反転したい id=2 のレコードの 大文字小文字を反転したい
  4. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC BEGIN; SELECT * FROM data WHERE id = 2 FOR UPDATE;
  5. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC BEGIN; SELECT * FROM data WHERE id = 2 FOR UPDATE;
  6. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC BEGIN; SELECT * FROM data WHERE id = 2 FOR UPDATE;
  7. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC BEGIN; SELECT * FROM data WHERE id = 2 FOR UPDATE;
  8. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 bbb 3 CCC UPDATE data SET value = ‘bbb’ WHERE id = 2; COMMIT; BEGIN; SELECT * FROM data WHERE id = 2 FOR UPDATE;
  9. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 bbb 3 CCC UPDATE data SET value = ‘bbb’ WHERE id = 2; COMMIT; BEGIN; SELECT * FROM data WHERE id = 2 FOR UPDATE;
  10. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 bbb 3 CCC BEGIN; SELECT * FROM data WHERE id = 2 FOR UPDATE;
  11. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC UPDATE data SET value = ‘BBB’ WHERE id = 2; COMMIT;
  12. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC UPDATE data SET value = YYY WHERE id = 2; COMMIT;
  13. READ COMMITTED + Locking Read で成功 id value 1 AAA

    2 BBB 3 CCC UPDATE data SET value = YYY WHERE id = 2; COMMIT; 2人が更新したらもとに戻る= Lost Update は回避できた
  14. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB id=3 のレコードが無ければ value=XXX で作りたい id=3 のレコードが無ければ value=YYY で作りたい
  15. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB BEGIN; SELECT * FROM data WHERE id = 3 FOR UPDATE;
  16. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB BEGIN; SELECT * FROM data WHERE id = 3 FOR UPDATE; ロックがかからない!
  17. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB BEGIN; SELECT * FROM data WHERE id = 3 FOR UPDATE;
  18. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB BEGIN; SELECT * FROM data WHERE id = 3 FOR UPDATE; ロックがかからない!
  19. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB 3 XXX INSERT INTO data VALUES (3, ‘XXX’); COMMIT;
  20. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB 3 XXX INSERT INTO data VALUES (3, ‘XXX’); COMMIT;
  21. READ COMMITTED + Locking Read で失敗 id value 1 AAA

    2 BBB 3 XXX 3 YYY INSERT INTO data VALUES (3, ‘YYY’); COMMIT; • 外部キー制約があればエラー • 外部キー制約がなければ不整合レコードが作られてしまう
  22. (MySQL) REATABLE READ + Locking Read で成功 id value 1

    AAA 2 BBB BEGIN; SELECT * FROM data WHERE id = 3 FOR UPDATE; ギャップロック! 影響範囲広すぎ…
  23. (Postgres) SERIALIZABLE + Locking Read で成功 id value 1 AAA

    2 BBB BEGIN; SELECT * FROM data WHERE id = 3 FOR UPDATE; SIRead ロックによりエラーとして検知できる (外部キー制約に依存しない) しかし SERIALIZABLE はエラー復帰制御が難しい… id=3 で FOR UPDATE してる人がいるから 他の人来たら失敗して もらうゾウ
  24. A: 専用テーブルのレコードロック id value 1 AAA 2 BBB id=3 のレコードが無ければ

    value=XXX で作りたい 最初に mutex テーブルの action=data_insert を 確認しよう! id=3 のレコードが無ければ value=XXX で作りたい 最初に mutex テーブルの action=data_insert を 確認しよう! action data_insert … data mutex
  25. A: 専用テーブルのレコードロック id value 1 AAA 2 BBB id=3 のレコードが無ければ

    value=XXX で作りたい 最初に mutex テーブルの action=data_insert を 確認しよう! id=3 のレコードが無ければ value=XXX で作りたい 最初に mutex テーブルの action=data_insert を 確認しよう! action data_insert … data mutex 「data テーブルに INSERT する」を 同時に1人しかできないように合意形成
  26. Postgres 組み込みのアドバイザリーロック関数 【注意】トランザクションのネスト対応をフレームワークや ORM がやっていると厳しい • 最上位のトランザクション以外は SAVEPOINT が実態なので,自動開放のタイミングが噛み合わない •

    Postgres はトランザクション中にエラーが発生すると ROLLBACK しかできなくなる ↓ 手動開放するほうの関数を選びつつ,ロールバック先の SAVEPOINT をこまめに取るしかない ↓ ライブラリに任せるとラク