記録の基盤/ The Log
何を、どんな形で、どこへ残しているか
記録の置き場は audit_logs という 1 本のテーブルです。この章では、そこに何が入るか(操作の種類・1 行の項目・行為者の種類)と、どう守られているか(追記だけを許す規則・月ごとの分割・書き込みの規律)を扱います。読み出す側は次の 3 章で扱います。
記録される操作What Gets Recorded
現在 163 種類です。一覧は共有の定数ファイル 1 本が唯一の置き場で、日本語の表示名も同じファイルに同数そろえてあります。片方だけ増えると自動テストが落ちるので、名前だけ増えて表示名が無い、という状態になりません。
| 領域 | 種類 | 主な中身 |
|---|---|---|
| ファイルとフォルダ | 39 | アップロード・差し替え・改名・移動・削除・復元・版の復元・完全削除・アーカイブ・保持の設定・ゴミ箱を空にする・検索・メタデータ編集・コメント・プレビュー・一括ダウンロード |
| 認証とアカウント | 25 | サインイン・サインアウト・二要素認証の登録と解除・パスワード再設定・メールアドレスの変更・端末の信頼・規約への同意・プロフィールとアバターの変更・アカウントの停止と復帰・社内ディレクトリとの同期 |
| Box とメンバー | 20 | Box の作成・設定変更・公開範囲の変更・保持期間の変更・無効化・メンバーの追加とロール変更と除外・オーナーの引き継ぎ・参加申請の受付と承認 |
| 管理と運用 | 17 | 管理画面の閲覧・ユーザの一括停止・Box の強制削除・ストレージの操作・指標の集計に伴う記録 |
| ウイルス検査 | 13 | 検査の完了・感染の検出・検査の失敗・検証不能の確認・再検査・隔離ファイルの取り出しと完全削除 |
| 共有リンクとファイルリクエスト | 13 | 発行・無効化・強制失効・受取側からのアクセス・提出の取り込み |
| 通知の配信 | 12 | 通知の作成・送信・再送・日次まとめの生成・受信設定の変更・不達の記録 |
| Drive 同期 | 12 | 同期の開始と完了・失敗・再同期・管轄外ファイルの検知・アーカイブ側の存否確認 |
| 監査とアクティビティ | 6 | 監査ログの閲覧・CSV の取り出しとその受付・退避分の取得・アクティビティの取り出しとその受付 |
| アラートと配信の運用 | 6 | アラートを対応中にする・解決する・無視する・失敗イベントの再投入と破棄・滞留した通知の再キュー |
名前は {対象}.{動作} の形で付けています(file.upload_initiated、member.role_change、share_link.revoke など)。新しい操作を実装するときは、先に定数へ追記してから実装する順序を規律にしています。逆順にすると、名前の綴りがコードのあちこちに散った状態で固まってしまうためです。
1 行に入るものAnatomy of a Row
列は 13 です。「いつ・誰が・何を・何に対して」を固定の列で持ち、操作ごとに違う中身だけを 1 つの自由項目にまとめています。
| 列 | 必須 | 内容 |
|---|---|---|
occurred_at | 必須 | 発生時刻。月ごとの分割のキーでもある |
id | 必須 | 連番。月ごとに別の番号列を使う。主キーは (occurred_at, id) の組 |
actor_type | 必須 | 行為者の種類(6 値・次節) |
actor_id | — | 操作した利用者。システムの操作では空 |
actor_share_link_id | — | 共有リンク経由のときの発行元リンク |
actor_file_request_id | — | ファイルリクエスト経由のときの発行元依頼 |
action | 必須 | 操作の名前(163 種のいずれか) |
box_id | — | 対象の Box。Box に紐づかない操作(サインインなど)では空 |
target_type | 必須 | 対象の種類(ファイル / フォルダ / メンバー / 共有リンク / コメント / Box / 利用者 / 通知) |
target_id | — | 対象の識別子 |
request_id | — | 1 回のリクエストを束ねる識別子。同じ操作から派生した複数行を辿れる |
reason | — | 重大な操作の理由。専用の列で、10〜256 文字の制約がデータベース側に入っている |
metadata | — | 操作ごとに違う中身(版の番号・変更前後のロール・期限・件数など)。JSON |
接続元の IP と端末情報の置き場
IP アドレスと端末の情報(ブラウザの種類など)は専用の列を持っていません。自由項目のなかに入っています。操作の 163 種すべてに付くものではなく、付ける意味があるのは一部(アップロードやサインインなど)だからです。
絞り込みを速くするための索引
索引は月ごとの子テーブルに 5 本ずつ張っています。うち 3 本は「値が入っている行だけ」を対象にした部分的な索引で、空の行がほとんどを占める列(Box や対象の種類)でも索引が小さく保たれます。
- Box と時刻 — Box のアクティビティの主経路
- 操作者と時刻 — 「この人の操作だけ」の絞り込み
- 操作の名前と時刻 — 「差し替えだけ」「削除だけ」
- 対象の種類と識別子と時刻 — 「このファイルに起きたこと」
- 時刻のみ — 新しい順の一覧(既定の表示)
10 万行を投入した状態で実行計画を固定する自動テストを持っています。守っているのは「索引が並び順を供給し、先頭のページで打ち切れること」と「テーブル全体の走査が現れないこと」です。実行計画そのものを 1 つに固定するのではなく、許容できる索引の集合でアサートしています(絞り込みの有無でデータベースがどちらを選んでも正しいため)。
行為者の 6 種類Six Kinds of Actor
「誰が」の欄は、ログインした利用者だけではありません。アカウントを持たない相手の操作や、システムが自動で行った操作も同じテーブルに残るため、種類を 6 つに分けています。
| 種類 | 誰の操作か | 識別子の入り方 |
|---|---|---|
| 利用者 | ログイン済みの一般の利用者(Box のメンバー) | 利用者の欄に入る |
| システム管理者 | 全社の管理権限を持つ利用者 | 利用者の欄に入る |
| 社内の閲覧者 | 社内に公開されている Box を、その Box のメンバーではない社内の利用者が開いたとき | 利用者の欄に入る |
| 共有リンク | リンクを受け取った社外の相手(アカウントなし) | 発行元リンクの欄に入る。利用者の欄は空 |
| ファイルリクエスト | 提出を依頼された社外の相手(アカウントなし) | 発行元依頼の欄に入る。利用者の欄は空 |
| システム | バッチ・ワーカー・自動処理 | 3 つの欄すべてが空 |
この 6 通りの「どの欄が埋まるか」は、データベースの制約として4 分岐で強制しています。たとえば共有リンク経由の行に利用者の識別子が入っていたら、その行は書き込みの時点で拒否されます。行為者の種類と識別子の組み合わせが崩れた行は、後から見つけても直せない(更新できない)ため、入る前に止めています。
追記だけを許すアクセス制御Append-Only Access
データベースには「誰がどの行に何をしてよいか」を規則として書く仕組みがあります。記録のテーブルでは、追加と参照の規則だけを書き、更新と削除の規則を書いていません。次の図は、その結果として何が通り何が止まるかを示したものです。
赤い枠は「通らない」ことを示します。4 行目の拒否がエラーではなく 0 件なのは、記録の存在そのものを伏せるためです。5 行目は権限の設定ミスで緩む余地がありません(規則が無いものは、権限をどう付け替えても許可になりません)。この 4 通りはすべてデータベースを実際に動かす自動テストで固定してあります。
自分の依頼を「成功」に偽装できないようにする
CSV の取り出し依頼を記録する台帳のテーブルには、もう 1 段の防御を入れています。利用者が追加できる列を限定し、状態・出力先・件数・完了時刻といった列は追加の対象から外してあります。外さないと、「状態は成功、出力先は退避済みの月のファイル」という行を自分で追加し、それに対してダウンロード用の URL を発行させる、という経路が成立してしまいます。
さらに URL を発行する側でも、出力先の文字列がその依頼の専用の場所を指しているかを毎回確かめています。1 段目(列の限定)を越えられても、2 段目で止まります。
月ごとに子テーブルへ分けるMonthly Partitions
記録のテーブルは、見た目は 1 本ですが、内部では月ごとの子テーブルに分かれています。書き込みも読み出しも窓口は親テーブル 1 つで、どの子に振り分けるかはデータベースが決めます。
「受け皿」は、どの月の範囲にも当てはまらない行が入る子テーブルです。正常な運用では 1 行も入りません。ここに行が入るのは「先の月を作り忘れた」ときで、放置すると気づかないまま記録が 1 か所に溜まるため、月次の点検で件数を実際に数えて確認しています(統計の推定値は使っていません。未集計のときは鳴らず、古い値のときは誤って鳴るためです)。
書き込みの規律Write Discipline
記録は「業務処理のついでに書く」ものなので、書き方を揃えておかないと、実装のたびに少しずつ違う形が混ざります。次の 3 つを規律として固定しています。
| 規律 | 内容 | 破ったときに何が起きるか |
|---|---|---|
| 同じ区切りで書く | 業務データの更新と記録の追加を、同じトランザクションに入れる | 記録だけが残る/業務データだけが残る、という食い違いが生じる |
| 返さない一括追加 | ログイン利用者の経路では、追加した行を読み返さない形だけを使う | 本番で必ず失敗する(読み返しは参照に当たり、一般の利用者には参照の権限が無い)。レビューの見落としがあっても混入しない |
| 名前を先に決める | 新しい操作は、共有の定数へ追記してから実装する | 綴りがコード中に散り、一覧と実態が合わなくなる。機械検査が実装より先に落ちる形にしてある |
自由項目に個人情報を入れない
操作ごとに違う中身は自由項目(JSON)に入りますが、ここに何を入れるかは操作ごとに精査しています。とくにメールアドレスの実値は、原則として入れません。たとえば共有リンクの受取人や、ファイルリクエストの提出者のアドレスは、記録には残さず、表示が必要になった時点で発行元のテーブルから引き直し、伏せ字にしてから返す形にしてあります。記録に入れてしまうと、後から伏せることも消すこともできなくなるためです。
例外として、接続元の IP と端末情報は自由項目に入ります。これは調査に必要な情報で、参照できるのはシステム管理者だけ、取り出すには理由の入力が要る、という形で扱いを制限しています。