モノリスを超えたスケーリング
6ヶ月前、当社のプライマリである800GBのデータベースは限界に達しました。チェックアウト取引の処理、ユーザーセッションの管理、そしてマーケティングチームのための大規模な分析ジョインなど、すべてを一度に処理しようとしていたからです。
15以上のテーブルをジョインすることも珍しくないマーケティングクエリは、CPU使用率を95%まで急上昇させ、実際の顧客に対してランダムなAPIタイムアウトを引き起こしていました。レポートデータを別のインスタンスに移動する必要がありましたが、譲れない要件が2つありました。機密性の高い個人情報(PII)や重いログテーブルを除外すること、そして1分たりともダウンタイムを許容しないことです。
物理ストリーミングレプリケーションは高可用性の標準ですが、融通が利きません。インスタンス全体のバイト単位のコピーを作成するため、2019年の2TBのアーカイブテーブルだけをスキップするといったことができません。そこで私たちを救ったのがロジカルレプリケーションです。特定のテーブルだけを別のデータベース(月額40ドルの安価なインスタンスでも可)にストリーミングできました。本番で半年間運用してみて、標準ドキュメントでは見落とされがちな落とし穴が見えてきました。
コアコンセプト:パブリケーションとサブスクリプション
ロジカルレプリケーションはシンプルなパブリッシュ・サブスクライブ(出版・購読)モデルを使用します。生のWAL(Write-Ahead Log)ファイルを送信する代わりに、PostgresはそれらのログをINSERT、UPDATE、DELETEといった行レベルの変更にデコードします。
- パブリッシャー: プライマリとなるソースデータベース。「パブリケーション」を定義します。これは共有したいテーブルのリストです。
- サブスクライバー: ターゲットとなるデータベース。パブリッシャーに接続してデータを取得する「サブスクリプション」を作成します。
- ロジカルデコーディング: WALから変更を抽出するエンジンです。これを有効にするには、
wal_levelをlogicalに設定する必要があります。
大きなメリットは、サブスクライバー側のデータベースが完全に書き込み可能な状態であることです。プライマリデータベースを遅くすることなく、レポート作成用に特化したインデックスをサブスクライバー側に追加できました。3つの異なるマイクロサービスからのデータを1つのセントラルレポートハブに集約することさえ可能です。柔軟性を重視して設計されています。
実践:最初のストリームをセットアップする
サーバー間のネットワーク経路を確認するまで、本番コンソールには触れないでください。以下は、私たちが移行に使用した正確なワークフローです。
ステップ1:パブリッシャー側の設定
まず、postgresql.confを調整します。wal_levelの変更には再起動が必要なため、トラフィックの少ない時間帯にスケジュールしてください。私たちは火曜日の午前3時に行いました。
# postgresql.conf を編集
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
サービスが再起動したら、サブスクライバーからの接続を許可するために pg_hba.conf を更新します。攻撃対象領域を最小限に抑えるため、最小限の権限を持つ専用ユーザーを使用してください。
-- 専用のレプリケーションユーザーを作成
CREATE ROLE replication_user WITH REPLICATION LOGIN PASSWORD 'your_secure_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO replication_user;
ステップ2:パブリケーションの作成
次に、どのテーブルを同期するかソースデータベースに伝えます。もしターゲットデータベースに外部データを先に投入する場合は、toolcraft.app/ja/tools/data/csv-to-jsonがおすすめです。ブラウザ上で完結するため、機密データがマシンから出ることはありません。スキーマの準備に最適です。
パブリッシャー側データベースにて:
-- 特定のテーブルに対してパブリケーションを作成
CREATE PUBLICATION my_reporting_pub FOR TABLE orders, customers;
ステップ3:サブスクライバー側の準備
データが流れ始める前に、サブスクライバー側にテーブル構造を用意しておく必要があります。ロジカルレプリケーションは CREATE TABLE などのDDL変更を同期しません。pg_dump を使用して、データなしのスキーマのみを取得します。
# パブリッシャーからスキーマのみをエクスポート
pg_dump -h publisher_host -U postgres -s my_db > schema.sql
# サブスクライバーにスキーマをインポート
psql -h subscriber_host -U postgres my_reporting_db < schema.sql
ステップ4:サブスクリプションの作成
最後のステップは、両者を接続することです。このコマンドを実行した瞬間、サブスクライバーはスナップショットフェーズを開始し、既存の行をコピーしてからリアルタイムストリーミングに切り替わります。
-- サブスクライバーデータベースにて
CREATE SUBSCRIPTION my_reporting_sub
CONNECTION 'host=publisher_host dbname=my_db user=replication_user password=your_secure_password'
PUBLICATION my_reporting_pub;
本番運用6ヶ月で学んだこと
初期設定は簡単ですが、メンテナンスこそが課題です。180日間の運用で、3つの厳しい教訓を得ました。
1. スキーマ変更の罠
パブリッシャーにカラムを追加してもサブスクライバー側の更新を忘れると、レプリケーションは即座に停止します。さらに悪いことに、サブスクライバーが停止している間、パブリッシャーのWALログは際限なく増え続けます。現在、私たちはDDL変更をまずサブスクライバーに適用するようにしています。これにより、パブリッシャーが送信を開始する前に、ターゲット側が常に新しいデータ形式を受け入れる準備ができている状態を保証します。
2. レプリケーションスロットの監視
レプリケーションスロットは諸刃の剣です。サブスクライバーが数時間オフラインになると、パブリッシャーはすべての変更をディスクにバッファします。4ヶ月目のネットワーク障害の際、パブリッシャーのディスク使用量は2時間で120GBも急増しました。現在、私たちは pg_replication_slots を15分おきに監視し、ディスクがいっぱいになる前に遅延を検知するアラートを設定しています。
SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS delay_bytes
FROM pg_replication_slots;
3. シーケンスのギャップ
シーケンスは複製されません。SERIAL や IDENTITY カラムを使用している場合、サブスクライバーのシーケンス値は1のままです。読み取り専用のレポート作成なら問題ありませんが、フェイルオーバーを試みる場合は致命的です。サブスクライバー側への書き込みを想定している場合は、シーケンス値を手動で同期する必要があります。
最後に
40%の負荷削減という結果がすべてを物語っています。複雑なクエリをオフロードすることで、メインAPIのレイテンシは平均で60ms低下しました。ロジカルレプリケーションは「設定して終わり」のツールではありません。積極的な監視と規律あるスキーマ移行のアプローチが求められます。しかし、選択的なデータ同期において、Postgresエコシステムで最も信頼できるツールです。もしあなたのモノリスが分析クエリの重みで苦しんでいるなら、負荷を分散させる時が来ています。

