「重複キーが一意性制 auth_permission_pkey に違反しています」それは突然やってきた
この投稿は約3分で読めます
「うし、レビュー終わった!ブランチ切り替えて実装の続きしよう!」と、いつものようにブランチを切り替えたときでした。
それは突然やってきたのです。
File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 84, in _execute
return self.cursor.execute(sql, params)
django.db.utils.IntegrityError: 重複キーが一意性制約"auth_permission_pkey"に違反しています
DETAIL: キー (id)=(177) はすでに存在します。
Django のマイグレーションで上記のような IntegrityError
が発生し、ローカル環境が立ち上がらなくなってしまいました。showmigrations
でマイグレーションの適用状況を確認すると、マイグレーションはすべて適用されていました。
思い出すんだ、数時間前の自分の行動を・・・
遡ること数時間前、こんなことをしていました。
- モデルのコードレビューで、マイグレーションやロールバックを何度か行った
- 途中、ロールバックし忘れたままブランチを切り替えてマイグレーションを行った
おそらく、上記の作業中に IntegrityError
を引き起こすような何かがあったのだと思います。
そして、当該モデルの実装をしていたメンバーの環境でも同じエラーが出ていたらしく、下記コマンドで修正したとのことでした。
が、もう少し自分でも調べてみることにしました。
SELECT setval('auth_permission_id_seq', (SELECT MAX(id) FROM auth_permission));
ローカル環境が落ちればメンタルも落ちる
落ちまくりのローカル環境と精神衛生を回復すべく、同じような問題に遭遇している人がいないか検索したところ、以下の投稿を見つけることができました。
django.db.utils.IntegrityError: duplicate key value violates unique constraint “auth_permission_pkey” (stackoverflow)
上記の Answer で、以下のブログが参照されていました。
How to reset the primary key sequence in PostgreSQL with Django (Calazan.com)
ブログの内容をざっくりかい摘むと、
- 新しいモデルを追加すると、
auth_permission
テーブルにレコードが追加される - マイグレーションすると、上記テーブルの pkey のシーケンスが自動的にインクリメントされる
- そのため、既存レコードの pkey とコンフリクトしない ID になるまでマイグレーションを何度か実行すればエラーは出なくなる
- Django には、シーケンスをリセットするための sqlsequencereset コマンドが用意されている
というような内容が書かれていました。
ふむふむ、なるほど。さっそく手元でも試してみました。
落ちたらあとは登るだけ ()
まずは、migrate
を実行してみると、(id)=(177)
だった部分が (id)=(178)
にインクリメントされました。
django.db.utils.IntegrityError: 重複キーが一意性制約"auth_permission_pkey"に違反しています
DETAIL: キー (id)=(178) はすでに存在します。
上記ブログによると、このまま何度か migrate
を実行すれば、いずれは重複しない ID に到達してマイグレートできるようになるとのこと。
ですが、このまま migrate
で解決しちゃうのはもったいないので (?)、sqlsequencereset
コマンドも試してみました。
$ python manage.py sqlsequencereset auth
...
BEGIN;
SELECT setval(pg_get_serial_sequence('"auth_permission"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "auth_permission";
SELECT setval(pg_get_serial_sequence('"auth_group_permissions"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "auth_group_permissions";
SELECT setval(pg_get_serial_sequence('"auth_group"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "auth_group";
COMMIT;
これは便利、シーケンスリセット用の SQL を手に入れることができました!
この SQL を dbshell で実行したところ、無事にシーケンスがリセットされ、ローカル環境も精神衛生も回復しました!
まとめ
- モデル作成、マイグレート、ロールバック、ブランチ切り替えなどをあれこれ試していると、
auth_permission
のpkey
のシーケンスが、既存のものと同期が取れなくなることがある - 同期が取れないと、「重複キーが一意性制 auth_permission_pkey に違反しています」がやって来る
SELECT setval('auth_permission_id_seq', (SELECT MAX(id) FROM auth_permission));
を実行しても直せるが、sqlsequencereset
を使うのが公式な解決手段なもよう
以上、現場からでした。