本文へ移動

運用と信頼性/ Reliability & operations

20 年運用に耐える信頼性レイヤとして、多層ウイルススキャン、追記専用の監査ログ、三段階の保持期間運用を組み合わせます。日常運用と長期運用の両方を見据えた設計を本ページで整理します。

01

多層ウイルススキャンMulti-layer scan

ファイルが利用可能になるまでに 4 層のチェックを通します。クライアントでの形式検証、5 バケット分離によるアップロード隔離、ClamAV による本体スキャン、最後に Google Drive 側の標準スキャンが入る構造です。

図 01 · 4 層スキャンの流れ UPLOADULAYER 1クライアント検証拡張子 / マジックバイトサイズ ≤ 200MBLAYER 25 バケット分離未検査バケット → Eventarc→ scanner サービスLAYER 3ClamAV 1.4本体スキャンfreshclam 6h ごとLAYER 4Drive 標準スキャン同期時に再スキャンGoogle 側で実行REJECTUI エラーQUARANTINEscan_status='infected'CLEANユーザに開示

クライアントでの形式検証を通過した後、ファイルはまず 未検査バケット に保管されます。Eventarc が object.finalized イベントを docshare-scanner サービスへ Push し、ClamAV が本体をスキャンします。clean なら 検査済みバケット に rewrite され、Drive 同期 ON の Box ならその後 Drive 側でも標準スキャンが走ります。infected の場合は 隔離バケット に隔離され、system_admin・UL者・Box Owner に即時メールが届きます。

LAYER 01

クライアント検証

拡張子(許可・拒否リスト)、マジックバイト、ファイルサイズ(200 MB 以下)を Web ブラウザ上で検証します。マクロ Office(.docm / .xlsm / .pptm)と古い Office(.doc / .xls / .ppt)はここで一律拒否します。

LAYER 02

5 バケット分離 + Eventarc

アップロード直後のファイルは 未検査バケット に置かれ、ユーザからは開示されません。Eventarc が object.finalized イベントを docshare-scanner に OIDC 認証付きで Push します。

LAYER 03

ClamAV 1.4

Go + ClamAV を載せた Cloud Run(min=1 / 大きめのメモリ)が本体スキャンを実行します。シグネチャは freshclam が 6 時間ごとに更新し、月次の EICAR 自動テストでスキャナの常時動作を検証します。

LAYER 04

Drive 標準スキャン

Drive 同期 ON の Box では、clean 後のファイルが Drive にもアップロードされ、Google 側で再度マルウェアスキャンが走ります。万一 ClamAV を抜けても、ここで重ねがけ防御になります。

02

改ざんを防ぐ監査ログAudit log integrity

監査ログは PostgreSQL の RLS(行レベルセキュリティ)で UPDATE と DELETE を拒否します。これに Supabase の自動バックアップと月次パーティションの GCS 退避を組み合わせ、3 重防御で完整性を担保します。

Layer 1 · DB

追記専用 RLS

audit_logs テーブルには INSERT と SELECT のポリシーのみを設置し、UPDATE と DELETE は RLS で拒否します。データベース管理者であってもアプリ経路からはレコードを書き換えられません。

Layer 2 · Backup

Supabase 自動バックアップ + PITR

Supabase Pro の自動スナップショットに加え、PITR(Point-in-Time Recovery、特定時刻への復元)で一定期間内の任意時点に戻せます。バックアップは一定期間保持します。

Layer 3 · Archive

月次パーティション + GCS 退避

pg_partman で audit_logs を月次パーティションに分割し、25 ヶ月超のパーティションを Cloud Storage の archive class へ CSV で退避します。一定期間の後に完全削除します。

ハッシュチェーンは不採用: 金融・医療レベルの完整性要件であれば prev_hash / row_hash を持つハッシュチェーンも検討対象ですが、一般企業の説明責任要件は上記 3 重防御で十分と判断しました。要件が変われば再評価します。

03

保持期間と Hold(リーガルホールド)Retention & Legal Hold

保持期間は 3 段階の階層(全社デフォルト → Box デフォルト → 個別 Hold)で運用します。アクセスのたびに期限を延長するローリングウィンドウと、UL 直後の 30 日凍結期間を組み合わせて、「うっかり消える」と「無制限に増える」の両方を防ぎます。

項目仕様
三層階層全社デフォルト(system_admin、初期 180 日) → Box デフォルト(Owner、無制限選択可) → 個別 Hold(Editor / Owner)。優先順位: Hold > Box デフォルト > 全社デフォルト
ローリングウィンドウDL / プレビュー / 差し替え / コメント / 共有リンク発行のたびに retention_expires_at = now() + 保持期間 に更新。Box 内の他ファイルアクセスでは更新しない
凍結期間UL 直後の最低 30 日は削除候補に含めない。retention_expires_at = max(retention_expires_at, created_at + 30d)
個別 HoldEditor / Owner が任意ファイルに対し 理由テキスト必須で Hold を設定。Hold ファイルは自動削除対象から完全に外れる
Hold バッジBox 内 Hold 合計容量がしきい値を超えると、Box 一覧・Box 詳細・/admin/boxes の 3 箇所に常時バッジを表示
削除当日メール通知削除当日にメール送信。宛先は Box Owner(必須) + 最終アクセスユーザ + UL者。配信モード(immediate / digest / off)を問わず immediate 強制
共有リンク有効ファイル共有リンクが有効なファイルは「失効 + 7 日」まで削除を延期
削除実行ジョブretention-deletion-daily(毎日、Cloud Run Jobs)。論理削除 + 当日メール + audit_logs 記録 → Day 30 で trash-cleanup-daily が物理削除 + Drive ゴミ箱送り
影響シミュレーション全社・Box デフォルト変更時に「N ファイル / X GB が新デフォルトで自動削除対象になります」を即座に表示。確定で既存 retention_expires_at を一括再計算 + audit_logs
04

Drive アーカイブ参照Drive Archive view

アプリ側で削除された後も、Drive に残っているファイルを read-only で参照できる「Drive アーカイブ」タブを Box 詳細に持ちます。Drive 同期 ON の Box でのみ表示されます。

項目仕様
対象アプリ側で削除済み(files.deleted_at IS NOT NULL)かつ Drive 側にまだ残っているファイル
UIBox 詳細の /boxes/[id]/drive-archive タブ。アプリ操作は DL と参照のみ可(編集・復元は不可)。Viewer 以上で参照可
データソースdrive_archive_files テーブル(box_id / original_file_id / name / drive_file_id / drive_web_view_link / deleted_at / deletion_reason / exists_in_drive / last_verified_at)
週次バッチdrive-archive-sync が週次で Drive API を叩き、exists_in_drive を更新。Drive 上で復元・削除されたケースに追従
監査参照時に drive_archive.viewed / drive_archive.downloaded として audit_logs に記録
05

監視・アラートMonitoring & alerts

通知先は system_admin 全員にメールで統一します。Slack / PagerDuty は採用せず、運用負荷とコストを抑えます。アプリ独自の業務的な信号は app_alerts テーブルにライト版で起票し、/admin/alerts から閲覧できます。

条件通知
Cloud Run の 5xx 率 > しきい値Cloud Monitoring → メール(system_admin 全員)
scan_status='infected' > 0即時メール(system_admin + UL者 + Box Owner、Resend)
ClamAV シグネチャ更新の連続失敗メール(system_admin 全員)
sync_jobs.status='dead_letter' > 0メール(system_admin 全員)
app_alerts の critical > 0メール(即時、system_admin 全員)
Cloud Billing 月予算超過段階的な割合でメール
例外・エラーCloud Error Reporting が Cloud Logging から自動集約。Sentry SaaS は不採用

app_alerts の 7 種: ① 感染検出、② Pub/Sub DLQ のしきい値超過、③ Drive 同期の連続失敗、④ 共有リンク ブルートフォース、⑤ Resend バウンス率のしきい値超過、⑥ ストレージ 80%、⑦ Cloud Billing 超過。/admin/alerts から「対応中 / 対応完了 / 無視(理由付き)」の手動ステータス遷移ができます。

06

運用作業のサイクルOperations cadence

リアルタイムのアラート対応に加えて、日次・週次・月次・四半期・年次のリズムで運用作業を分散させます。20 年運用に耐えるための仕掛けを定期的に動かします。

サイクル作業内容
リアルタイムapp_alerts の critical / warning への対応。Cloud Run 5xx 率や ClamAV 異常検知のメール対応
日次/admin ヘルスサマリ確認、日次バッチ(保持削除・Drive 補修・admin_metrics 集計)の成否確認
週次共有リンク監査(残存 7 日以内のハイライト確認)、サマリ通知(digest)の到達確認
月次ストレージ予算レビュー、/admin/boxes / /admin/users の死蔵ハイライト確認、ClamAV 検出テスト(EICAR)
四半期バックアップリストア演習(DB を過去の時点へ PITR でリードオンリー復元)、SLO レビュー
年次シークレット一括ローテ(JWKS / OAuth client_secret / Resend API Key / BETTER_AUTH_SECRET / Supabase DB パスワード)、災害復旧訓練、SLA / SLO 見直し、無効化ユーザの 5 年保持期限確認
07

長期運用バッチ(18 種)Background jobs

ユーザ操作の裏で動く 18 種類の定期バッチを、単一の Cloud Run Jobs サービス docshare-batch に統合しています。Cloud Scheduler から起動し、--args でジョブ種別を振り分けます。

ジョブ頻度役割
outbox-dispatcher1 分Outbox テーブルの未送信イベントを Pub/Sub に publish
dlq-monitor5 分Dead Letter Queue の件数を監視し、しきい値超で critical アラート起票
share-link-cleanup5 分失効期日を過ぎた共有リンクを revoked 化
digest-worker30 分該当時刻のユーザの digest 通知をまとめて Resend 送信
retention-deletion-daily毎日保持期限切れファイルの論理削除と当日メール通知
trash-cleanup-daily毎日論理削除 30 日経過分の物理削除 + Drive ゴミ箱送り
drive-repair毎日Box 単位の並列で Drive 差分補修・orphan 検知・削除検知
admin-metrics-aggregator毎日storage / user_activity / box_activity の集計と materialized view 更新
retention-warning-aggregator毎日Box Owner 向け週次「今週削除予定」サマリ生成
expired-invitations-cleanup毎日期限を過ぎた招待・参加申請を自動失効
workspace-directory-sync毎日Google Workspace Directory を読んで在籍状態をユーザに反映
version-cleanup-weekly週次Box 設定の世代数を超えた旧バージョンを GCS から削除
drive-archive-sync週次Drive アーカイブの exists_in_drive を週次で更新
audit-log-partition-rotator月次25 ヶ月超のパーティションを GCS archive class へ退避し DROP
notifications-cleanup-monthly月次保存期間を過ぎた通知履歴を単純 DELETE
clamav-eicar-test月次EICAR テストファイルを自動 UL して検出確認の E2E
disabled-user-cleanup-5y月次5 年経過の無効化ユーザを system_admin 承認のうえ完全消去
jwks-rotator年次Better Auth の JWT 鍵を年次ローテ、旧鍵の一定期間の並行公開で無停止切替
08

コストの構成Cost structure

費用は、Cloud Run(サービス 4 + Jobs 1)・Cloud Storage(保管と転送)・Cloud Armor / ロードバランサ / 監視の従量課金と、Supabase Pro のプラン料金、Resend(無料枠を超えた分だけ従量)で構成します。Cloud Billing で月予算を設定し、段階的な割合でメール通知します。

09

長期運用リスクと軽減策Long-term risks & mitigations

20 年運用を見据えたとき、想定すべきリスクと対処をあらかじめ設計に組み込みます。ここでは代表的な 4 件を挙げます。詳細リスト(R-01 〜 R-25)は別資料に整理しています。

R-01

監査ログ肥大化

操作のたびに行が増えるため、20 年運用ではテーブルが極めて巨大化します。インデックスや VACUUM の負荷も増えます。

軽減策: pg_partman で月次パーティション、25 ヶ月超を GCS archive class へ退避(CSV / Parquet)、一定期間の後に完全削除。常時オンラインのテーブルサイズを抑えます。

R-03

GCS 容量暴走

保持期間設定や Hold の使い方によっては、ストレージ容量が想定外に増加し、月額コストを圧迫します。

軽減策: 全社デフォルト 180 日 + Hold 方式、/admin/storage での予算ライン警告、Object Versioning の noncurrent_version_expiration(一定期間)、Cloud Billing アラート、Box 別 Hold バッジ常時表示(一定容量以上)。

R-07

JWT 鍵の長期未ローテ

Better Auth が発行する JWT 鍵を放置すると、鍵漏洩時の影響範囲が拡大します。手動ローテは忘れがちです。

軽減策: jwks-rotator Job が年次ローテを自動実行、旧鍵を一定期間並行公開して無停止切替。鍵の使用が一定日数を超えると warning アラート。

R-05 / R-06

死蔵ユーザ・退職者残存

退職者のアカウントがいつまでも残ったり、未ログインユーザが滞留すると、不要な攻撃面が広がります。

軽減策: 60 日未ログインを /admin/users でハイライト、workspace-directory-sync で Google Workspace の在籍状態を反映し自動無効化を提案、expired-invitations-cleanup で 30 日経過招待を失効、disabled-user-cleanup-5y で 5 年経過後の完全消去。