こんにちは、takaramです。
私が担当しているサービスでは、RDBMSにPostgreSQLを利用しています。今回は業務で行ったデッドロックの調査で知った、PostgreSQLの仕様に関して書いていきます。
ここでは デッドロック や パーティショニング といった用語が登場しますが、今回これらの説明は割愛します。
パーティショニングについてご存じでない方は、まずはこちらの記事をお読みください。
tech-blog.rakus.co.jp qiita.com
なお、この記事の内容はPostgreSQL 12.3で確認したものです。
テーブルのロック
PostgreSQLのロックについては、以下のドキュメントに詳しく書かれています。
これによると、テーブルレベルのロックだけで8種類が存在します。LOCK TABLE
文でロックが獲得されるのは言うまでもありませんが、INSERT文やUPDATE文、SELECT文ですら対象のテーブルのロックを獲得します。
さらに、実は以下のような操作でもテーブルのロックが獲得されます。
子テーブルの作成
PostgreSQLの継承機能を利用し子テーブルを作成すると、親テーブルのロックが獲得されます。例として、psqlコマンドで以下のようなSQLを実行してみます。
partition_test=# CREATE TABLE parent (); partition_test=# BEGIN; BEGIN partition_test=# CREATE TABLE child () INHERITS (parent); CREATE TABLE
ここで別のコンソールからロックの状態を確認してみましょう。PostgreSQLの場合、pg_locks
を参照することでロック状態が確認できます。結果を見てみると、parent
テーブルのSHARE UPDATE EXCLUSIVEロックが獲得されているのがわかります。
partition_test=# SELECT d.datname, l.locktype, l.relation::regclass, l.mode FROM pg_locks l LEFT JOIN pg_database d ON l.database = d.oid WHERE l.pid != pg_backend_pid(); datname | locktype | relation | mode ----------------+---------------+----------+-------------------------- | virtualxid | | ExclusiveLock partition_test | object | | AccessShareLock partition_test | relation | 16428 | AccessExclusiveLock | transactionid | | ExclusiveLock partition_test | relation | parent | ShareUpdateExclusiveLock (5 行)
パーティションの追加
PostgreSQL 10以降では宣言的パーティショニングがサポートされていますが、パーティションテーブルにパーティションを追加する際に、パーティションテーブルのロックが獲得されます。これもpsqlコマンドで確認してみます。
partition_test=# CREATE TABLE partition (id integer) PARTITION BY LIST (id); CREATE TABLE partition_test=# BEGIN; BEGIN partition_test=# CREATE TABLE partition_1 PARTITION OF partition FOR VALUES IN (1); CREATE TABLE
別コンソールのpsqlで確認すると、パーティションテーブルのACCESS EXCLUSIVEロックが獲得されているのが確認できます。
partition_test=# SELECT d.datname, l.locktype, l.relation::regclass, l.mode FROM pg_locks l LEFT JOIN pg_database d ON l.database = d.oid WHERE l.pid != pg_backend_pid(); datname | locktype | relation | mode ----------------+---------------+-----------+--------------------- | virtualxid | | ExclusiveLock partition_test | object | | AccessShareLock partition_test | relation | 16434 | AccessExclusiveLock partition_test | relation | partition | AccessExclusiveLock | transactionid | | ExclusiveLock (5 行)
デッドロックが起こりそうな例
上記のようにPostgreSQLではパーティションの追加などの操作でもロックが発生します。こうした挙動を把握、意識していないと、うっかりデッドロックを引き起こしてしまう場合があります。
前提
あるアプリケーションにユーザとグループが存在し、一人のユーザは複数のグループに所属することができるとします。
これをデータベース上で表現しようとすると、ユーザとグループは多対多の関係なので、ユーザテーブル、グループテーブルと、この2つを結ぶ中間テーブルを作成することになると思います。
この中間テーブルはレコード数がとても多くなりそうなので、グループごとにパーティショニングすることにしました。グループテーブルにレコードを挿入するたびに、トリガーで新しいパーティションを追加します。
以上をSQLで表現すると、以下のようになります。
CREATE TABLE users ( id serial PRIMARY KEY, name text ); CREATE TABLE groups ( id serial PRIMARY KEY, name text ); CREATE TABLE user_group ( user_id integer, group_id integer ) PARTITION BY LIST (group_id); CREATE FUNCTION groups_insert_trigger_func() RETURNS trigger AS $$ BEGIN EXECUTE 'CREATE TABLE user_group_g' || NEW.id || ' PARTITION OF user_group FOR VALUES IN (' || NEW.id || ')'; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER groups_insert_trigger AFTER INSERT ON groups FOR EACH ROW EXECUTE FUNCTION groups_insert_trigger_func();
デッドロックの発生
上記のようなテーブル、トリガーを作成した状態で、2つのトランザクションA, Bで以下のような順序で操作を行うとデッドロックが発生します。
トランザクション | SQL |
---|---|
A | LOCK TABLE user_group; |
B | INSERT INTO groups (name) VALUES ('group 1'); |
A | LOCK TABLE groups; |
トランザクションBでINSERT文を実行すると、
- groupsテーブルをロックし行挿入
- トリガーでuser_groupのパーティションを作成
- user_groupのロック待ち
となり、その後でトランザクションAがgroupsのロックを獲得しようとするとデッドロック状態になってしまいます。
トランザクションBでは見た目上はgroupsテーブルに挿入しているだけなので、これでデッドロックが発生するとは気づきにくそうです。
ここでは例として宣言的パーティショニングを使ったケースを挙げていますが、子テーブルの作成でもロックが発生するため、継承によるパーティショニングでも同様にデッドロックが起こり得ます。ただし宣言的パーティショニングの場合、トランザクションBが獲得しようとするuser_groupのロックはもっとも厳しいACCESS EXCLUSIVEロックなので、LOCK TABLE user_group;
をSELECT * FROM user_group;
などに置き換えても同じくデッドロックしてしまいます。
対策
このようなデッドロックを回避する方法の一つは、テーブルのロック順序に気をつけることです。
上の例でいうと、トランザクションAでuser_groupよりも先にgroupsをロックすると、デッドロックが発生しなくなります。
まとめ
- 子テーブルの作成(
CREATE TABLE ... INHERITS ...
)の際、親テーブルがSHARE UPDATE EXCLUSIVEロックされる - パーティションの作成(
CREATE TABLE ... PARTITION OF ...
)の際、大元のパーティションテーブルがACCESS EXCLUSIVEロックされる
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.comラクスDevelopers登録フォーム
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com