コラム: 監査ログ、全部DBに入れとけばよくない?
「監査ログってとりあえずDBに書いておけばいいでしょ?」
— たいていのプロジェクトは、この一言から始まる。そして1年後に後悔する。
IDサービスを運用していると、監査ログは静かに、しかし確実に膨れ上がります。認証、トークン発行、API呼び出し。ユーザーが操作するたびにログは増え続けます。最初は数GBだったテーブルが、気づけば数百GB。クエリは遅くなり、バックアップは終わらず、ストレージ代は跳ね上がる。
このコラムでは、「監査ログをどう扱うか」について、記録・検索・保存・コストの4つの観点から考えます。
そもそも監査ログは何が特殊なのか
監査ログが厄介なのは、他のデータと根本的に性質が違うからです。
ユーザーデータは増えても、せいぜいユーザー数分です。セッションやトークンはTTLで自動消滅します。でも監査ログは 「全操作の記録」かつ「消してはいけない」 データです。増える一方で、減ることがない。
しかも、4つの要求が互いに矛盾します。
「1件も漏らさず記録したい」 ←→ 「認証を遅くしたくない」
「すぐに検索したい」 ←→ 「安く長期保存したい」
全部をDBに入れておけば記録も検索も簡単です。でもDBのストレージ代はS3 Glacierの700倍。7年保存が必要なデータを全部DBに抱えるのは、冷凍すればいい食材を全部冷蔵庫に入れているようなものです。
記録: 同期か、非同期か
最初に直面する判断が「監査ログを同期で書くか、非同期で書くか」です。
同期書き込みは単純です。認証処理のトランザクション内でINSERTするだけ。確実だし、実装も簡単。ただし、DBが遅くなると認証全体が道連れになります。
認証リクエスト → 認証処理 → 監査ログINSERT → レスポンス
↑
ここが遅いと全部遅い
非同期書き込み(SQS経由) にすれば、認証のレイテンシからは解 放されます。でも代わりに、コンシューマーの実装、DLQ(デッドレターキュー)の監視、リトライ設計が必要になります。しかもSQSへの送信自体が失敗すればログは欠損します。非同期にしたからといって、欠損リスクがゼロになるわけではありません。
結論: 小規模のうちは同期でいい。 テーブル設計がまともなら(後述のインデックスとパーティション参照)、数千万行までは認証レイテンシへの影響は最小限です。「ピーク時にDBの書き込みタイムアウトが出始めた」が非同期に切り替える合図です。
検索: 「いつのデータを探すか」で手段が変わる
監査ログに対する検索は3パターンあります。
| パターン | 例 | どれくらい速くほしいか |
|---|---|---|
| 運用調査 | 「ユーザーAの直近1時間のログイン試行」 | 秒以内 |
| インシデント対応 | 「このIPからの全アクセスを過去90日分」 | 数秒以内 |
| コンプライアンス監査 | 「2023年度のテナントXの全認証イベント」 | 分〜時間でも可 |
この3つを1つのデータストアで全部カバーしようとすると破綻します。直近データの検索を速くするためにインデックスを追加すれば書き込みが遅くなるし、7年分のデータにインデックス を張ればストレージが膨れ上がる。
解決策は「時間で分ける」ことです。直近のデータは高速に検索できる場所に、古いデータは安い場所に置いて、それぞれに合った方法で検索する。当たり前のことですが、意外とこれをやらずに「全部DBに入れてGROUP BYが遅い」と嘆くプロジェクトは多いです。
Aurora上の直近データについては、検索パターンに合わせたインデックスを3本程度に絞るのが現実的です。監査ログは書き込み中心なので、インデックスを増やしすぎると書き込み性能を食います。
-- この3本で運用調査とインシデント対応をカバーする
CREATE INDEX idx_tenant_user_time ON audit_logs (tenant_id, user_id, created_at DESC);
CREATE INDEX idx_tenant_event_time ON audit_logs (tenant_id, event_type, created_at DESC);
CREATE INDEX idx_ip_time ON audit_logs (ip_address, created_at DESC);
保存: 3層に分ける
ここが監査ログ設計の核心です。データの鮮度で3つの層に分離します。
書き込み
│
▼
┌──────────┐ 90日後 ┌──────────┐ 1年後 ┌──────────┐ 7年後
│ Aurora │ ──────→ │ S3 │ ─────→ │ Glacier │ ──→ 自動削除
│ (ホット) │ │(ウォーム) │ │(コールド) │
│ │ │ │ │ │
│ SQL検索 │ │ Athena │ │復元→Athena│
│ ミリ秒 │ │ 秒〜分 │ │ 分〜時間 │
└──────────┘ └──────────┘ └──────────┘
ホット層(Aurora、直近90日): 月次パーティションとインデックスで高速検索。90日を過ぎたらパーティションDROPで除去します。ここがポイントで、DELETE WHERE created_at < ... はテーブルスキャン+VACUUMが必要ですが、DROP TABLE audit_logs_2024_01 はメタデータ削除のみ。データ量が大きいほど差が出ます。
ウォーム層(S3 Standard、90日〜1年): AuroraからエクスポートしたデータをParquet形式で保存します。JSONのまま置くと、Athena のスキャン量(=料金)が10〜20倍になります。Parquetは必要なカラムだけ読むので、SELECT event_type, created_at FROM ... のようなクエリで効果が絶大です。
コールド層(S3 Glacier、1年〜7年): S3ライフサイクルポリシーで自動遷移。Object Lock(Complianceモード)で改ざん防止。年に1回あるかないかの監査対応用です。
移行はEventBridge+Step Functionsで毎晩自動実行。Aurora→S3エクスポート→Athenaパーティション更新→AuroraパーティションDROPの4ステップです。一度作れば放置できます。
コスト: 数字で見ると一目瞭然
「3層に分けるのは面倒だけど、本当に効果あるの?」 — 数字で見てみます。
前提: 1ユーザーあたり1日10イベント、1イベント1KB(JSON)/ 0.1KB(Parquet)
| 規模 | 3層設計 | Aurora全保持 | 差 |
|---|---|---|---|
| 1万ユーザー | 〜$0/月 | $3/月 | ほぼ変わらない |
| 10万ユーザー | $15/月 | $90/月 | 6倍 |
| 100万ユーザー | $123/月 | $900/月 | 7倍以上 |
1万ユーザーでは差はわずかです。だから最初は「全部DBでいい」は正しい。でも10万ユーザーを超えたあたりから差が開き始め、100万ユーザーでは年間$9,000以上の差になります。
しかもこれはストレージ代だけの話です。Aurora上に数百GBのテーブルを抱えると、クエリが遅くなる、バックアップに時間がかかる、リストアのRTOを満たせない、といった問題がコストとは別に発生します。
100万ユーザーの監査ログ(7年分、全部Aurora):
Aurora上のデータ量: 約 2.5TB
バックアップ所要時間: 数時間
リストア所要時間: 数時間
90日前のログ検索: 秒〜分(パーティションあれば)
3年前のログ検索: 分〜タイムアウト(使い物にならない)
100万ユーザーの監査ログ(3層設計):
Aurora上のデータ量: 約 90GB(直近90日のみ)
バックアップ所要時間: 数十分
リストア所要時間: 数十分
90日前のログ検索: ミリ秒〜秒
3年前のログ検索: 秒〜分(Athena)
別の選択肢: 最初からS3に書いたらどうなる?
ここまで「Aurora → S3 → Glacier」の3層設計を説明してきましたが、「そもそもAuroraを経由せず、最初からS3に書けばもっとシンプルで安いんじゃないか?」という疑問は自然です。
Kinesis Data Firehoseを使えば、アプリからのログをバッファリングしてS3にParquet形式で直接書き出せます。Auroraは完全にスキップ。
S3直接書き込みアーキテクチャ:
アプリ ──→ Kinesis Firehose ──→ S3 (Parquet)
バッファ: 60秒 or 1MB │
Athena で検索
コストは確かに安い。 Aurora不要なので、10万ユーザーで月$15だった監査ログ関連コストが$5以下になります。
でもこのアーキテクチャには明確なトレードオフがあります。
検索のレイテンシが秒〜分になる。 「さっき失敗したログインを調べたい」という運用調査で、Auroraならミリ秒で返るクエリがAthenaでは数秒〜十数秒かかります。1日に何十回も調査する運用者にとって、この差はストレスになります。
書き込みからの反映ラグがある。 Firehoseはバッファリングする(デフォルト60秒〜5分)ので、「今起きたイベント」がすぐには検索できません。インシデント対応の初動で「直近5分のログが見えない」のは地味に痛い。
Athenaはクエリごとに課金される。 1回あたりは安くても($5/TBスキャン)、運用者が1日50回クエリを打つ文化だとそれなりに積み上がります。Auroraなら何回SELECTしてもインスタンス料金内です。
Aurora + S3 3層 S3直接(Athena)
────────────── ─────────────
直近ログの検索速度 ミリ秒〜秒 数秒〜十数秒
書き込み反映ラグ なし(同期) 60秒〜5分
1日50回の調査 追加コストなし $0.5〜$5/日