本文へ移動
松葉 / Matsuba ・ 開発過程資料

設計の背景/ Design Rationale

何を守るために、どこに線を引いたか

この章では、記録と検知の作りを決めた判断を順に説明します。どの判断も「素朴に作ると何が空くか」から出発しています。個別の実装は次章以降で扱うので、ここではなぜその形なのかだけを扱います。

01

解決する業務課題The Problem

やり取りをメールと紙から Web へ移すと、操作そのものは 1 か所で起きるようになります。ただし記録は自動では残りません。残す仕組みを作らなければ、Web に移した分だけ「後から辿れない操作」が増えます。

実際に問い合わせとして起きるのは、次のような場面です。いずれも「その時点で誰かが書き留めていたか」だけが答えを決めます。

Questions That Need a Log
聞かれること答えるために必要な記録記録が無いと
取引先に渡した資料の最新版はどれか。誰がいつ差し替えたかファイルの差し替えと版の記録ファイルの更新日時しか残らず、誰がどの版に替えたかは復元できない
この見積を、どなたが受け取って開いたかダウンロードとプレビュー、共有リンク経由のアクセスの記録「送ったはず」までしか言えない
先月このフォルダを触ったのは社内の誰と、取引先のどなたかBox 単位で時系列に並んだ操作の履歴心当たりを人に聞いて回ることになる
退職者が最後に何を持ち出したか操作者で絞り込める全社の記録調べる手段が無い
先週の通知メールが届いていないが、送信自体は行われたのか配信の状態と、その滞留を判定する仕組み本人が申告するまで誰も気づかない

最後の 1 行は種類が違います。前の 4 つは「起きたことを残す」話ですが、これは「起きていないことに気づく」話です。サーバが落ちればクラウドの監視が拾いますが、サーバは正常に動いていて通知メールだけが出ていない、という故障は外からは見えません。判定にはアプリのデータベースの中を数える必要があります。この資料が扱う 2 つの柱は、この 2 種類にそれぞれ対応しています。

02

記録を 1 本に集めるOne Table

記録の置き場は 1 本のテーブルだけです。「Box のメンバーに見せる用の履歴テーブル」を別に持つ案は採りませんでした。

別に持つと、同じ出来事を 2 か所へ書くことになります。片方だけ書けた瞬間に食い違いが生まれ、しかもそれはあとから直せません(どちらが正しいかを判定する材料が無い)。書き込みの回数も増えます。読む側の都合で書く側を増やすのは、割に合わない取引でした。

そこで書き込み経路を 1 本に固定し、読む側を 3 つに分けました。分けるのは読み取り時なので、食い違いが起きる余地がありません。読み口ごとに「出す項目」と「見せる相手」が違うだけで、元にしている行は同一です。

業務データと同じ区切りで書く

記録は業務処理の中で、業務データと同じトランザクションで書きます。ファイルの行を作る処理と、その記録を残す処理が同じ区切りに入っているので、「ファイルはできているのに記録が無い」という状態が構造的に生じません。逆に記録に失敗すれば業務処理ごと巻き戻ります。

書き込みの負荷: 記録のテーブルは月ごとに子テーブルへ分かれており、書き込みが当たるのは最新の月だけです。過去 2 年分が同じ場所に積み上がっても、追記の速度は変わりません。
03

追記だけを許すAppend-Only

監査の記録は「都合の悪い行を後から消せない」ことに価値があります。松葉ではこれをアプリの実装で守っていません。データベースの権限設計で守っています。

データベースには行ごとのアクセス制御があり、「誰がどの行に対して参照・追加・更新・削除をしてよいか」を規則として書きます。記録のテーブルについては、更新と削除の規則をそもそも書いていません。書いていない操作は既定で拒否されるため、システム管理者のアカウントであっても更新も削除もできません。「権限を絞る」のではなく「規則が存在しない」形にしてあるので、権限の付け替えで緩むこともありません。

三重の防御

消せないことは、次の 3 つが重なって成り立っています。1 つ目は改変の防止、2 つ目は事故からの復旧、3 つ目は保持期間を超えたあとの保全です。

Three Layers of Integrity
層内容守る相手
① 追記だけの規則更新と削除の許可規則を作らない。参照はシステム管理者のみ。Box のメンバーは専用の関数経由だけ意図的な改変・アプリの不具合による書き換え
② 自動バックアップデータベース側の自動バックアップと、一定期間の任意時点への巻き戻しテーブルごと失う規模の事故
③ 外部への退避25 ヶ月を超えた月を圧縮した CSV としてオブジェクトストレージへ移し、行数と検査値を突き合わせて保全する保持期間を超えた分の消失

「参照できない」ことを書き込みの形の強制に使う

記録の追加は全員に許していますが、参照はシステム管理者だけです。この組み合わせには副作用があります。データベースには「行を追加して、追加した行をそのまま返す」書き方がありますが、返す動作は参照に当たるため、一般の利用者がその書き方をすると失敗します。そこで追加は「返さない一括追加」の形だけを使う規律にしています。規律を破ると本番で必ず落ちるので、レビューの見落としがあっても混入しません。

04

同じ記録を 3 つの読み口で出すThree Readers

読み口ごとに「出す量」を変えています。量を変える基準は、その読み口が何のためにあるか、そして誰が同席するかです。

Why Each Reader Shows a Different Amount
読み口目的出す量とその理由代わりに置いた歯止め
全社の監査ログ不正・事故の調査。退職者の持ち出しの確認生のまま全部。接続元の IP や端末情報を伏せると調査の役に立たないため、伏せないシステム管理者に限定。取り出しは理由を 10〜256 文字で必須、1 時間 5 回まで。閲覧そのものを毎回記録する
Box のアクティビティ業務の履歴を関係者で共有する業務の状態を変えた操作だけ。IP・端末情報・内部の識別子は業務判断に使わないので出さない。1 つの Box には社内と取引先が同席するため、社外の担当者名やメールも伏せ字にする権限の判定と項目の絞り込みをデータベース側の関数に閉じ込め、アプリからは迂回できない形にする
退避分の取り出し2 年より前を掘る例外的な調査その月のまるごと(圧縮した CSV)15 分だけ有効な URL を都度発行。理由を必須にし、発行そのものを記録する

とくに 2 番目には気を配りました。Box は「案件や取引先ごとの共有単位」で、1 つの Box に社内の担当者と取引先の担当者が同時に入ります。ここで内部情報が出ると、取引先 A が取引先 B の担当者名やアドレスを知り得ます。この読み口では、出してよい項目を「対応表」として列挙し、表に無い項目は通り道そのものが無い形にしています(詳細はアクティビティの章で扱います)。

05

クラウドの監視と別に、自前の検知を持つ理由Two Kinds of Monitoring

クラウド側の監視は稼働しています。それでもアプリ自身の検知を別に持っているのは、両者が見ているものが違うからです。

What Each Kind of Monitoring Can See
監視見えるもの見えないもの
クラウド側の監視サービスの応答が返らない。エラー率が上がった。バッチが異常終了した。キューに未配信が溜まった「サービスは正常に応答しているのに、業務が前に進んでいない」状態
アプリ自身の検知送信待ちの通知が 30 分以上そのまま。日次の同期が 2 日連続で失敗。メールの不達率が一定の割合を超えた。削除の当日通知が作られていない。ウイルス検査が一定の件数以上滞留しているインフラそのものの故障(そちらはクラウド側が拾う)

右の列の事象は、どれもアプリのテーブルを数えないと判定できません。「送信待ちのまま 30 分」は通知の宛先の状態を見る話で、サービスの応答時間には現れません。だからアプリ側に検知を置いています。

実際に起きた例

本番では、ファイルの検査完了を伝えるイベントが、発行の権限が足りないために送れず、再試行の上限に達して溜まりました。画面にはエラーが出ず、通知メールだけが出ないという形の故障です。クラウド側から見ればサービスは正常に応答しており、異常な指標も出ていません。

これは 5 分ごとに回る検知器が拾いました。アラートには内訳(どの種類のイベントが何件、いちばん古いものはいつ発生したか、直近のエラーは何か)が残り、システム管理者が画面から確認できる状態になりました。

この故障が示していること: 「画面にエラーが出ない」故障は、気づく仕組みを別に用意しないと誰にも届きません。とくにメールの配送は、送られなかったこと自体が誰の目にも触れないため、待っているだけでは永久に発覚しません。
06

設計の四原則Design Principles

以降の章で出てくる個々の判断は、すべて次の 4 つのどれかに紐づいています。