午前2時のストレージ緊急事態
時刻は午前2時、ページャーのアラートが鳴り響いています。プライマリPostgreSQLインスタンスのディスク容量が再び98%に達しました。今月すでに行われたEBSボリュームの拡張は2回目です。財務チームからは、なぜRDSの月額請求が12,000ドルまで急増したのかと問い詰められています。原因は、telemetry_logsテーブルが4TBまで膨れ上がったことでした。アプリケーションが日々の運用に必要としているのは、直近30日間のデータだけであるにもかかわらず、です。
5年分もの履歴ログを高性能なNVMeストレージに保存し続けるのは、リソースの無駄遣いです。それはまるで、高校時代の古い卒業アルバムを銀行の厳重な金庫に保管しているようなものです。高価で不要なだけでなく、バックアップ速度やメンテナンスウィンドウにも悪影響を及ぼします。ここで登場するのが「データ階層化(Data Tiering)」です。「ホット」なデータは高速なディスクに保持し、「コールド」なデータはAmazon S3のような安価なストレージにオフロードします。
アーカイブオプションの検討
データベースがストレージ制限に達し始めたとき、通常は3つの道があります。私はそのすべてを実装してきましたが、本番環境での比較は以下の通りです。
1. 「削除して忘れる」戦略
90日より古いデータを削除するcronジョブを実行します。最も単純な方法ですが、リスクが伴います。顧客から前年比の成長レポートを求められた瞬間に、手詰まりになります。現代のSaaSにおいて、データは完全に破壊したくない資産であることがほとんどです。
2. 二次アーカイブデータベース
古いレコードを、別の低スペックなPostgreSQLインスタンスに移行します。これによりプライマリの容量は空きますが、開発者にとっては大きな悩みの種になります。アプリケーション側でどちらのデータベースにクエリを投げるかを判断する複雑なロジックが必要になり、接続管理のオーバーヘッドが倍増します。
3. Foreign Data Wrapper (FDW) によるデータ階層化
これが最もバランスの取れたアプローチです。データをS3に移動させつつ、標準的なSQL経由でアクセス可能な状態を維持します。アプリケーションから見れば、そのデータは単なる別のパーティションのように見えます。予算面では、コストはわずか数円で済みます。今回はこの戦略を実装していきます。
S3階層化のトレードオフ
本番環境のワークロードを移行する前に、パフォーマンス特性を理解しておく必要があります。S3はローカルSSDの直接的な代替にはなりません。
- メリット:
- 大幅なコスト削減: S3 Standardেরコストは約0.023ドル/GBです。これに対し、AWS gp3ボリュームは0.125ドル/GB、プロビジョンドIOPSは0.20ドル/GB以上かかります。
- 無限の拡張性: S3には「ディスクフル」という状態がありません。設定変更なしで1TBから1PBまでスケールできます。
- プライマリDBの軽量化: アクティブなインデックスを小さく保つことで、それらをRAM上に常駐させることができます。これにより、「ホット」なクエリを10ms未満で実行し続けることが可能になります。
- デメリット:
- レイテンシの増加: ローカルクエリが20msで済むのに対し、S3に対する同じクエリは2〜5秒かかる場合があります。これはユーザー向けの機能ではなく、内部レポート用として活用すべきです。
- 管理オーバーヘッド: IAMロールの管理や、外部テーブルのスキーマがデータファイルと同期していることを確認する手間が発生します。
アーキテクチャ:パーティショニング + S3
本番レベルの構成では、PostgreSQLの宣言的パーティショニングとs3_fdwを組み合わせて使用します。このセットアップにより、1つの親テーブル(例:orders)に対し、orders_2024_q3(ディスク上)とorders_2022_archive(S3上)のような子パーティションを持たせることができます。
これらの移行作業中、レガシーデータの変換が必要になることがよくあります。テストや検証のためにCSVエクスポートをJSONに変換する必要がある場合は、toolcraft.app/ja/tools/data/csv-to-jsonが非常に便利です。ブラウザ上ですべてを処理するため、機密データが外部サーバーに送信されることがなく、SOC2コンプライアンスにおいても安心です。
ステップバイステップの実装手順
それでは、s3_fdwを設定してみましょう。このガイドでは、LinuxベースのPostgreSQLインスタンス、またはカスタム拡張機能を許可しているサービスを想定しています。
ステップ1:拡張機能のインストール
データベースで拡張機能を有効にします。AWS RDSやAuroraでは、通常代わりにaws_s3拡張機能を使用しますが、セルフマネージドのPostgreSQLではs3_fdwが標準的です。
CREATE EXTENSION s3_fdw;
ステップ2:外部サーバーの定義
PostgreSQLがS3バケットに到達する方法を知る必要があります。プレースホルダーを実際のAWSリージョンと認証情報に置き換えてください。
CREATE SERVER s3_server
FOREIGN DATA WRAPPER s3_fdw
OPTIONS (host 's3.us-east-1.amazonaws.com');
CREATE USER MAPPING FOR current_user
SERVER s3_server
OPTIONS (access_key 'YOUR_ACCESS_KEY', secret_key 'YOUR_SECRET_KEY');
ステップ3:外部テーブルのマッピング
次に、バケット内の特定のCSVファイルを指すテーブルを定義します。これがSQLクエリとS3オブジェクトの間の架け橋となります。
CREATE FOREIGN TABLE logs_2023_archive (
id bigint,
event_name text,
created_at timestamp
)
SERVER s3_server
OPTIONS (bucket 'my-company-archives', filename 'logs/2023_backup.csv', format 'csv');
ステップ4:データ移行の自動化
手動での移動はミスが発生しやすいため、ローテーションを処理する PL/pgSQL 関数をスクリプト化すべきです。ロジックは単純で、データをエクスポートし、ローカルストレージを削除して、S3テーブルをリンクするという流れです。
実際のパーティションの入れ替えは以下のようになります。
-- 1. ローカルデータをS3に移動(AWS RDSツールを使用する例)
SELECT * from aws_s3.query_export_to_s3(
'SELECT * FROM logs_old_partition',
aws_commons.create_s3_uri('my-company-archives', 'logs_old.csv', 'us-east-1')
);
-- 2. 重いローカルパーティションを削除
ALTER TABLE logs DETACH PARTITION logs_old_partition;
DROP TABLE logs_old_partition;
-- 3. S3パーティションを接続
ALTER TABLE logs ATTACH PARTITION logs_2023_archive
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
実践から学んだ教訓
データの階層化は、単なるコスト削減以上の意味を持ちます。それはデータベースを再び管理可能な状態に戻すことです。フルバックアップ(pg_dump)に12時間ではなく15分しかかからなくなれば、目標復旧時間(RTO)は劇的に改善されます。ただし、一点だけ覚えておいてください。S3は従来のデータベースインデックスをサポートしていません。アーカイブされたデータにクエリを投げる際は、エンジンがバケット内の全ファイルをスキャンするのを防ぐため、必ず日付範囲を指定するようにしてください。

