Rebounder Tech Blog

運用している当事者が書く、本番システムの記録。

マイグレーション番号衝突を直した1時間後、0041_console_name.sqlでまた衝突した

公開 読了時間 約5分執筆: Rebounder 開発チーム(当該システムの運用当事者)

※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。

結論

手動リネームで直したマイグレーション番号の衝突は、直した瞬間のトランクにしか効かない。59分後に別の2本のブランチがまとめてマージされれば、直した本人が選んだ番号ごと再び衝突する。

結論

手動リネームでマイグレーション番号の衝突を直した58分後に、同じ番号でまた衝突しました。 別のfeatureブランチが2本、独立に同じ番号を選んでトランクへ入ってきたためです。直し方自体は前回と同じ「後発をリネーム」でしたが、今回は当事者が3組に増えていました。

症状

supabase/migrations/ は手番号(0001 0002 …という単純な連番)で管理しています。ある晩、この連番が同じ数字のまま3つ並びました。

0041_console_name.sql              ← 23:05 マージ
0041_school_postal.sql             ← 23:14 マージ(直前の0040重複の修正としてリネーム)
0041_share_links_report_emailed.sql ← 00:12 マージ

3つとも add column if not existsalter column ... set default だけの追加専用SQLで、対象テーブルも app_settings / schools / share_links とすべて別だったため、本番への実害はありませんでした。危険だったのは前回と同じで、新規環境やstagingを連番順に流したときにどれが先に走るか環境ごとに変わりうる、という潜在的な不確実性です。

原因

コミットのタイムスタンプを並べると、事情が見えてきます。

846f8ae 23:05:44  feat(admin): コンソール表示名を統一          → 0041_console_name.sql
b4a2828 23:14:56  fix(migrations): 二重採番 0040 を解消        → 0040_school_postal.sql → 0041 へリネーム
2085eba 00:12:43  merge(integrate): zero-touch-automation      → 0041_share_links_report_emailed.sql
7b2746b 00:15:38  chore(migrations): 番号重複を整理(0041×3→0040..0043)

846f8ae(23:05)がトランクへ入った時点で 0041 は console_name の持ち物になっていました。ところが b4a2828(23:14)は、別々のPR(0040_share_links_report0040_school_postal)が同じ 0040 番で衝突した問題を直すために、後発の school_postal を「次に空いている番号」として 0041 へリネームしています。このリネームを行ったブランチは 846f8ae の存在をトランク上で把握できていなかったため、衝突を直す作業自体が、既に埋まっていた番号を選んで新しい衝突を作っていました

さらに1時間もしないうちに、別系統の zero-touch-automation ブランチ(マージコミット 2085eba、00:12)が 0041_share_links_report_emailed.sql を追加してマージされました。このブランチの元コミット(4353ec2、23:03)はどちらのリネームより前に作られていたため、独立に 0041 を選んでいます。

この夜は複数の長期化したfeatureブランチ(学校名寄せUI・書類署名サブシステム・月次レポート自動化など)をまとめてトランクへ統合する作業をしていました。ふだんなら1本ずつマージするところ、23:05から00:15までの70分間に4本のマージが連続し、「次の番号を確認してから採番する」という手順が追いつかない速度でマージが重なったことが、1時間足らずでの再衝突につながっています。

直す

7b2746b(00:15)で、残っていた重複を整理しました。3本のうち最初にトランクへ定着していた share_links_report_emailed はそのまま 0041 に残し、後発の2本をリネームしています。

0041_school_postal.sql             → 0042_school_postal.sql
0041_console_name.sql              → 0043_console_name.sql
0041_share_links_report_emailed.sql → (そのまま)

ここでも前回同様、ファイルの中身は1バイトも変えていません。ただし副作用として、リネーム後のファイルにも古い番号のヘッダコメントがそのまま残りました

-- 0040: 学校の郵便番号(...)     ← ファイル名は 0042_school_postal.sql
--  0041: コンソール表示名を「キミテラス運営」へ統一(...)   ← ファイル名は 0043_console_name.sql

この状態は40分ほど残り、c17ff65(00:55)で別件(schema.sql の整合作業)のついでに気づいて直されています。リネームはファイルシステム上の操作であり、ファイルの中身にあるコメント上の番号までは連動しません。「リネームした」と「中身の記述も追随した」は別の作業だということです。

再発防止

この記事の根拠には、番号衝突を自動で検出する仕組みを追加したという記述はありません。1回目(0035 の重複)でも2回目(今回)でも、直し方は一貫して「見つけたら手でリネームする」でした。

構造として見えるのは、衝突を警戒する意識と、実際に衝突が起きるタイミングが噛み合っていなかったということです。23:14のリネームは直前の 0040 重複を踏まえた対応でしたが、対応した瞬間のトランクにしか効きません。その後70分のあいだに2本のマージが重なれば、リネームで選んだ番号ごと巻き込まれます。手番号方式である限り、「次の番号」を判断する材料は常に「自分が最後にpullしたトランクの状態」であり、同じ時間帯に他のブランチが何を選んでいるかは、マージされるまで誰にも見えません。

よくある質問

Q11回直したのに、なぜまた衝突したのですか?

直した時点で「次に空いている番号」として0041を選びましたが、その1時間足らずのあいだに、それぞれ独立に進んでいた別の2本のfeatureブランチが、同じトランクの状態を見て同じように「次は0041」と判断していたためです。片方はリネームより前から0041を使っていて、もう片方はリネームの直後にマージされました。

Q2同じ日に3つも0041が生まれたのは偶然ですか?

偶然というより、複数の長期化したfeatureブランチをまとめてトランクへ統合する『統合フェーズ』に入っていたことが直接の条件です。ふだんは1本ずつマージされるところに、短時間で3本のマージが連続したため、採番の重複が起きる窓が広がりました。

Q3本番への実害はあったのですか?

ありません。3つとも `add column if not exists` や `alter column ... set default` のような追加専用・冪等なSQLで、内容が互いに独立していたため、どの順番で適用されても結果は同じでした。

Q4リネームすればファイルの中身も直りますか?

直りません。ファイル名を変えても中身のコメントはそのままなので、リネーム後のファイルにも古い番号のヘッダコメントが残ります。今回も0042・0043にリネームした直後は本文の見出しコメントが0040・0041のままで、40分後に別のコミットで手直ししています。

確認した環境

  • @supabase/supabase-js ^2.106.2(kimiteras-portal)
  • 2026-06-14 23:05 〜 06-15 00:15 に本番運用中のリポジトリで発生

この記事の根拠

  • SQLファイルコミット 846f8ae
  • SQLファイルコミット b4a2828
  • SQLファイルコミット 2085eba
  • SQLファイルコミット 7b2746b
  • SQLファイルコミット 7b2746b
  • SQLファイルコミット c17ff65
  • SQLファイルコミット c17ff65

本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。