🐘

Cloud SQL で管理する複数データベースをひとつに統合するための試行錯誤

2025/03/07に公開

はじめに

ビットキーで bitkey platform を開発している @otakakot です。

bitkey platform では Cloud SQL for PostgreSQL を活用しています。
Cloud SQL では紆余曲折事情があり複数のデータベースをひとつのインスタンスで管理しています。
「Cloud SQL のメジャーバージョン更新に DMS を採用するのは複数データベース持ちには向いていないのかもしれない」という記事で複数データベース持ちは DMS を使うときに課題があるということが判明しました。

https://zenn.dev/bitkey_dev/articles/c71631a13a88fa

見つめ直した結果、複数データベースで管理する必要性はなくデータベースを統一しようという機運が高まりました。
システムは 24/365 で稼働しているためデータベース統合に伴うダウンタイムはなるべく短くしたいです。
また、移行に際してデータの欠損は無くしたいです。

本記事において Cloud SQL のメジャーバージョン更新に乗じてデータベースを統一することを推進した際にハマったポイントをご紹介します。

移行手順

データ移行(および Cloud SQL のメジャーバージョン更新)は Logigal Replication を利用した Blue/Green デプロイによって実現しました。

https://www.postgresql.org/docs/current/logical-replication.html

実際に移行した手順はこちらになります。

  1. 旧バージョン Cloud SQL インスタンスでの準備
    1. パブリケーション用のフラグを設定 & 再起動
    2. パブリケーション対象のテーブルを設定
      ※ データベースの数だけパブリケーションを設定
    3. レプリケーション用ユーザーを作成
  2. 新バージョン Cloud SQL インスタンスへのデータ移行
    1. 新バージョン Cloud SQL インスタンスの作成
      ※ このとき次回のバージョン更新に向けてパブリケーション用にフラグを設定
    2. マイグレーションによってテーブル等を作成
    3. データを削除
    4. サブスクリプションを設定
      ※ データベースの数だけサブスクリプションを設定
    5. データ同期完了を待機
  3. アプリケーションの接続先切り替え
  4. 後片付け
    1. 新バージョン Cloud SQL インスタンスのサブスクリプション設定を削除
    2. 旧バージョン Cloud SQL インスタンスのパブリケーション設定を削除
    3. 旧バージョン Cloud SQL インスタンスを停止 & 削除

ハマりポイント

レプリカインスタンスに対してサブスクリプションは設定できない

レプリケーションを実施するにはインスタンスで logical_decoding を有効化する必要があります。
この設定を行うにはインスタンスの再起動が必要となります。
再起動は1分程度で完了しますが、少しでもダウンタイムを避けるために冗長構成のために利用しているレプリカインスタンスへの設定で行えないか検討しました。

パブリケーションはプライマリインスタンスへと接続し作成します。
この適用はレプリカインスタンスにも反映されパブリケーションは適用されます。
準備をしていざ新バージョン Cloud SQL でサブスクリプションをレプリカインスタンスへと設定すると下記のエラーが発生し失敗します。

ERROR:  logical decoding cannot be used while in recovery

こちらのエラーを検索すると設定できないということが改めてわかりました。

https://stackoverflow.com/questions/67872689/can-you-logically-replicate-a-physical-postgres-replica

https://pganalyze.com/blog/5mins-postgres-16-logical-decoding#logical-decoding-can-not-be-used-while-in-recovery

レプリケーションはプライマリインスタンスに対して設定しましょう。

各データベースに同じテーブル名が存在する

論理レプリケーションは通常の DML 操作と同じように振る舞われます。

https://www.postgresql.jp/document/16/html/logical-replication-conflicts.html

この仕様を加味すると各データベースに同じテーブル名があった場合には注意が必要となります。
それぞれのデータベースにあるデータを統一しても問題がない場合は気にする必要はないのですが、もともと別のテーブルとして扱っているのであれば別のものとして扱いたいと思います。

※ 我々は同じテーブル名に対しては使われていないテーブルだったということがあり、該当のテーブルは削除してすることで事なきを得ました。

対応方針として以下の手順を検討しました。

  1. 移行前にテーブル名をそれぞれリネーム & アプリケーションの参照も更新
  2. 別のテーブルとして移行

移行作業前にマイグレーションおよびアプリケーションの変更が必要となります。
シンプルですがこの方式が確実かと思います。

SQL Dump によるテーブル移行ではなくマイグレーションによるテーブル作成

レプリケーションはDML操作が適用されるため移行先インスタンスにテーブルは事前に作成しておく必要があります。

テーブルを作成する選択肢として以下の2つがありました。

  • SQL Dump
  • マイグレーションファイルの適用

我々は下記の理由からマイグレーションファイルの適用を利用してテーブルを移行先インスタンスに用意することとしました。

  • Cloud SQL が作成するシステムテーブルを含めたくない
  • --schema-only だと FUNCTION が含まれない

レプリケーション対象からマイグレーション履歴テーブルは除外する

テーブル作成を SQL Dump ではなくマイグレーションによって行うため、新バージョンの Cloud SQL にはすでに migrations テーブルとその履歴が作成されます。
レプリケーションはデータがコンフリクトすると停止するという制約があります。

https://www.postgresql.jp/document/16/html/logical-replication-conflicts.html

旧バージョンの Cloud SQL の migrations テーブルを対象としてしまうとデータがコンフリクトし制約によってレプリケーションが停止してしまいます。
そのためパブリケーションを作成するときには CREATE PUBLICATION name FOR ALL TABLES; でなく対象のテーブル CREATE PUBLICATION name FOR TABLE xxx, yyy, zzz ...; と列挙して対応しました。

https://www.postgresql.org/docs/current/sql-createpublication.html

マイグレーション実施後にすべてのテーブルデータを削除する

マイグレーションファイルにデータを投入する SQL が存在すると、こちらもデータがコンフリクトしレプリケーションが停止してしまいます。
解決策は至ってシンプルで新バージョンインスタンスのデータをすべて削除することで対応しました。
効率的な SQL が他にも存在するかと思いますが以下の SQL を実行しています。

DO $$                   
DECLARE
  table_name text;
BEGIN
  FOR table_name IN
    SELECT tablename 
    FROM pg_tables
    WHERE schemaname = 'public'
  LOOP
    EXECUTE format('TRUNCATE TABLE %I', table_name);
  END LOOP;
END $$;

注意点としてこの SQL を実行するとマイグレーション履歴も削除されてしまいます。
そのため、再びマイグレーション履歴はデータを追加する必要があります。

おわりに

Cloud SQL for PostgreSQL のメジャーバージョン更新に乗じて複数データベースをひとつに統合することができました。
この方法を使えばダウンタイムは旧バージョンインスタンスの再起動時とアプリケーションの接続切り替えに抑えることができます。
この記事が Cloud SQL に複数データベースを抱えていてお困りの方のお役に立てれば幸いです。

Bitkey Developers

Discussion