0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

開発記録:アクセス解析の失敗でアップロードを止めない設計

0
Posted at

HTMLをリンクで共有するPlopinoを開発しています。アクセス解析を実装する際、サイトの画面と、ユーザーがアップロードしたHTMLを同じ方法では測れませんでした。今回は現在の実装と、その割り切りを記録します。

計測する場所を二つに分けた

製品のページにはUmamiのブラウザースクリプトを置きます。一方、配信するユーザーのHTMLにはそのスクリプトを挿入していません。こちらはサーバー側からboard-viewというイベントを送ります。

サーバーから同じ閲覧についてpageviewも送る構成にはしていません。また、レポートでpageviewとboard-viewを足して「ユニーク訪問者」とは呼びません。計測対象も欠損の条件も違うためです。

静的ファイルの配信側では最終的な応答ステータスを見ます。2xxと304は対象になり得ますが、HEAD、ディレクトリのリダイレクト、404などは対象外です。さらに、ページ以外のリソースはイベント用コールバックで除外しています。CSSや画像のリクエスト数を閲覧数にしないための境界です。

サーバーで数えるとリンクプレビューも入る

GETが来たことと、人が画面を読んだことは一致しません。現状はUser-Agentの既知の特徴を見て、クローラー、リンクプレビュー、一部の診断ツール、空のUAを除外します。Globalpingのプローブも明示的に扱っています。

ただし、これは人間判定ではありません。ブラウザーを名乗る自動アクセスは残り得ますし、広すぎるパターンは正当なアクセスを落とします。クライアント側でスクリプトが動いた場合も、人間だと断定できません。数字を読む際は、公開やリンクコピーなどの行動と分けて確認します。

アップロード結果を解析サーバーの状態に依存させない

uploadとboard-viewの呼び出し側は、イベント送信の完了をawaitしません。ただし、voidを付けるだけでPromiseの拒否が処理されるわけではありません。送信関数の中で想定する失敗を処理します。

以下は実装のパターンを抜き出した例です。payloadの組み立て、UAフィルター、ログ出力は省略しています。

async function sendEvent(endpoint, payload, fetchImpl = fetch) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 5000);
  try {
    const response = await fetchImpl(endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
      signal: controller.signal,
    });
    return response.ok;
  } catch {
    return false;
  } finally {
    clearTimeout(timer);
  }
}

製品の実装ではHTTPの非OK応答と例外に対して警告を記録します。タイムアウトの標準値は5秒です。finallyでタイマーを解放するので、正常終了した送信にタイマーを残しません。

代わりに、イベント配送の保証はありません。永続キューはなく、プロセス終了などで失われる可能性があります。課金の根拠や監査記録として使うなら、この設計のままでは不十分です。

ネットワークを使わずに確かめたこと

送信関数にはfetchを注入できるようにしています。既存のモジュールテスト9件を再実行し、すべて成功しました。実際の解析サーバーへは送信していません。

観点 確認する内容
UA判定 ブラウザー、既知のボット、空の値
無設定 送信を行わない
リクエスト payload、ヘッダー、追加データ、URL末尾の正規化
失敗 非OK応答、ネットワーク例外を呼び出し側へ投げない
タイムアウト 中断を扱いfalseで終了する
サイズ URLやタイトルを上限で切る

タイムアウトのテストには短い待ち時間を注入しています。このテストで分かるのはコードの振る舞いであり、本番の配送率やUA判定の精度ではありません。

今の判断

Plopinoでは、まず本来のアップロードと閲覧を成立させ、解析は失敗し得る補助情報として扱っています。uploadイベントの件数を新規利用者数に読み替えたり、同じ操作に対応するクライアントとサーバーのイベントを足したりしないことも、実装と同じくらい大事でした。

小さなサービスでも「何を数えるか」「失敗時に何を止めるか」を先に決めておくと、グラフの意味を説明しやすくなります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?