アプリは書き換えられても、データベースは何十年も使われ続けることが多い。その理由、マイグレーションがコスト高になる原因、安全に進化するスキーマ設計の方法を解説します。

数年ソフトウェアに関わっていると、同じ話が何度も繰り返されるのを見ているはずです:アプリは再設計され、書き換えられ、リブランドされ、あるいは完全に置き換えられる。一方でデータベースは黙って稼働を続けます。
企業はデスクトップアプリからウェブアプリへ、次にモバイルへ、さらに新しいフレームワークで「v2」を作るかもしれません。しかし顧客情報、注文、請求、商品カタログはしばしば同じデータベース(あるいはその直系の子孫)に残り、時には10年前に作られたテーブルがそのまま使われていることもあります。
簡単に言えば:アプリケーションコードはインターフェイスと振る舞いであり、置き換えやすいため頻繁に変わります。データベースは記憶であり、ビジネスが依存する履歴を保持しているため変更がリスクになるのです。
非技術的な例:店を改装することはできますが(新しい棚、レジ、サイン)、在庫記録や領収書を捨てる必要はありません。改装がアプリで、記録がデータベースです。
このパターンに気づくと、意思決定の仕方が変わります:
以降のセクションでは、なぜデータベースが残りやすいのか、なぜデータの移行が難しいのか、そしてアプリの書き換えを何度も耐えられるようなデータベースの設計・運用の実践的手法を説明します。すべての変更を危機にしない方法を学べます。
アプリが「プロダクト」に感じられることが多いですが、何が起きたかを覚えているのはデータベースです。
ショッピングアプリが5回デザインを変えても、顧客は購入履歴が残っていることを期待します。サポートポータルがベンダーを変えても、チケットや返金、約束の記録は一貫している必要があります。その連続性は顧客、注文、請求、サブスクリプション、イベント、そしてそれらの関係性として保存されます。
機能が消えればユーザーは不満を持ちますが、データが消えると信頼、収益、法的根拠を失う可能性があります。
アプリはソース管理やドキュメントから再構築できることが多いですが、現実の履歴は再現できません。去年の支払いを再実行したり、顧客が同意した瞬間の同意を再現したり、いつ何が発送されたかを記憶から正確に再構成することはできません。タイムスタンプの欠落、孤立レコード、不整合な合計などの部分的な損失でも、プロダクトの信頼性を損ないます。
ほとんどのデータは存在期間が長くなるほど有用になります:
だからチームはデータを副産物ではなく資産として扱います。新しいアプリの書き換えはより良いUIをもたらすかもしれませんが、何年にもわたる歴史的真実を置き換えることは稀です。
時間が経つにつれて、組織はデータベースを共通の参照点として標準化します:データベースからエクスポートしたスプレッドシート、そこに構築されたダッシュボード、財務プロセスの突合、繰り返し使われる「既知の正しい」クエリなどです。
これがデータベース長寿の感情的な中心です:周囲のアプリが変わっていても、みんなが頼る記憶として残り続けるのです。
データベースはめったに「一つのアプリが所有する」ものではありません。時間とともに複数のプロダクト、内部ツール、チームの共通の真実ソースになります。この共有依存が、アプリコードが置き換えられてもデータベースが残る大きな理由です。
同じ一連のテーブルが次のような用途に使われることはよくあります:
これら各利用者は異なる言語で作られ、異なるリリーススケジュールで運用され、異なる担当者により保守されています。アプリが書き換えられても、皆が依存する同じレコードを読み保持する必要があります。
統合は特定のデータモデル(テーブル名、カラムの意味、参照ID、レコードの表すものに関する前提)に“結びつく”傾向があります。API経由の統合であっても、APIが内部のデータベースモデルを反映することがよくあります。
だからデータベースの変更は一チームの判断では済まないことが多いのです。スキーマ変更はエクスポート、ETLジョブ、レポートクエリ、メインのプロダクトリポジトリ外の下流システムに波及する可能性があります。
バグのある機能を出せばロールバックできます。しかし共有されたデータベース契約を壊すと、請求、ダッシュボード、レポーティングが同時に止まることがあります。リスクは依存する数だけ増幅されます。
これが「一時的」な選択(カラム名、列挙値、NULLの扱いなど)が粘着質になる理由でもあります:多くのものがそれらに静かに依存してしまうのです。
安全に管理する実践が知りたい場合は /blog/schema-evolution-guide を参照してください。
アプリの書き換えは部分的に行えることが多いです。UIを差し替え、サービスを置き換え、あるいはAPIの背後で機能を再構築しながら、同じデータベースを保持できます。何か問題が起きても、デプロイをロールバックしたり、トラフィックを旧モジュールに戻したり、古いコードと新しいコードを併存させることができます。
データは同じ柔軟さを与えてくれません。データは共有され相互に結びついており、常に正しいことが期待されます—「次のデプロイ後に大体正しくなる」では困るのです。
コードをリファクタリングするときは命令を変えています。データをマイグレーションするときは、ビジネスが頼っているものを変えています:顧客レコード、トランザクション、監査トレイル、商品履歴など。
新しいサービスは一部ユーザーでテストできますが、データベースのマイグレーションはすべてに影響します:現行ユーザー、過去のユーザー、履歴行、孤立したレコード、3年前のバグで作られた一意のエントリなど。
データ移行は単なる「エクスポートしてインポート」ではありません。通常は以下を含みます:
各ステップは検証を必要とし、検証には時間がかかります—特にデータセットが大きく、誤りの影響が重大な場合はなおさらです。
コードのデプロイは頻繁で可逆的であることが多いですが、データのカットオーバーは手術のようです。
ダウンタイムが必要なら、ビジネス運用、サポート、顧客期待を調整する必要があります。ゼロに近いダウンタイムを目指すなら、デュアルライト、変更データキャプチャ、段階的レプリケーションなどを行い、新システムが遅い・間違っている場合の対応策も用意します。
ロールバックも異なります。コードのロールバックは簡単ですが、データのロールバックはバックアップの復元、変更のリプレイ、あるいは「間違った場所に書かれた」一部の書き込みを受け入れて調整することを意味します。
データベースは履歴を蓄積します:奇妙なレコード、古いステータス、部分的に移行された行、誰も覚えていないワークアラウンド。これらのエッジケースは開発データセットではほとんど現れませんが、本番のマイグレーションではすぐに表面化します。
そのため組織はコードの書き換えを何度も受け入れても、データベースは安定させておくのです。データベースは依存対象であるだけでなく、安全に変更するのが最も難しいものなのです。
アプリコードの変更は主に新しい振る舞いをデリバリすることです。問題が起きればデプロイをロールバックしたり、フラグで切り替えたり、素早くパッチを当てることができます。
スキーマ変更は異なります:既に存在するデータのルールを形作り直すことであり、そのデータは何年も古く、一貫性がなく、複数のサービスやレポートに依存している可能性があります。
良いスキーマは滅多に凍結しません。課題は歴史的データを有効で使いやすいまま進化させることです。コードとは違い、データは「再コンパイル」してきれいな状態に戻せない—過去のすべての行を持ち運ぶ必要があります。
だからスキーマ進化は既存の意味を保ち、保存されているものの書き換えを強制しない変更を好みます。
付加的な変更(新しいテーブル、カラム、インデックス)は通常、古いコードが動き続けることを許しつつ、新しいコードが新構造を利用できます。
破壊的な変更(カラム名の変更、型の変更、1つのフィールドを複数に分割、制約の厳格化)は次のような連携更新を必要とします:
メインアプリを更新しても、忘れられたレポートや統合が古い形状に依存していることがあります。
「ただスキーマを変える」は簡単に聞こえますが、何百万行の既存データをオンラインのまま移行することを考えると複雑です。考慮すべき点:
NOT NULL カラムのために値をバックフィルするALTER 操作中の長時間ロックやタイムアウトを避ける多くの場合、複数段階のマイグレーションになります:新しいフィールドを追加し、両方に書き、バックフィルし、読みを切り替え、最後に古いフィールドを削除する。
コード変更は可逆的かつ孤立していることが多い;スキーマ変更は永続的で共有されます。マイグレーションが実行されると、それはデータベース履歴の一部となり、将来のすべての製品バージョンがその決定と共存しなければなりません。
アプリケーションフレームワークは速いペースで変わります:5年前に「モダン」だったものが今ではサポート外、人気がなく、採用が難しいことがあります。データベースも変わりますが、コアの考え方や日常的なスキルははるかにゆっくり進化します。
SQLとリレーショナルの概念は何十年も安定しています:テーブル、結合、制約、インデックス、トランザクション、クエリプラン。ベンダーが機能を追加しても、メンタルモデルは馴染み深いままです。この安定性によりチームは新しい言語でアプリを書き換えても、同じデータモデルとクエリ手法を保てます。
新しいデータベース製品でも、レポーティングやトラブルシュート、ビジネス上の問いに合うように「SQLライク」なクエリ層やリレーショナル型の結合、トランザクションセマンティクスを再導入することがよくあります。
基礎が一貫しているため、周辺のエコシステムは世代を超えて続きます:
この継続性が「強制的な書き換え」を減らします。採用が難しくなったからアプリフレームワークを捨てることがあっても、データ用の共通言語としてのSQLが放棄されることは稀です。
データベースの標準や慣習は共通の基盤を作ります:SQL方言は同一ではないにせよ、多くのウェブフレームワークより互いに近いです。これによりアプリ層が進化してもデータベースを安定基盤として維持しやすくなります。
実務的な効果は単純です:アプリの書き換えを計画するとき、多くのチームは既存のデータベーススキル、クエリパターン、運用慣行をそのまま使えるため、データベースは複数世代のコードより長く残る安定した基盤になります。
ほとんどのチームが同じデータベースを使い続けるのは好みのためではありません。既に構築した運用習慣があるからです—その習慣は血と汗で得たものです。
一度データベースを本番に投入すると、それは会社の「常時稼働」機構の一部になります。夜中にページ通知が行く対象であり、監査が問う対象であり、新しいサービスが最終的に接続するものです。
1〜2年後には通常次のような確かなリズムがあります:
データベースを置き換えると、そのすべてを実トラフィックで再学習する必要が生じます。
データベースは放置されるものではありません。時間をかけてチームは信頼性に関する知見を蓄積します:
その知見はダッシュボードやスクリプト、あるいは人々の頭の中に残りがちで、一つのドキュメントにまとまっていないことが多いです。アプリの書き換えは振る舞いを保ちながら行えますが、データベースの置き換えは同時に動作性、性能、信頼性を再構築することになります。
ロール、権限、監査ログ、シークレットのローテーション、暗号化設定、誰が何を読めるかといった管理はコンプライアンス要件や社内ポリシーと整合します。
データベースを変えるということはアクセスモデルをやり直し、コントロールを再検証し、センシティブなデータが引き続き保護されていることを業務側に再証明する必要があります。
運用成熟度があればリスクは下がります。新しいデータベースがより良い機能を約束しても、古いデータベースには「稼働し続け、復旧でき、問題発生時に理解可能であり続けた」という歴史的価値があります。
アプリコードは新しいフレームワークやクリーンなアーキテクチャに置き換えられますが、コンプライアンス義務は記録に結びついています—いつ何が起き、誰が承認し、顧客がその時に何を見たか。だからデータベースは書き換えの際に動かしがたい存在になります。
多くの業界は請求書、同意記録、財務イベント、サポート対話、アクセスログなどに最小保持期間を課しています。監査人は「アプリを書き換えたから」という理由で履歴が失われたことを受け入れません。
たとえ日常的に使われないレガシーテーブルであっても、要求に応じて出力し、それがどのように作られたか説明できる必要があるかもしれません。
チャージバック、返金、配送紛争、契約上の質問はスナップショットに依存します:当時の価格、使用された住所、受け入れた条件、あるいは特定の分単位のステータスなど。
データベースがこれらの事実の権威あるソースである場合、それを置き換えることは単なる技術プロジェクトではなく、証拠を変えてしまうリスクを伴います。だからチームは既存のデータベースを残し、その上に新しいサービスを作ることが多いのです。
一部のレコードは削除できず、他はトレーサビリティを壊す形で変換できません。正規化をやめたり、フィールドを統合したり、カラムを削除したりすると、監査トレイルを再構築する能力を失うかもしれません。
この緊張はプライバシー要件と保持が絡むと顕著になります:トランザクション履歴を保ちながら選択的なマスキングや仮名化を行う必要があり、これらの制約はデータに最も近い場所に残ります。
データ分類(PII、財務、医療、内部のみ)の方針やガバナンスはプロダクトの進化より安定していることが多いです。アクセス制御、レポート定義、単一の真実ソース決定は多くの場合データベースレベルで強制されます。なぜならBIダッシュボード、財務エクスポート、規制レポート、インシデント調査など多くのツールがデータベースを直接参照するからです。
書き換えを計画するなら、コンプライアンス報告を最優先要件として扱ってください:必要なレポート、保持スケジュール、監査用フィールドをスキーマに触る前に棚卸しすること。一つの簡単なチェックリストが助けになります(参照: /blog/database-migration-checklist)。
ほとんどの「一時的」な選択は無作為に行われるわけではなく、期限や緊急要請下で行われます:ローンチ期限、クライアントの急ぎ、規制対応、混乱したインポート作業など。驚くべきことに、それらの選択が元に戻されることは稀です。
アプリは素早くリファクタできますが、データベースは古い利用者と新しい利用者を同時にサービスする必要があります。レガシーテーブルやカラムが残るのは何かがまだそれに依存しているからです:
フィールドを「リネーム」しても、古いものを残しておくパターンがよくあります。例えば customer_phone_e164 を追加しても、phone が夜間エクスポートで使われているためそのまま残すことがあるのです。
ワークアラウンドはスプレッドシート、ダッシュボード、CSVエクスポートに組み込まれ、これらは生産コードのようには扱われません。誰かが「Financeが移行するまで」のつもりで使った結合が、四半期プロセスの依存先になってしまい、テーブルを削除すること自体が業務リスクになります。
これが廃止予定のテーブルが何年も残る理由です:データベースはアプリだけでなく組織の習慣にもサービスしているのです。
クイックフィックスとして追加されたフィールド(promo_code_notes、legacy_status、manual_override_reason)はしばしばワークフローで判断材料として使われます。一度人がそれを使って結果を説明し始めると、それはもはや任意ではなくなります。
移行を信用できないと、チームは重複データを保持します:顧客名の複製、キャッシュされた合計、フォールバックフラグなど。これらの追加カラムは一見無害に見えますが、真実のソースが複数できることで新たな依存関係を生みます。
この罠を避けるには、スキーマ変更をプロダクト変更として扱い、意図を文書化し、利用者をマッピングしてから何かを削除してください。実践的チェックリストは /blog/schema-evolution-checklist を参照してください。
複数世代のアプリに耐えるデータベースは、内部実装の詳細ではなく共有インフラとして扱う必要があります。目標はすべての将来機能を予測することではなく、変更を安全に、段階的に、可逆的にすることです。
アプリのコードは書き換えられますが、データ契約は再交渉が難しい。テーブル、カラム、キー関係を将来のシステムやチームが依存するAPIだと考えてください。
付加的な変更を好む:
将来の書き換えが失敗するのはデータが欠けているからではなく、曖昧だからです。
意図がわかる一貫した命名を使って(例:billing_address_id と addr2 の違い)制約でルールを表現してください:主キー、外部キー、NOT NULL、一意性、チェック制約など。スキーマ近くに軽いドキュメント(テーブル/カラムコメントや短いリビングドキュメント)を追加し、「なぜそうしたか」を残すことが重要です。
すべての変更には前進と後退の道筋が必要です。
頻繁なアプリ反復の間もデータベース変更を安全に保つ実践として、計画段階での「準備モード」とロールバックの規律を配達ワークフローに組み込むと良いでしょう。例えば内部ツールや新しいアプリを構築するとき、スキーマを安定した契約として扱い、スナップショットとロールバック風の手法を使って偶発的な変更の影響範囲を減らすといったやり方です。
スキーマを安定した契約で設計し安全な進化を可能にすれば、アプリの書き換えはルーティンとなり、危機的な「データ救出」作業になりません。
データベースの置き換えは稀ですが、存在しないわけではありません。それを成功させるチームは“勇敢”なのではなく、数年前からデータを移植可能にし、依存関係を可視化し、アプリを一つのエンジンに密結合させない準備をしているのです。
エクスポートを一度限りのスクリプトではなく第一級機能として扱い始めてください。
結合度が高いほど移行は書き換えになります。
バランスの取れたアプローチを目指してください:
新しいサービスを素早く作る際(例:React管理アプリ+Goバックエンド+PostgreSQL)には、ポータビリティと運用の明瞭さがデフォルトとなるスタックを選ぶと良いでしょう。Koder.aiはそうした広く採用されたプリミティブを活かし、ソースコードのエクスポートをサポートしています。これによりアプリ層を置き換えてもデータモデルに縛られにくくなります。
データベースはメインアプリ以外にもレポート、スプレッドシート、定期ETL、サードパーティ統合、監査パイプラインなどを支えます。
生きたインベントリを維持してください:誰が読み書きしているか、頻度はどれくらいか、壊れたら何が起きるか。/docs に担当者と連絡先を載せるだけでも予期せぬ問題を防げます。
一般的な合図:ライセンスやホスティングの制約、直せない信頼性問題、欠けているコンプライアンス機能、極端なワークアラウンドを強いるスケール上の限界。
主なリスク:データ損失、意味の微妙な変化、ダウンタイム、レポーティングのズレ。
より安全なアプローチは通常「並行稼働(parallel run)」です:データを継続的に移行し、結果を検証(行数、チェックサム、ビジネスメトリクス)、トラフィックを徐々に移し、信頼が十分得られるまでロールバック経路を維持する。
データベースはビジネスの歴史的事実(顧客、注文、請求、監査トレイルなど)を保持しているためです。コードは再デプロイや書き換えが可能でも、失われた履歴を再構築することは難しく、金銭的・法的・信頼の問題を引き起こす可能性があります。
データの変更は共有され、永続的だからです。
一つのデータベースはしばしば以下のような共通の真実ソースになります:
アプリを書き換えても、これらすべての利用者は安定したテーブル、ID、意味を必要とします。
まれです。ほとんどの“マイグレーション”はデータベース契約を安定させたままアプリコンポーネントを置き換えるよう段階的に行われます。
一般的な進め方:
ほとんどのチームは**付加的(additive)**な変更を安全と見なします:
これにより旧コードと新コードを並行稼働させられます。
曖昧さはコードより長く残ります。
実用的な対策:
billing_address_id)。NOT NULL、一意制約、チェック)。「変な」行に備えてください。
マイグレーション前に計画すべきこと:
本番に近いデータでテストし、単なる変換ロジックではなく検証手順を含めてください。
コンプライアンスはUIではなく記録に紐づきます。
保持や再現が求められるものの例:
フィールドを形を変えたり削除したりするとトレーサビリティや監査性が失われ、アプリが移行していても問題になります。
互換性が隠れた依存を作るからです:
非推奨化はプロダクト変更として扱い、意図を文書化し、利用者を追跡し、廃止計画を設定してください。
実用的なチェックリスト:
これにより書き換えが日常的な作業になり、「データ救出」プロジェクトに変わりません。