はじめに
こんにちは、バクラクビジネスカード開発チームの @budougumi0617 です。
先月まではテックリードでしたが、4月からエンジニアリングマネージャになりました。
バクラクビジネスカードは2022年からサービスを提供している法人様向けクレジットカードのサービスです。昨年2025年からはETCカードのサービス提供も開始しました。
本記事では、もともとクレジットカードの決済データを受け取ることのみを前提に設計されていたバクラクビジネスカードのデータベースに対して新しくETCカードの決済データも受け入れられるようにマイグレーションを行なったときに考えたことをまとめます。
要点を先に書くと、次の3つです。
- 約3年分の決済レコードが蓄積された本番DBに対して、サービス無停止でスキーマ変更を完了した
- テーブルごとに許容できるマイグレーション時間も考慮しながらマイグレーション戦略を切り分けた
- 本番DBの複製を利用した実データを用いたマイグレーション時間の事前計測
決済系プロダクトでスキーマ拡張を行うエンジニアや、歴史あるサービスでの大きめのDBマイグレーションを検討している方にとって、なんらかのヒントになれば幸いです。
背景:クレジットカードの体験をETCカードでも提供する
バクラクビジネスカードは他のバクラクプロダクトと連携し、クレジットカードの決済から経費精算・経理担当の会計処理までのシームレスな体験を提供しています。 「クレジットカードの体験をETCカードでも提供してほしい」と希望するお客様の声は常にありました。
ここで、バクラクビジネスカードはプロセシングプラットフォームであるインフキュリオン様のXardを利用してクレジットカード機能を開発しています。 2025年、XardでETCカード発行サービスがリリースされることになり、バクラクでもETCカードを提供することとなりました。
インフキュリオン、「Xard(エクサード)」の新オプション機能として 「ETCカード発行」サービスを2025年7月より提供開始 - Infcurion, Inc.
Xardを利用したETCカードは、通常のクレジットカードと同じAPIを用いてETCカードの発行および高速道路利用時の決済情報を受け取ることができます。 バクラクビジネスカードのオプションとしてETCカードを新たに提供するにあたり、技術的に大きな問いが1つありました。
それは、クレジットカードの決済情報を受け取ることのみを前提としたデータベースのテーブルをETCカードの決済情報にも対応させることでした。
クレジットカードの決済情報テーブルをETCカードの決済情報にも対応するように拡張する
バクラクビジネスカードは、Xardからの決済リクエスト、Webhookを受信して決済を処理しています。 クレジットカード(以降クレカ)の決済情報とETCカードの決済情報では含まれる内容に違いがあることが事前にわかっていました。
クレカとETCの決済データの差分
ETCの決済データは、クレジットカードの決済データといくつかは共通している一方、各々で固有の項目もそれなりにあります。たとえば以下のとおりです。
2つの大きな制約
今回のカード・決済情報テーブルのETCカード対応拡張に着手するうえで、外せない制約が2つありました。
1つ目は「無停止要件」です。クレジットカードはお客様が24時間365日利用するサービスです。 決済を止めるメンテナンスはどうしても回避できない場合を除き行わない方針で開発・運用をしています。
2つ目は「既存データ量」です。バクラクビジネスカードはリリースから3年以上が経過し、主要テーブルには3年分の決済情報のレコードが蓄積されていて、今この瞬間にも新たな決済明細のレコードが増え続けています。ローカルでは一瞬のマイグレーションも、本番では何分・何十分とかかる可能性がありました。
「無停止」と「3年分のレコードを抱えたテーブルへの変更」が同時に求められる、これが今回の出発点です。
テーブルがどのようなユースケースで利用されているか考える
スキーマ拡張の議論を始めると、つい「どんなテーブル設計が綺麗か」「どんなマイグレーションが速いか」という話に目が向きがちです。
しかし、今回のETC対応で最初に行なったのは、そのテーブルが本番でどう利用されているかを見るという工程でした。同じスキーマ変更要件でも、当てるテーブルの利用パターンが違えば、許される操作の幅も大きく変わります。
バクラクビジネスカードの決済処理を構成するテーブルは、本番での利用のされ方が大きく異なる2系統に分けられました。
- 系統A:決済処理APIに紐づくテーブル
- 指定時間以内にレスポンスを返さなければ決済そのものが失敗扱いになる経路で参照・更新されます
- 既存処理に与える影響を限りなく小さくする必要があります
- 許されるのは「短時間で完了し、マイグレーション後もアプリの挙動に影響がない」スキーマ変更です
- 系統B:Webhook経由の非同期処理に紐づくテーブル
- 外部からのイベントを弊社システム内部のキュー経由で非同期に処理する経路で参照・更新されます
- 一時的な処理失敗はキューで再処理できる設計になっており、ある程度の処理遅延・実行時間を許容できる経路です
- サブテーブル分離など、踏み込んだスキーマ変更も現実的な選択肢になります
この2系統を同列に扱ってしまうと、「全テーブルを最速で完了させる」という強い縛りが生まれ、選べる打ち手が極端に狭くなります。逆に切り分ければ、系統Aには本当に安全な操作だけを当て、系統Bには踏み込んだ変更を入れる、という割り当て方ができます。
非同期経路のWebhook基盤の設計については、過去の発表資料もあわせてご覧ください。
系統ごとに違うアプローチを選ぶ
テーブルの利用パターンが違うことを踏まえて、ETC対応のスキーマ拡張も系統ごとに違うアプローチをとりました。
系統A:既存テーブルに最小限の変更だけを当て新規テーブルを追加する
API処理側のテーブルには、既存クエリの形を変えずに済む最小限のスキーマ変更だけを当てる方針にしました。 具体的には、決済の親テーブルに種別を見分けるカラムを1つ追加し、ETC固有の付随情報は新規テーブルに寄せる構造です。 もともと決済処理APIの内容を格納しておくテーブルは2つに分かれていました。
- IDや受信時刻など基本的な情報を持つ親テーブル
- 金額や加盟店名などに関する詳細テーブル
そこで詳細テーブルはクレカ用として既存のまま残し、ETC側は新規に別の詳細テーブルを用意することにしました。
判断軸はシンプルで、API処理側では次の3点が強く求められたからです。
- 既存クエリへの影響を最小化すること
- 明細表示や集計クエリを1テーブルで完結させること(UNIONや分岐を増やさない)
- マイグレーション中・マイグレーション後もアプリケーションが決済を処理し続けられること
修正前後の構造のイメージは次のとおりです。
flowchart LR
subgraph Before["修正前"]
A1["親テーブル"]
A2["詳細情報テーブル"]
A1 --> A2
end
subgraph After["修正後"]
B1["親テーブル</BR>種別カラム"]
B2["詳細サブテーブル<BR/>(クレカ用とする)"]
B3["ETC用詳細情報テーブル<BR/>(新規)"]
B1 --> B2
B1 -- "種別=ETC のときのみ参照" --> B3
end
Before --> After
系統B:共通テーブル+種別ごとのサブテーブルに分ける
Webhook経由側のテーブルも最終的な形は似た構造になるように変更しました。
Webhookで受け取る生データは最初1テーブルで全てを含んでいました。 そのテーブルにETCカードのカラムを追加するとクレカ・ETCカードでお互いNULLになるカラムが増え、意味づけも複雑化します。
そこで、共通テーブルには種別を見分ける情報を持たせ、種別固有のデータはサブテーブルに完全分離する形にしました。修正前後の構造のイメージは次のとおりです。
flowchart LR
subgraph Before["修正前"]
A1["共通テーブル<br/>(クレカ用カラムも含む)"]
end
subgraph After["修正後"]
B1["共通テーブル<br/>+ 種別カラム<br/>(クレカ用カラムは後日DROP)"]
B2["クレカ用<br/>詳細サブテーブル<br/>(新規)"]
B3["ETC用<br/>詳細サブテーブル<br/>(新規)"]
B1 -- "種別=クレカ" --> B2
B1 -- "種別=ETC" --> B3
end
Before --> After
このアプローチではデータの扱いが大きく代わりますが、共通テーブルへのカラムのDROPを後日行えばマイグレーション実行直後でもアプリケーションは問題なく動きます。
そのかわり新設したサブテーブルへ過去分のデータをバックフィルする作業や、共通テーブル側の旧カラムを段階的に整理していく作業も発生します。 しかしこれらの「時間のかかるデータ移行作業」でも、Webhook経路の場合は移行作業中にWebhook処理が失敗してもキューの再実行によりリカバリが可能です。
同じ要件、違う実装手段
ETC対応で必要だった共通要件をまとめると、次の3つでした。
- カード種別を識別する仕組み
- ETC固有データを保持する場所
- 既存クレカ機能への影響を出さないこと
同じ要件でも、系統Aと系統Bでは実装手段が違います。
- 系統A:既存テーブルの延長線上で考える
- 系統B:多少実行コストがかかってもデータを整理する
「全テーブルで同じマイグレーション戦法をとる」という方針を強引に当てはめないことで、それぞれのテーブルの本番利用に合った形に落とし込めました。
マイグレーションを当てる前に、実データ量で測る
設計のアプローチが決まっても、本番にそのまま実行してよいとは限りません。先に述べたとおり、ローカルでは一瞬で完了するDDLでも本番のデータ量では想定外のロックや所要時間で決済を巻き込むリスクがあります。
ここで効いてきたのが、本番相当の実データ量でDDLの実行時間を必ず検証するという工程でした。バクラクプロダクトでは、権限管理された本番クローン環境で、事前にDDLを検証できるBytebaseをフロントにおいたSQL実行基盤があります。詳しくは以下の記事をご覧ください。
この基盤の上で、今回投入したいDDLを実際に流し、所要時間とロックの挙動を計測しました。チェックしたのは主に次の点です。
- カラム追加が
ALGORITHM=INSTANTで完了するか、それともテーブルコピーになるか - 「カード種別を識別するカラム」の追加が高速で終わるか
- 新規インデックス追加にかかる時間と、その間の参照/更新への影響
DROP COLUMNにどれくらい時間がかかるのか
検証で得られた所要時間が、系統A・系統Bそれぞれの戦略を本番に当てて良いかの判断根拠になりました。
複製環境ではデータが消える DROP COLUMN も気兼ねなく試せたため、戦略の幅も広く取れます。何より「確かめはしたけれど本番で本当に問題ないか不安」という気持ちにならずに自信をもってマイグレーション実行日を迎えることができました。
実施と結果
運用中のテーブルへのマイグレーションのため、実際のマイグレーションではアプリケーションコードのCRUDが壊れないかをQAで確認しつつ、本番環境へメンテナンスタイムなしでマイグレーションを実施していきました。 結果として、今回の一連のスキーマ変更について、マイグレーション起因の決済失敗は確認されず、メンテナンスタイムなしで完了することができました。 2025年夏にリリースしてからとくに問題なくETCカードの処理も行えています。
学びと振り返り
今回のプロジェクトで、後続のスキーマ変更にも持ち越したい学びは次のあたりです。
- マイグレーションアプローチは、テーブルの本番利用に合わせて選ぶ API処理に紐づくテーブルには軽量な変更を当て、Webhook経由のテーブルには踏み込んだ変更を当てる、という割り当てがETC対応で機能しました。同じ考え方は決済に限らず、ECの注文テーブルへの配送種別追加、SaaSの課金テーブルへの新プラン追加など、SLAの厳しい経路と緩い経路が同居するシステム全般に応用できます。
- マイグレーションは実データ量で測ってから当てる ローカルでの感覚と本番でのふるまいは違います。本番相当のデータで実際に流して数字を見ることで、「この変更は適用できる」「この方針は見直すべき」という判断を実測値にもとづいて行えました。
- 本番クローン環境は意思決定の精度を上げる 単なる検証ツールではなく、設計や戦略そのものを引き返す根拠として機能しました。
おわりに
クレジットカードは止められないからこそ、「止めずにどう変えるか」を設計・意思決定の中心に置く必要があります。今回はETCカードの追加という形で、その難しさと面白さに、設計と運用の両面から向き合いました。 テーブルの本番利用を見てからアプローチを決めること、そして実データ量で実行時間を必ず検証すること、この2つが今回の無停止マイグレーションを支えました。
バクラクビジネスカードを開発しているバクラク事業部のPayment開発部では今年もBPSP(Business Payment Service Provider)をリリースしたり、新たな決済サービスの開発を続けています。
AIエージェントや決済サービスの開発に少しでも興味がある!という方はぜひ以下からご応募ください!