From ef94d2ef105f69674d6d4a127b97b3b2c5361790 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 16 Jun 2026 17:52:51 +0200 Subject: [PATCH] fix(repo): make member/user foreign keys deferrable to avoid create_member deadlock Concurrent create_member transactions took FK FOR KEY SHARE (MultiXact) locks on shared rows across members/users/membership_fee_types and could form a cross-transaction cycle, producing intermittent PostgreSQL deadlocks (40P01) under load. Making the three foreign keys DEFERRABLE INITIALLY DEFERRED moves the check to commit time and breaks the cycle, without weakening integrity (NOT NULL and ON DELETE RESTRICT are unaffected). --- ...165309_make_member_user_fks_deferrable.exs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 priv/repo/migrations/20260616165309_make_member_user_fks_deferrable.exs diff --git a/priv/repo/migrations/20260616165309_make_member_user_fks_deferrable.exs b/priv/repo/migrations/20260616165309_make_member_user_fks_deferrable.exs new file mode 100644 index 0000000..2fa45fb --- /dev/null +++ b/priv/repo/migrations/20260616165309_make_member_user_fks_deferrable.exs @@ -0,0 +1,32 @@ +defmodule Mv.Repo.Migrations.MakeMemberUserFksDeferrable do + @moduledoc """ + Makes the members/users foreign keys DEFERRABLE INITIALLY DEFERRED. + + Concurrent `create_member` transactions take FK `FOR KEY SHARE` (MultiXact) + locks on these foreign keys at statement time and can form a cross-transaction + lock cycle, producing a PostgreSQL `deadlock_detected` (40P01). Deferring the + FK checks to commit time breaks the cycle. + + Constraint deferrability is not tracked by AshPostgres resource snapshots, so + this is a hand-written migration with raw `execute/2`. Do not regenerate it + via `mix ash_postgres.generate_migrations`. + """ + use Ecto.Migration + + def change do + execute( + "ALTER TABLE users ALTER CONSTRAINT users_member_id_fkey DEFERRABLE INITIALLY DEFERRED", + "ALTER TABLE users ALTER CONSTRAINT users_member_id_fkey NOT DEFERRABLE" + ) + + execute( + "ALTER TABLE users ALTER CONSTRAINT users_role_id_fkey DEFERRABLE INITIALLY DEFERRED", + "ALTER TABLE users ALTER CONSTRAINT users_role_id_fkey NOT DEFERRABLE" + ) + + execute( + "ALTER TABLE members ALTER CONSTRAINT members_membership_fee_type_id_fkey DEFERRABLE INITIALLY DEFERRED", + "ALTER TABLE members ALTER CONSTRAINT members_membership_fee_type_id_fkey NOT DEFERRABLE" + ) + end +end