BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

数百万行でも怖くない!MySQL INSTANT DDLで「完全無停止」カラム追加

BASE ADVENT CALENDAR 2025 DAY.14

はじめに

本記事は BASE アドベントカレンダー 2025 の 14 日目の記事です。

BASE BANK Dept で フルサイクルエンジニア をしている 02 です。

2025年4月、BASEは新しい振込申請機能「最速振込」をリリースしました。最短10分、土日祝日を含む365日対応での入金が可能になり、ショップオーナーさんのキャッシュフロー改善に大きく貢献しています。

本記事では、最速振込の実装で使用したスキーマ変更とMySQL INSTANT DDLを活用したマイグレーションについて解説します。なお、テーブル名・カラム名は説明用に簡略化しています。

プロジェクト管理やリーダーシップの観点、振込申請や最速振込の詳細については、以前の記事「最速振込の舞台裏:プロジェクトのリードの実践と学び」で紹介しました。よろしければそちらもご覧ください。

既存のテーブル設計課題と新しいカラム

最速振込を追加する前、BASEの振込申請には通常振込、お急ぎ振込、定期振込の3種類がありました。当時、振込種別は複数のテーブルを参照して判定していました。

  • 通常振込:振込手数料テーブルのお急ぎ振込フラグがOFF
  • お急ぎ振込:振込手数料テーブルのお急ぎ振込フラグがON
  • 定期振込:定期振込ログテーブルにレコードが存在

しかし、この設計には課題がありました。

  1. 振込種別を判定するために複数テーブルをJOINする必要がある
  2. フラグでは2種類しか表現できず、お急ぎ振込か否かしか判断できない
  3. 新しい振込種別を追加するたびに判定ロジックが複雑化する

これらの課題を解決するため、振込申請テーブルに振込種別カラムを追加しました。

transfer_type varchar(20) COMMENT '振込種別(通常振込: normal、お急ぎ振込: express、定期振込: scheduled、最速振込: instant)'

これにより、振込種別の判定が振込申請テーブルの1カラムで完結します。カラム追加には、MySQLのINSTANT DDLを活用しました。

MySQL INSTANT DDLの活用

INSTANT DDLとは

MySQL 8.0.12で導入されたALTER TABLEのアルゴリズムです。従来のCOPYやINPLACEと異なり、メタデータの更新のみで操作が完了するため、非常に高速です。

アルゴリズム 動作 特徴
COPY テーブルをコピーして入れ替え テーブルロックが発生、最も遅い
INPLACE その場で変更(内部的には再構築) DML操作は可能だが、時間がかかる
INSTANT メタデータのみ更新 非常に高速、テーブルサイズに依存しない

INSTANT DDLは行データの再配置や再構築を行わず、メタデータだけを更新します。

結果として、アプリケーション視点では接続断やタイムアウト、強制リトライが生じず、読み書きトラフィックを止めないまま変更を完了できます。これが本記事で述べる「完全無停止」の根拠です。

MySQL 8.0.29以降、INSTANT DDLは以下の操作に対応しています。

  • カラムの追加(任意の位置に追加可能)、削除
  • カラム名の変更
  • ENUM/SETへの要素を追加
  • DEFAULTの追加、変更、削除
  • 仮想カラムの追加、削除
  • テーブル名の変更
  • インデックスのデータ構造(BTREEやHASH)の変更

ただし、以下のような制限があります。

  • INSTANTのアルゴリズムに対応していない構文との併用はできない
  • ROW_FORMAT=COMPRESSEDのテーブルでは使用できない
  • FULLTEXTインデックスを含むテーブルでは使用できない
  • INSTANT DDLの操作回数には上限があり、テーブルごとに64回(超過時はOPTIMIZE TABLEでリセット)まで

詳細については、MySQLの公式ドキュメントをご参照ください。

https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html#online-ddl-column-operations

なぜINSTANT DDLを選んだのか

振込申請テーブルは数百万件のレコードを持つ、追加・更新頻度の高いテーブルです。従来のINPLACE DDLでは長時間のロックが発生するため、メンテナンス時間の確保か、gh-ostを使用したカラム追加が必要だと考えていました。

しかし、INSTANT DDLを使用すればサービスへの影響を最小限に抑えられ、メンテナンス時間も不要です。さらに、マイグレーション手順を適切に設計すれば、DDLの実行だけでカラム追加が完結します。

本番相当のデータ量を持つ検証環境で検証した結果、無停止で実現できると確信しました。

  • INPLACE DDL: 3分ほど
  • INSTANT DDL: 374ms

マイグレーション手順と安全なカラム追加の戦略

新しいカラムを追加する際、単純にカラムを追加してアプリケーションを更新するだけでは不十分です。過去のレコードとの整合性が取れないため、以下の手順で段階的にマイグレーションを行いました。

なお、今回出てくるクエリは、例として名称は一部置き換えています。

Step 1: DEFAULT句ありでカラム追加

まず、振込種別カラム transfer_type を DEFAULT句 'normal'(通常振込)で追加します。これにより、既存レコードの transfer_type には自動的に 'normal' が設定されます。

ALTER TABLE 振込申請テーブル
  ADD transfer_type varchar(20) DEFAULT 'normal' COMMENT '振込種別',
  ALGORITHM=INSTANT, LOCK=DEFAULT;

'normal'(通常振込)をデフォルト値として選んだのは、通常振込が最も多く使われている振込種別だからです。Step 4で過去データをマイグレーションする際、更新するレコード数を最小限に抑えられます。最もレコード数の多い通常振込を更新対象から除外できるため、この方法が効率的だと判断しました。

Step 2: アプリケーションの対応

振込申請作成時に振込種別カラムを設定するようアプリケーションを更新します。

  • 通常振込作成時: transfer_type = 'normal'
  • お急ぎ振込作成時: transfer_type = 'express'
  • 定期振込作成時: transfer_type = 'scheduled'

Step 3: DEFAULTを検知用の値に変更する

次にDEFAULT句を検知用の値である 'unsupported' に変更します。

ALTER TABLE 振込申請テーブル
  ALTER transfer_type SET DEFAULT 'unsupported',
  ALGORITHM=INSTANT, LOCK=DEFAULT;

この変更後、transfer_type = 'unsupported' のレコードが発生しないことを監視します。もし発生した場合は、対応漏れがあることを意味するため、アプリケーションコードを修正します。

Step 4: 過去データのマイグレーション

レコードを監視して対応漏れがないことを確認したら、過去のレコードを関連テーブルの情報をもとに更新します。

-- お急ぎ振込の過去レコード(振込手数料テーブルのお急ぎ振込フラグを参照)
UPDATE 振込申請テーブル
LEFT JOIN 振込手数料テーブル ON 振込申請テーブル.振込申請id = 振込手数料テーブル.id
SET 振込申請テーブル.transfer_type = 'express'
WHERE 振込手数料テーブル.お急ぎ振込フラグ = 1;

-- 定期振込の過去レコード(定期振込ログテーブルの存在を参照)
UPDATE 振込申請テーブル
LEFT JOIN 定期振込ログテーブル ON 振込申請テーブル.id = 定期振込ログテーブル.id
SET 振込申請テーブル.transfer_type = 'scheduled'
WHERE 定期振込ログテーブル.id IS NOT NULL;

Step 1でDEFAULT句を追加した際、既存レコードのtransfer_typeには自動的に'normal'が設定されています。そのため、通常振込の過去レコードについてはUPDATE文を実行する必要はありません。

Step 5: 参照ロジックの切り替え

最後に、振込種別の判定ロジックを新しいカラムtransfer_typeを参照するように切り替えます。また、任意のタイミングでDEFAULT句を削除します。

ALTER TABLE 振込申請テーブル
  ALTER transfer_type DROP DEFAULT,
  ALGORITHM=INSTANT, LOCK=DEFAULT;

こういった手順で振込申請テーブルに振込種別カラムを追加しました。

おわりに

最速振込の実装では、MySQL INSTANT DDLを活用することで、更新頻度の高い数百万行のテーブルへ、メンテナンスなしでスキーマ変更を実現できました。

MySQL 8.0.29以降を使用している方は、ぜひINSTANT DDLの活用を検討してみてください。

BASE BANK Deptでは、プロダクト開発をリードできるエンジニアを募集しています。興味のある方は、ぜひ採用情報をご覧ください!

binc.jp

明日は、BASEアドベントカレンダーは @capi さんです!お楽しみに!