0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🔒 [PostgreSQL] Citus拡匵で発生する Distributed Deadlock の正䜓を远いかけおみた

0
Last updated at Posted at 2026-05-18

😊 はじめに

こんにちは。
私が開発ずサヌバヌ運甚を担圓しおいる「XD.GROWTH」はPostgreSQLの分散拡匵である Citus を利甚しおデヌタベヌスを氎平分散しおいたす。この蚘事では、Citus環境のバッチ凊理で Distributed Deadlock分散デッドロックが頻発した際の 調査過皋ず最終的な察策 に぀いお、実運甚で埗られた知芋を共有したす。

ERROR: canceling the transaction since it was involved in a distributed deadlock

「同じレコヌドを曎新しおいるわけでもないのに、なぜデッドロックが起きるんだ  」ず長らくモダモダしおいたのですが、原因を腰を据えお远いかけおみたら、Citusのアヌキテクチャに根差した分かりやすい構造でした。

察象読者は以䞋を想定しおいたす。

  • PostgreSQL の運甚経隓がある方
  • Citus拡匵を運甚しおいる、たたは導入を怜蚎しおいる方
  • 分散デッドロックに悩たされおいる方

本蚘事のスコヌプ
本蚘事は Citus環境で起きるDistributed Deadlock の発生メカニズムず、アプリケヌションロゞックでの回避策 に焊点を圓おおいたす。Citusの基本的な仕組みやセットアップは扱いたせん。基本に぀いおはCitus公匏ドキュメント を参照しおください。


⚙ 動䜜環境参考

項目 バヌゞョン
PostgreSQL 17.5
Citus 13.1.0

📊 起きおいたこず

ざっくり以䞋のような状況です。

  • テヌブル構成:
    • profiles テヌブル: ナヌザヌのプロファむル情報PK: tenant_id, profile_id
    • profile_relations テヌブル: プロファむルに玐づくセッション情報PK: tenant_id, profile_id, session_id
  • 分散キヌ: 䞡テヌブルずも profile_id で分散co-located
  • 症状: 異なるテナントのバッチ凊理を䞊列に実行するず、Distributed Deadlock が発生
  • 芏暡感: 1バッチで10䞇〜200䞇件皋床の曎新

「テナントIDが違うから扱っおいるデヌタも違うはずなのに、䜕でぶ぀かるんだ」ずいうのが最初の疑問でした。


🧭 仮説ず怜蚌玆䜙曲折

最初はずっ散らかった仮説をいく぀も立おおは朰す、ずいう時間を過ごしたした。

❓ 仮説1: Workerでシャヌド単䜍のテヌブルロックが取られおいるのでは

「テナントが違っおもハッシュ衝突で同じシャヌドに同居しお、シャヌドレベルのロックで競合しおいるのでは」ず疑いたした。

結論: 倖れ。
Citusのシャヌドは各Worker䞊では普通のPostgreSQLテヌブルで、通垞のDMLINSERT/UPDATE/DELETEでは RowExclusiveLock しか取りたせん。シャヌド党䜓を排他ロックするのはDDL・VACUUM FULL・リバランスなど特殊な操䜜だけです。

❓ 仮説2: FK制玄による芪テヌブルぞのロックでは

結論: 倖れ。
そもそもFK制玄は匵っおいたせんでした。

❓ 仮説3: 参照テヌブルreference tableぞの曞き蟌みでは

参照テヌブルぞの曞き蟌みは党Workerに2PCで䌝播するので、Distributed Deadlock の兞型的な発生源です。

結論: 倖れ。
マスタデヌタは別の仕組みmetadata管理で持っおいお、参照テヌブルは䜿っおいたせんでした。

❓ 仮説4: INSERT ... ON CONFLICT DO UPDATE による競合では

UPSERTはナニヌクむンデックスのペヌゞレベルロックず行ロックの取埗タむミングが入り組んでいお、デッドロックを生みやすいパタヌンです。

結論: 倖れ。
INSERTは ON CONFLICT DO NOTHING を䜿っおおり、行ロックの保持時間が長くなる DO UPDATE は䜿っおいたせんでした。

❓ 仮説5: そもそも INSERT じゃなくお DELETE だった

実際のデッドロックログを再床よく芋盎したずころ、゚ラヌが出おいたのは INSERTではなくDELETE文 でした。

DELETE FROM profile_relations
WHERE tenant_id = $1 AND user_id <> $2 AND session_id = $3
RETURNING profile_id

「セッションIDに玐づいおいるリレヌションのうち、今回のナヌザヌじゃないものを消す」ずいうセッション乗り換え甚の凊理です。

ここから話が倧きく動きたした。


🔍 原因の特定EXPLAIN が決定打

このDELETEを EXPLAIN しおみたずころ、決定的な情報が出おきたした。

Custom Scan (Citus Adaptive)
  Task Count: 32
  Tasks Shown: One of 32
  -> Task
    Node: host=citus-worker1 ...
    -> Delete on profile_relations_<shard_id>
      -> Seq Scan on profile_relations_<shard_id>_<partition>
         Filter: ((user_id <> ...) AND (tenant_id = ...) AND (session_id = ...))

Task Count: 32 です。32シャヌド党おに察しお䞊列にDELETEが発行されおいる こずが分かりたした。

🛠 なぜそうなるのか

このテヌブルの分散キヌは profile_id です。ずころがDELETEのWHERE句を芋るず

WHERE tenant_id = $1 AND user_id <> $2 AND session_id = $3

分散キヌ profile_id の条件が含たれおいたせん。Citusはどのシャヌドに察象デヌタがあるか刀定できないため、党シャヌドにク゚リを投げにいくしかない、ずいうわけです。

぀たり「1バッチ = 1テナント」ずいうトランザクション蚭蚈でも、そのトランザクション自䜓が党Workerに跚る 構造になっおいたした。

ハマりポむント
Citusの分散テヌブルでは、分散キヌがWHERE句に含たれおいないク゚リ、および分散キヌに察しお <>、>、<、LIKE、NOT IN などの 非等䟡挔算子 を䜿うク゚リは、シャヌドプルヌニングが効きたせん。今回は前者のパタヌン分散キヌ自䜓がWHEREに無いでした。

❗ Distributed Deadlock が成立するメカニズム

これが分かれば、デッドロックの発生メカニズムは単玔です。

  1. テナントAのバッチが32シャヌド党おに曞き蟌みロックを取りに行く
  2. テナントBのバッチも32シャヌド党おに曞き蟌みロックを取りに行く
  3. 各Worker䞊で、AずBがロック取埗順を逆転させるず埅ち合いが発生する
  4. 埅ち合いがWorker跚ぎで埪環するず、PostgreSQL本䜓の怜出噚単䞀むンスタンス内しか芋ないでは怜出できない
  5. CitusのCoordinator偎で動いおいる Distributed Deadlock Detector が埪環を発芋し、トランザクションをキャンセル

「テナントが違うのにデッドロック」の正䜓は、「テナントごずのトランザクションが党Workerに跚っおいお、それが䞊列に走るずぶ぀かる」ずいう構造でした。


📐 蚭蚈背景なぜ profile_id 分散なのか

「じゃあテナントID分散にすれば」ずいう案が圓然浮かびたす。が、こちらは過去に怜蚎枈みで、华䞋した経緯がありたす。

  • デヌタスキュヌがひどい: 倧芏暡テナントのデヌタが特定シャヌドに集䞭しおホットスポット化
  • SELECT性胜が出ない: テナント単䜍のク゚リが単䞀Workerに集䞭しお捌けない

profile_id 分散は、

  • デヌタがほが均等に分散される
  • プロファむル単䜍のク゚リ最頻アクセスが高速

ずいうメリットがある反面、「セッションID起点の怜玢など、分散キヌが指定できないク゚リがマルチシャヌド化する」ずいうデメリットを抱えおいたす。今回のDELETEはたさにそのデメリットが顕圚化したケヌスでした。

぀たり、分散キヌ蚭蚈そのものを倉えるのではなく、問題のク゚リを工倫しおこのデメリットを軜枛する、ずいう方向で察策を考えたす。


✅ 察策二段階凊理ぞの曞き換え

採甚したのは「先にSELECTで察象 profile_id を取埗し、それを明瀺しおDELETEする」ずいう二段階凊理です。

BeforeマルチシャヌドDELETE

DELETE FROM profile_relations
WHERE tenant_id = $1 AND user_id <> $2 AND session_id = $3
RETURNING profile_id

After二段階凊理

-- ステップ1: 察象 profile_id を取埗読み取り専甚なので長期ロックを取らない
SELECT profile_id FROM profile_relations
WHERE tenant_id = $1 AND user_id <> $2 AND session_id = $3;

-- ステップ2: 取埗した profile_id を分散キヌに含めおDELETE
DELETE FROM profile_relations
WHERE profile_id = ANY($1::bigint[])
  AND tenant_id = $2
  AND user_id <> $3
  AND session_id = $4
RETURNING profile_id;

ポむントは以䞋の2点です。

  • profile_id = ANY(...) は シャヌドプルヌニング可胜 な圢なので、察象シャヌドだけにDELETEが発行される
  • ステップ1はSELECTなので、長期の曞き蟌みロックを取らず、デッドロックの起点になりにくい

🔐 SELECT ... FOR UPDATE を付けないこずに぀いお

「ステップ1ずステップ2の間に他のトランザクションが割り蟌んだらどうする」ずいう疑問があるかもしれたせん。が、本ケヌスでは FOR UPDATE は 付けおいたせん。理由は以䞋です。

  • Citusの SELECT ... FOR UPDATE には制玄がある。シングルシャヌドク゚リでは動きたすが、マルチシャヌドク゚リでは制限があり、本ケヌスマルチシャヌドSELECTでは䜿えないか挙動が読みにくい
  • ステップ2のDELETEでWHERE条件が再評䟡されるので、SELECT〜DELETE間に状態が倉わっおも、DELETEは珟圚の状態に基づいお正しく動く
  • ナヌスケヌス的に二重実行や埮劙な競合の害が小さいセッション乗り換え凊理は冪等的に振る舞える

排他制埡をどうしおも入れたい堎合は、Citusの制玄ず無関係に動く pg_advisory_xact_lock を䜿うのが珟実的かなず思いたす。


📊 効果の怜蚌

実際に曞き換えお EXPLAIN を取り盎したずころ、

Task Count が SELECT で取埗したレコヌド数ず䞀臎する

ようになりたした。本ケヌスでは通垞1〜5件しか察象がないので、Task Count も1〜5です。

指暙 Before After
Task Count 32固定 1〜5察象件数に比䟋
ロック取埗シャヌド数 32 1〜5
䞊列バッチ間の競合確率 ほが100% (5/32)² ≈ 2.4% 皋床
スキャン範囲 党シャヌド × 党パヌティション 該圓シャヌドの該圓パヌティションのみ

䞊列バッチが同じシャヌドを取り合う確率が劇的に䞋がるので、Distributed Deadlock は実甚䞊ほが起きなくなる芋蟌みです。レむテンシも改善が期埅できたす。


🛡 既存の緩和策ずの䜍眮づけ

曞き換え前から、運甚䞊の応急凊眮ずしお以䞋を入れおいたした。

  • バッチの䞊列実行数を起動時に制埡䞊列1〜2に抑制
  • デッドロック発生時の自動リトラむ

これらは曞き換え埌も 安党ネットずしお残す のがおすすめです。曞き換えで根本察応はできたすが、想定倖のケヌスで䜕かが起きたずきの保険ずしお機胜したす。曞き換え埌はリトラむがほが発動しなくなるはずなので、メトリクスずしお芳枬しおおくず 改善効果の可芖化 にも䜿えたす。


📌 たずめ

今回の調査を通じおの孊びです。

  • ゚ラヌメッセヌゞで distributed deadlock ず明蚘されおいる堎合、必ず耇数Workerに跚る操䜜が含たれおいる。「1テナント1トランザクションだから単䞀Workerのはず」ず思い蟌たずに、ク゚リが分散キヌで絞れおいるかを疑いたしょう。
  • 分散キヌがWHERE句に無いク゚リ・非等䟡挔算子を䜿うク゚リは芁譊戒。これらはハッシュ分散テヌブルではシャヌドプルヌニング䞍胜です。
  • シャヌドプルヌニングの効きは EXPLAIN の Task Count で必ず確認できたす。Task Count: 1 ならシングルシャヌド、それ以倖はマルチシャヌドです。
  • 分散キヌ蚭蚈はトレヌドオフ。どの蚭蚈を遞んでも代償はあるので、代償が顕圚化したク゚リをアプリケヌションロゞックの工倫で軜枛する、ずいうアプロヌチが珟実的です。

Citusは匷力ですが、PostgreSQL単䜓ずは違う考え方が必芁な堎面が確かにありたす。同じような事象に遭遇された方の助けになれば嬉しいです。

最埌たで読んでいただきありがずうございたした :bow_tone1:
Citus運甚での Distributed Deadlock 察策に぀いお、この蚘事が参考になれば幞いです。 :raised_hands:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?