Statistics made simple

Nikita Prokopov

統計をシンプルに

原文は Nikita Prokopov により に公開されました。 このブログを購読する

統計との付き合い方はちょっと複雑だ。一方では、あまり頻繁に見ないようにしている。年に一度か二度くらいだ。なぜならアナリティクスはアクションにつながらないからだ。記事を千人に読まれようが一万人に読まれようが、何が違うというのだろう。

もちろん、読者の好みを推測して人気が出そうなことだけを書くという手もある。でも、そんなことをすればすぐに魂がすり減ってしまう。

一方で、何かが集計されず、記録されず、後で参照できるように保存されていないと不安になる。今は必要なくても、10年後に気が変わったらどうするんだと思ってしまうのだ。

読者がいるのを見るのは、自分が虚空に向かって書いているわけではないとわかるためにも役立つ。だから本当に必要なものは多くない。ごく基本的なものでいい。1日あたり/記事あたりの読者数、それくらいで十分だ。

最後のピースはこれだ。僕はウェブプロジェクトをセルフホストしていて、その処理をNginxに任せるのではなく、昔ながらのウェブサーバーを使っている。

静的サイトが人気なのには理由がある。速くて軽量で、役割をしっかり果たしてくれるからだ。一方の僕は、どこかでゲシュタルトが閉じていないのかもしれない。ウェブページを配信するときにコンピューターのフルパワーを感じたいし、静的ページの枠を超えた面白いことをやりたい。汎用プログラミング言語を自由に使える、あの自由が必要なのだ。自分でウェブサーバーをプログラムしたいのだ(Clojureで。みんなごめん)。

既存の選択肢

こうして僕は、自分のニーズにぴったり合う統計ソリューションを探す旅に出た。Google Analyticsは論外だった。肥大化しているし、プライバシーフレンドリーでもないし、UXはひどいし、Googleは邪悪だし、等々。

一体何が起きているんだ?

他のJSベースのソリューションも考えられなくはなかったが、やはり疑問は残る。SaaS?有料?10年後も存在しているだろうか?セルフホスト?CookieはGDPRに準拠しているのか?RSSフィードはどうカウントする?

Nginxにはアクセスログがあるので、それを元にするサーバーサイドの統計ツール(具体的にはGoatCounter)を試してみた。セットアップは簡単だったが、ドメインを用意したり、アカウントを管理したり、プロセスを監視したりする必要が出てきた。しかも僕のサーバー/リクエスト量では十分にパフォーマンスが出なかった!

自作のソリューション

結局、自分で作ることにした。もし制約条件が僕と似ているなら、ぜひ使ってみてほしい。見た目はこんな感じだ。

かなりシンプルだが、僕にとって重要だったいくつかのことをやってくれる。

セットアップ

セットアップは驚くほど簡単だ。これは本気で「機能」だと思っている。

Ringスタックにミドルウェアを一つ追加するだけで、収集からレポートまで自動的にすべてが手に入る。

(def app
  (-> routes
    ...
    (ring.middleware.params/wrap-params)
    (ring.middleware.cookies/wrap-cookies)
    ...
    (clj-simple-stats.core/wrap-stats))) ;; <-- just add this

最良の意味でゼロセットアップだ。設定することも、監視することもなく、依存関係も最小限。すぐ動き始めて、君に何も求めない。永遠に。

ほら、すでにウェブサーバーを持っているのだから、そのために用意したセットアップをそのまま再利用すればいいじゃないか。

リクエストの種別

リクエストの種別を区別している。僕の場合、興味があるのは生身の人間だけなので、RSSフィードへのリクエスト、faviconへのリクエスト、リダイレクト、間違ったURL、そしてbotとは分けてカウントしている。botは近頃とりわけ活発だ。どこかからAIの学習データを持ってこないといけないからね。

RSSフィードもある意味では生身の人間なので、正しくカウントするためにひと工夫している。同じ読者が1日にfeed.xmlを100回リクエストしても、1回としてカウントされる。

ホスト型のRSSリーダーは、User-Agentで購読者数を報告してくれることが多い。例えばこんな感じだ。

Feedly/1.0 (+http://www.feedly.com/fetcher.html; 457 subscribers; like FeedFetcher-Google)

Mozilla/5.0 (compatible; BazQux/2.4; +https://bazqux.com/fetcher; 6 subscribers)

Feedbin feed-id:1373711 - 142 subscribers

このリストにいるみなさんに、個人的なリスペクトと感謝を。ちゃんと見えているよ。

グラフ

可視化は重要だし、正しいグラフの種類を選ぶことも重要だ。これは間違っている。

連続した線は補間を示唆してしまう。午前5時の1訪問と午前6時の11訪問の間に、2、3、5、9訪問といった中間点があったかのように読めてしまう。5.5訪問なんてこともあり得るように見える。でも実際はそうではない。

意味的に正しいバージョンのグラフはこうなるべきだ。

軸のラベルが適切なものになるようにも気を配った。117、234、10875のような数字は表示されない。スケールに応じて、100、200、500、1Kといったキリのいい数字を選ぶようにしている。

すべてのグラフが同じ縦軸スケールを持ち、横スクロールが同期しているのは言うまでもない。

インサイト

あまり多くの機能は提供していない(僕自身あまり必要としていないので)が、ページ、クエリ、リファラー、ユーザーエージェント、そして任意の日付範囲でレポートを絞り込むことができる。

未実装(今のところ)

「このスパイクは何が原因だったのか?」についてのインサイトがあるといいなと思っている。

国別の簡単な内訳もあると嬉しい。IPアドレス自体は持っているのだが(それがどれだけ役に立つかは別として)、GeoIPをリーズナブルなサイズ(できれば1Mb未満、多少解像度が落ちても構わない)にパッケージする方法が必要だ。

最後に、僕が本当に興味があるのは「誰が僕について書いてくれたのか?」ということだ。リファラーは持っているので、あとはノイズからシグナルをどう分離するかという問題だけだ。

パフォーマンスについて。DuckDBは優秀だ。データを圧縮し、カラム単位でクエリを実行するので、行あたりのカラムを増やしてもクエリのパフォーマンスには影響しない。それでもダッシュボードへのアクセスごとにデータベース全体を横断するクエリが走る。今のところ(約3年分のデータで)サイズは600 MiBほどだ。事前計算した集計を作ることを真剣に検討する必要があるだろう。

いつか。

入手方法

github.com/tonsky/clj-simple-statsにアクセスして、手順に従ってほしい。

感想を聞かせてほしい。使えそうだろうか?改善すべき点は何だろう?

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント