サーバーの中に、電車を走らせた

うちの小さな fediverse サーバ sukhi に、/map という公開ページを作りました。サーバの中身を駅と線路の路線図にして、その上を小さな電車が走っています。本数は飾りではなくて、この 1 日にほんとうに流れた量です。

作るのが思いのほか楽しかったので、その記録を書きます。

きっかけ — ダッシュボードではなく、景色

きっかけは、karutte-wt — WebTransport の裏口 — を sukhi につないでいる最中の、nyanrus の一言でした。「sukhi と karutte を駅とみなして、NATS の内部経路・外部経路を線路として例えたら、眺めていても面白いし、外の人にとっても構造を理解しやすいと思う。ページ作らない?」

たのしそう、とすぐ返事をしました。サーバの「いま」を見せる道具は、ふつうはステータスページか、グラフの並んだダッシュボードです。でも sukhi は友達と使う小さな星で、見に来るのは運用者ではありません。数字の羅列ではなく、眺めて「ああ、動いてるんだなあ」とわかる景色。そして言われてみると、sukhi の構造 — gateway から NATS 操車場を通って delivery へ、Cloudflare の表口、karutte 経由の WT 裏口 — は、駅と線路にけっこう素直に対応するのです。

返事のかわりに、まず本物の線路配置を確かめに行きました。そして、ルールを一つだけ決めました。絵はぜんぶ、実配線と一対一に対応させること。かわいいからという理由で、存在しない線路は引かない。逆に、ほんとうに在る経路は、ちゃんと線にする。比喩は自由だけれど、つながり方は本物 — という縛りです。この縛りがあると、絵を直すたびに自分のサーバの配線を見直すことになって、それが一番の収穫だった気がします。

駅と路線

おもて口線(緑)は、あなたのブラウザから sukhi までの HTTPS です。あなた → Cloudflare 宇宙港 → Anubis 検問所 → gateway。Anubis は AI クローラー避けの門番で、駅ではないので丸にせず、線をまたぐ関所のバーで描きました。

その上に、細い灰色の線がもう一本。帰り専用の軽電鉄(SSE)です。行きのリクエストとは別に、サーバから押し流す長生きのストリームが一本ある — 実配線がそうなので、地図でも別の線にしました。場内放送(新しい投稿のお知らせ)は、この線を走ります。

連合線(茶)は ActivityPub です。出る便は delivery から貨車で島のきわの貨物発着場へ、そこから小さなロケットが点線の航路に乗って、連合宇宙へ渡ります。じつは最初、「delivery から Cloudflare へ急行」と描きかけました。でも外向きの配達は、うちの Cloudflare を通りません。直接、外へ出ていく。だから急行の終点は宇宙港ではなく、自前の埠頭です。ここが「つながり方は本物」ルールの効いたところで、絵を描くことが配線の再確認になりました。

入りの便は逆向きです。よそのサーバからの便は Cloudflare 宇宙港に着きます。ただし旅客の丸ではなく、すぐ下の貨物船ターミナルへ。そこから貨物急行が gateway まで走るのですが、この急行は Anubis の検問所のを抜けます。実配線でも /inbox は Anubis の素通しリストに入っているからで、検問のバーはおもて口線にしか届かない高さで描いてあります。

もう一本、破線の WT 新幹線。karutte(x64 の箱)を経由する WebTransport の直通ルートで、いまは試運転中です。新幹線の車両は実物どおり両頭にして、WebTransport は duplex なので、一本の列車が行ったり来たりします。これだけは実流量ではなく「試運転中」という状態の描写で、開業したら実数につなぎ替えるつもりです。

こまかい遊びとして、進行方向の矢印は線の上に乗せず、始点のわき・進行方向の右側に置きました。この地図は右側通行 — 対向する線が左手に見える、ソウルの地下鉄と同じ配置です。

数字は、ぜんぶ本物

列車の本数は、公開の GET /api/map から来ます。認証なしで、返すのは粗い数だけ。この 1 日に生まれた note の数(ローカル・リモート別)、連合へ届けた便の数、JetStream の滞留。宛先も本文もアカウントも含まれないので、無認証で開けておけます。そのかわり無認証の口が DB への直通にならないよう、結果は 5 秒だけサーバ側で持ち回します。

一度だけ、設計をやりなおしました。最初は「直近 5 分」で数えていたのですが、しずかな星では 5 分窓はほぼいつも 0 で、地図がいつ見ても空っぽになってしまう。1 日基準に直したら、しずかな星にも「この星の一日」が見えるようになりました。窓の広さは、にぎやかさに合わせて選ぶものでした。

数から本数への変換は、桁で増えるようにしました。

const trains = (n: number, max = 4) =>
  n <= 0 ? 0 : Math.min(max, Math.floor(Math.log10(n)) + 1);

1〜9 本の投稿なら列車 1 本、10〜99 なら 2 本。線形だとにぎやかな日に画面が電車だらけになるし、この鈍さなら「桁が変わった」ときだけ景色が変わります。

そして、取れないときは取れないと言う。案内板は「案内板の数字が、いま、取れませんでした。線路の形は、そのままです。」と言って、線路だけは描き続けます。数字が嘘をつかないことと、地図が消えないことは、両立できます。

電車を走らせる — SMIL と、はまった穴

乗り物のアニメーションは、SVG に昔からある SMIL(animateMotion + mpath)だけで動いています。JavaScript のアニメーションループはゼロで、見えている線路の path をそのまま道として参照します。

<animateMotion dur="28s" begin="-14s" repeatCount="indefinite" rotate="auto">
  <mpath href="#p-front" />
</animateMotion>

枯れた仕組みですが、穴には、いくつか、はまりました。

begin は負にする。正のオフセットだと、発車時刻が来るまで列車が SVG の原点(左上)に立ち尽くします。begin="-14s" のように負にして、「もう走っていたこと」にするのが正解でした。

rotate="auto" の機首は、path の向きで決まるkeyPoints="1;0" で線路を逆走させたら、ロケットが後ろ向きに飛びました。逆向きに走らせたい便がある線は、path 自体を逆向きに描きます。

動きを減らす設定では、隠すのではなく、停めて見せる。最初、prefers-reduced-motion のときは乗り物を丸ごと非表示にしていました。そうしたら「ロケットが飛んでない」という報告が来て — ああ、そうか。動きを減らしたい人にも、乗り物には居てほしいのです。いまは keyPoints を同値にピン留めした SMIL で、道すじの途中に静かに停めて見せています。

<animateMotion dur="1s" fill="freeze" calcMode="linear"
  keyPoints="0.6;0.6" keyTimes="0;1">
  <mpath href="#lane-star" />
</animateMotion>

あとから生まれた svg の SMIL は、Chromium で凍ることがある。ページの下半分(宇宙図)は最初のデータが届いてから DOM に入るのですが、その中の SMIL が、時計は進むのに絵が動かない状態で固まることがありました。マウントから一拍おいて svg.setCurrentTime(svg.getCurrentTime()) — 時計をこつんと触ると、気づいて動きだします。

おまけをひとつ。Playwright は stroke だけの path を hidden 扱いするので、E2E テストで線路の存在を確かめるなら、svg そのものを getByRole('img') で見るのが素直でした。

連合の宇宙図

ページの下半分には、もう一枚。sukhi と行き来のあるサーバたちを、星として描いた星図があります。

載せ方はホワイトリストです。管理画面で選んだ星だけが、名前つきで空に載る。連合先のドメイン一覧はそれ自体が関係の情報なので、勝手に全世界へ公開しない — リストの外のサーバは、数字ごと出しません。

星の置き方は、ぜんぶ決定的にしました。位置はドメイン名のハッシュが種で、いつ見ても同じ空。ラベルが重なったら、その星のハッシュ由来の角度から螺旋に外へ逃がします。これも入力だけで決まるので、同じ星ぞろえなら空はいつも同じ形です。星の大きさはこの 1 日に届いた便りの桁で決まって、便りのあった星からは、小さなロケットが sukhi へ飛んできます。

むすび

地図にしてみたら、思わぬ実用もありました。DLQ 側線 — 届けられなかった配達が 30 日待つところ — に赤い貨車が留まっていたら、どこかに便が届いていません。グラフの異常を読みとる代わりに、「側線に貨車がいる」で気づけます。ふだんの案内板は「側線は、からっぽです。よいことです。」と言っています。

サーバの監視ページって、運用者のための道具と決まったものでもないみたいです。実配線と一対一、数字は本物、という縛りさえ守れば、景色にしても嘘にはならない。そして景色なら、友達にも見せられます。

きょうも、小さな電車がことばを運んでいます。よかったら、見にきてください