🛠️

本番環境のテーブルカラムを安全に変更する

2024/06/19に公開

はじめに

こんにちは、FarStep です。

先日「本番環境で運用しているシステムのデータベースを変更する」というタスクを行いました。具体的には、とあるテーブルのカラムのデータ型を変更するという内容です。このタスクは、システム運用において非常にデリケートな作業であり、慎重に行う必要がありました。特に、本番環境で稼働しているシステムの場合、データの整合性を保ちつつ、サービスを停止させることなく変更を加えなければなりません。

今回は、私が実際に行ったデータベースの変更の手順を紹介しながら、本番環境のテーブルカラムを安全に変更する方法について解説します。

それでは、始めます 🚀

行ったタスクの要件について

今回のタスクは、books テーブルの description カラムのデータ型を VARCHAR(255)から TEXT に変更するというものでした(テーブル名やカラム名などは架空のものです)。books テーブルは、システムの中でも特に重要なテーブルの一つで、非常に大きなテーブルです。
そのため、以下の要件を満たす必要がありました。

  1. データの整合性を保つこと
  2. サービスを停止させないこと
  3. 有事の際にロールバックできるようにしておくこと

特に、三つ目の要件が重要です。データベース変更は、慎重に行わないと取り返しのつかない事態を引き起こしかねません。
そのため、変更を加える前に、ロールバック可能な状態を作っておく必要がありました。

データベース変更のアンチパターン

ここで、データベース変更のアンチパターンについて触れておきましょう。
それは、「既存のカラムを直接変更してしまうこと」です。
既存のカラムを直接変更してしまうと、以下のような問題が発生する可能性があります。

  • 変更が失敗した場合、元の状態に戻すことが困難である
  • 変更中にサービスが停止してしまう
  • データの整合性が損なわれる

特に、一つ目の問題は深刻です。万が一、変更が失敗した場合、元の状態に戻すことができなければ、システムは停止したままになってしまいます。これは、サービスを提供し続けるという観点からは許容できません。
そのため、データベース変更を行う際は、常にロールバック可能な状態を作っておくことが重要です。

今回採用した変更方法

では、具体的に今回どのように変更を行ったのかについて解説します。
今回は、以下の手順で変更を行いました。

  1. 新しいカラム description_text を追加する
  2. アプリケーションのコードを変更し、古いカラム description から新しいカラム description_text に少しずつデータを移行する
  3. データの移行が済んだことを確認した後、アプリケーションのコードを変更し、description_text カラムのみを読み書きするようにする
  4. description カラムを削除する

この方法の利点は、以下の通りです。

  • 新しいカラムを追加するだけなので、既存のデータを変更する必要がない
  • アプリケーションのコードを変更することで、古いカラムと新しいカラムの両方を読み書きできる
  • 最終的に古いカラムを削除することで、無用なカラムを残さない

また、この方法なら、途中でロールバックが必要になった場合でも、アプリケーションのコードを元に戻すだけで済みます。
データは、古いカラムと新しいカラムの両方に残っているので、データの整合性を損なうことはありません。

以下では、それぞれの手順について詳しく説明します。

手順1:description_text カラムの追加

まず、description_text という名前で、TEXT 型のカラムを追加します。このとき、description カラムは残しておきます。
これにより、アプリケーションは引き続き description カラムを使うことができ、影響を最小限に抑えることができます。

ALTER TABLE books ADD COLUMN description_text TEXT;

手順2:データの移行

次に、description カラムのデータを description_text カラムにコピーします。
このデータ移行には、主に三つの方法があります。

  1. 一括でコピーする方法
  2. バッチ処理(バックグラウンドジョブ)を使う方法
  3. アプリケーションのコードを用いて徐々にコピーする方法

今回は、三番目の方法を採用しました。一番目の方法は、非常に大きなテーブルの場合、一度に大量のデータを書き込むことになるため、パフォーマンスに影響を与える可能性があります。二番目の方法は、データ移行の間バッチ処理を管理する必要があり、コストがかかります。そのため、今回は三番目の方法を採用しました。

具体的には、description カラムを参照する際、下記が実現するようにアプリケーションのコードを修正しました。

  • description_text カラムに値があれば、その値を返す。
  • description_text カラムに値がない場合は、description カラムの値を description_text カラムにコピーしてから、description カラムの値を返す。

上記の仕様を図で表すと、以下のようになります。

上記の処理が description カラムが参照されるたびに走ります。
このようにすると、「徐々に」「安全に」データを移行することができます。

手順3:description_text カラムのみを読み書きする

続いて、アプリケーションのコードを変更し、description_text カラムのみを読み書きするようにしました。

しかし、実はアプリケーションのコードを変更する前に行ったことがあります。
それは、description_text に値を持たないレコードに対して、description カラムの値を description_text カラムにコピーするというバッチ処理です。

前述したデータ移行方法では、description カラムが読み込まれない場合、description_text カラムは空のままです。
実際にテーブルを確認してみると、description_text に値を持たないレコードが一部存在することがわかりました。

そこで、バッチ処理を実行しました。バッチ処理によるパフォーマンスへの影響が心配されましたが、description_text に値を持たないレコードはごく一部だったため、これらのレコードを更新するための書き込みが一度に大量に発生するということはありませんでした。

バッチ処理が完了したことを確認した後、改めてアプリケーションのコードを変更し、description_text カラムのみを読み書きするようにしました。
これにより、description カラムを安全に削除できる状態となります。

手順4:description カラムを削除する

最後に、description カラムを削除します。

ALTER TABLE books DROP COLUMN description;

ここまでの手順を踏むことで、description カラムを安全に削除することができました。
これまでの処理によるデータの整合性の問題やサービスの停止は発生しませんでした。

まとめ

本記事では、本番環境のテーブルカラムを安全に変更する方法について、実際の事例をもとに解説しました。
ポイントは以下の三つです。

  • 既存のカラムを直接変更するのではなく、新しいカラムを追加する
  • アプリケーションのコードを変更し、徐々にデータを新しいカラムに移行する
  • データの移行とアプリケーションコードの変更が完了したことを確認してから、古いカラムを削除する

上記のポイントをおさえることで、データの整合性を保ちつつ、サービスを停止させることなくカラムの変更を行うことができます。
また、途中でロールバックが必要になった場合でも、アプリケーションのコードを元に戻すだけで済むので、安全性が高いのも利点の一つです。

実際のシステム運用では、このようなデータベースの変更は避けて通れません。
変更の際は、十分な計画とテストを行い、安全性を確保することが重要です。
本記事が、皆さんのシステム運用の一助となれば幸いです。

Discussion