Write and Run

it's a simple way, but the only way.

リモートマシンから手元に向かってコマンドを実行できるやつを作った

KOBA789 です。寒い日が続きますね。こうもあまりに寒いとアイスを食べたくなるものです。昨日の私はその衝動に抗えず、コンビニでソフトクリーム(チョコ味とのミックス)を買ってきて食べました。余計に寒くなったのでもう二度とやりません。今はおでんが食べたいです。よろしくお願いします。

リモートから手元に向かってコマンドを実行したい

さて、寒いとアイスが食べたくなるように、リモートマシンに SSH でログインしていると手元でコマンドを実行したくなるものです。せっかくリモート接続してるのにね。人って不思議です。

たとえば、SSH 先の Linux マシンで code って打ったら手元の MacBook AirVS Code が起動してほしいわけです。VS Code の Integrated Terminal 内ならできますけど、そもそも VS Code のウィンドウが1枚も開いていないときには使えない技です。

Alacritty で SSH して、cd でプロジェクトのディレクトリまで潜って、さぁやるぞと思って code . と叩くと zsh: command not found: code の響きあり。がーんだな……出鼻をくじかれた。ただでさえ寒くてお布団から出たくない*1のにこれでは仕事する気になるわけがありません。エンジニアリングでなんとかしましょう。MacBook AirLinux サーバーを併用するなんていうトリッキーな開発環境でなければ困らない問題ですが、困っているのでなんとかするしかありません。

さらにいうと、KOBA789 は AWS の認証情報管理に aws-vault というツールを使っています。これはアクセスキーやセッショントークンを OS のセキュアなキーチェーンに保存してくれて Credential Provider の一種である Process credentials として振る舞えるというスグレモノです。要はキーチェーンに保存しておいたクレデンシャルを AWS CLIAWS SDK から透過的に使えるというわけです。

さて話が脱線しているようにも見えますがそうでもないのでお付き合いください。この aws-vault で使うキーチェーンが問題です。macOS であれば標準の Keychain Access.app 一択ですが、Linux ではいろいろな選択肢があり困ります。しかもどれもデスクトップ環境のない(= headless な)環境だと使いづらいです。ここはせっかくなので SSH 先の Linux マシンでも macOS のキーチェーンを使いたいですよね。あれ、これってもしかして VS Code の問題と同じ手法で解決できるのでは?

SSH Agent Protocol

そうと決まれば次はどうやって実現するかが問題です。SSH と関係ないサイドチャネルの通信でどうにかするのはセキュリティの観点からナシでしょう。せっかく Secure SHell のセッションがあるのですからこれを使うべきです。

では逆向きに SSH セッションを張るというのはどうでしょうか。リモートの Linuxssh temoto-machine するというイメージです。悪くはないように見えますが、リモートマシンから手元のマシンへの接続性が常にあるとは限りません。頻繁に持ち歩き、時には信用できないネットワークにも接続するかもしれない MacBook AirSSH のポートを開けておくというのはナンセンスでしょう。これもボツです。

というわけで見出しに書いた手法 SSH Agent Protocol目的外利用します。

SSH Agent Protocol とはその名の通り ssh-agent で使われているプロトコルで、ForwardAgent yes すると SSH 先のリモートマシンでも手元のマシンにしかない秘密鍵が使えるようになったりするアレです。元々そういう目的のプロトコルなので、伝送路はセキュアであると考えていいでしょう。

肝心のプロトコルの仕様については以下の IETF のページにあります。微妙に曖昧というか細かいところの詰めが甘いような気もしますが、そこは実地でパケットキャプチャしたりしてなんとかすればよいのです。

draft-miller-ssh-agent-04

プロトコルの大枠だけざっくり解説しておくと、フレームの長さに続いてフレームのボディーが流れてくるシンプルなフレーミングを用いて、1リクエスト=1フレームに対して1レスポンス=1フレームを返すというだけのシンプルな仕様です。レスポンスはリクエストと同じ順で返さねばなりません。

この SSH Agent Protocol には拡張機能のための仕様があり、message type = SSH_AGENTC_EXTENSION(27) としたメッセージの中身に拡張機能の識別子と任意のデータを詰めて送っていいことになっています。任意のデータを送れるので、つまりなんでもアリです。ここにコマンド名や引数を付けて送信し、stdout や stderr を返してもらえば目的は達成できそうです。

というわけで実装

論理できた*2のであとは yaru-dake です。日曜日を潰して実装しました。

あ、この記事は Rust Advent Calendar 2022 2枚目 day5 の記事です。なので Rust で実装しました。本当は発表の場がなくて困っていたところに Advent Calendar の空き枠を見つけただけですが。

github.com

まず agent(手元)側で次のように起動して SSH します。

$ echo $SSH_AUTH_SOCK
/home/user/.ssh/agent
$ ssh-rev agent --ssh-rev-sock /path/to/rev-sock
$ export SSH_AUTH_SOCK=/path/to/rev-sock
$ ssh remote-host

そして client(リモート)側で次のようにしてコマンドを実行します。

$ ssh-rev exec -- hostname
temoto-machine

きっと手元のマシンで実行した結果が得られるはずです。得られなかったらバグってます。残念でしたね。

Agent から Client へは任意のタイミングで送信できない問題

しくみとしては前述のとおりです。が、実は意外とテクい部分があったのでご紹介します。

SSH Agent Protocol は Client 主導のリクエスト・レスポンスモデルです。つまり、Agent の好きなタイミングで Client にメッセージを Push することは許されません。HTTP と同じセマンティクスだと思ってもらえれば納得しやすいでしょう。

しかしその通信モデルでは stdout や stderr の配送で困ります。それらは任意のタイミングでバイト列が発生しますが、これを Client に伝える術がありません。まぁぶっちゃけコネクションは張りっぱなしなのでプロトコルを無視すれば送れてしまうんですが、とりあえず足掻いてみましょう。

Client から Agent に送れるリクエストの種類のひとつとして、WATCH というものを定義しました。これを送信すると、Agent は

  1. 子プロセスの stdout から出力が発生
  2. 子プロセスの stderr から出力が発生
  3. 子プロセスが死んだ(exited)

のいずれかのイベントが起きるまで、レスポンスを返さずに黙り込みます。昔のウェブで使われた Comet と呼ばれるテクニックと同じですね*3

一見うまく動作しそうなこの設計にもまだ問題があります。Client は任意のタイミングで stdin のバイト列を書き込みたいのです。先述のとおり、SSH Agent Protocol ではレスポンスの順番を入れ替えてはいけません。WATCH のレスポンスが返ってくるまでは stdin の書き込みが成功したかどうかわかりません。

そこで、WATCH は stdin の書き込みリクエストでキャンセルできることにしました。WATCH のレスポンス待ち中に stdin の書き込みリクエストを投げると、先行する WATCH に対応するレスポンスは Cancelled が返るようにしました。

さあこれで解決、となればよかったのですが、これでもまだまだ問題があります。次のようなケースを考えます。

$ dd if=/dev/zero of=/dev/stdout bs=1M count=100 | ssh-rev exec -- cat > /dev/null

大量のデータを手元に送りつけ、cat で折り返してリモートに送り返すというシナリオです。一見うまく動きそうに見えますが実は途中で詰まります。さぁなぜでしょうか。この原因究明は読者の課題とします。

……とするとブーイングが飛んできそうなのでちゃんと解説すると、このような stdin への書き込みが頻発するようなシナリオでは WATCH リクエスト、つまり stdout の読み出しリクエストがすぐにキャンセルされ、stdin の書き込みが圧倒的に有利になります。その結果、stdout を読み出すチャンスがないまま cat のバッファが埋まり、やがて stdin への書き込みが永久に完了しなくなるのです。わかりましたか、読者?

ちなみにこれをうまく解決するプロトコルは実装しておらず、未解決です。もし使う人がいたら気をつけてください。

まとめ

SSH Agent Protocol の仕様に違反した実装しても誰も困らんのではないか……

おまけ

SSH 先から手元の VS Code を起動する設定

以下のようなシェルスクリプトreverse-code命名して PATH の通ったところに置きます。REMOTE_HOSTSSH 先のホスト名に置き換えてください。

#!/usr/bin/env bash

ssh-rev exec -- code --remote ssh-remote+REMOTE_HOST "$(readlink -f $1)"

そして、.zshrc に以下のようなコード片を入れています。Integrated Terminal 内でだけ使える code コマンドを殺さないようにしているわけです。

if ! command -v code &> /dev/null; then
  alias code=reverse-code
fi

SSH 先から手元の aws-vault を使う設定

~/.aws/config にこんな感じで書けばいいと思います。PROFILE_NAME, TEMOTO_PROFILE_NAME は置き換えてください。

[profile PROFILE_NAME]
credential_process = ssh-rev exec -- aws-vault exec --no-session --json TEMOTO_PROFILE_NAME

*1:KOBA789 の寝室にはエアコンがありません

*2:机上では成立することが確認できたこと

*3:†リアルタイムウェブ†

ISUCON12予選にRubyで出場して8位で予選通過した(ソレイユ)

霧矢あおい(KOBA789)です。

タイトルのとおり、ISUCON12予選に Ruby で出場して8位で予選通過しました。最終スコアは34635(ベストと等しい)です。

本戦出場は ISUCON9 ぶり2回目です。

チーム「ソレイユ」

今年は私の多忙もあってチーム解散の危機だったんですが(チームメイトにはご迷惑をおかけしました)、無事いつものチームで出場することができました。

霧矢あおい「無事、チーム『 ソレイユ』の一員になれました。ありがとうございました!」
ひとりチームからソレイユへの移籍に際して運営さんにお手伝いいただき、無事移籍を成功させたときの様子。

というわけでメンバーは以下:

アイカツ劇場版公開おめでとうございます。忙しくてまだ観られていません。

たたかいのきろく

うちのチームは例によって Git をほぼ使っていないので正確な記録はないのですが、今回は私が ToDo リストを書きながらマネージャーっぽいことをしていたのでそれをベースに書きます。

  • 10:00 競技開始
    • KOBA789 & s4ichi: レギュレーション読み合わせ開始
    • osyoyu: 競技用サーバーに入って開発環境構築開始
  • 10:20 頃
    • KOBA789 & s4ichi: とりあえずブラウザでアプリケーションを触ってメンタルモデルを構築
  • 10:30 頃
    • KOBA789 & s4ichi: とりあえずコードを上から下まで流し読み。雰囲気を掴む
    • KOBA789: 「SQLite3 がいつもの戦い方を阻害するので MySQL への移行を視野に入れた方がいい」
  • 10:50
    • osyoyu: 開発環境構築完了
    • all: KOBA789 から osyoyu へ、課題のアプリケーションのメンタルモデルを共有
    • osyoyu: 「いつものツール使うのに Docker 邪魔なので降ろした方がいい」
    • osyoyu & s4ichi: Docker 降ろし開始
    • KOBA789: DB へのアクセスパターンの洗い出しを開始
  • 12:00
    • KOBA789: DB へのアクセスパターンの洗い出しを完了。MySQL への移行が現実的な規模であることを確信
    • osyoyu & s4ichi: Docker 降ろし完了。いつものツールで開発・分析が可能に
    • KOBA789: 「移行を見据え、すべての SQL に名前をつけてメソッドに切り出したい。移行しなくても書き換えに役立つ」
    • all: 手分けして SQL 呼び出しのメソッド切り出しを開始
  • 12:30
    • all: メソッド切り出しが完了
    • s4ichi: 切り出したメソッドを使うように変更を開始
    • KOBA789: ranking のアルゴリズムの読解を開始(味集中カウンターへ)
    • osyoyu: visit_history の Redis 化を開始
  • 12:45
    • KOBA789: ranking アルゴリズムの読解が完了。player_score はプレイヤーひとりあたり最新の1行だけを保持すればいいことに気づく
    • KOBA789: player_score のデータ構造の最適化と MySQL への移行を同時に開始(player_last_score テーブルを MySQL に作る)
  • 13:19
    • KOBA789: player_last_score 対応のアプリのコード書き換えが完了
    • KOBA789: player_last_score の初期データ作成スクリプトの開発開始
  • 13:50
    • KOBA789: player_last_score の初期データが完成
    • osyoyu: visit_history の Redis 化がバグり散らしたので断念
    • osyoyu & s4ichi: player & competition の MySQL 移行を開始
    • KOBA789: MySQL 移行完了後の一手を考えるため、プロファイリングとタスク整理の旅に出る
    • なおこのへんまでスコア変化なし
  • 15:40
    • osyoyu & s4ichi: MySQL 移行完了
    • s4ichi: 移行完了を確かめるため、SQLite 周りのメソッドを noop に書き換え開始
    • osyoyu: いつもの分析ツールが使えるようになったため、MySQL にインデックスを貼ってスロークエリを潰し始める
    • KOBA789: player_score CSV の入稿で1件も保存されずに不整合でペナルティを食らう原因を調査開始
  • 16:18
    • KOBA789: ペナルティの原因は DELETE & INSERT によるデッドロックトランザクションが ROLLBACK してるからと判明。バルクインサートにして解決を試みる
    • s4ichi: 地道に N+1 を潰し始める
    • osyoyu: 完全に MySQL に移行できたため、満を持して複数台構成へ
    • このへんでスコアが一気に跳ねて25000くらいに
  • 17:00 頃
    • s4ichi: 「player の INSERT で dispense_id が遅い」
    • KOBA789: dispense_id を Redis INCR 化
    • osyoyu & KOBA789: visit_history の効率化に再挑戦開始
  • 17:40 頃
    • osyoyu & KOBA789: visit_history がまたもやバグり散らしたため断念
    • s4ichi: デバッグログを無効化
    • デバッグログ切っただけで1割くらいスコア伸びた
  • 17:50 頃
    • s4ichi: YJIT 有効化
    • YJIT は意味なかった(MySQL がパツパツで Ruby が遊んでいたため)
  • 18:00 競技終了
    • 17:00 から本質的な改善はなく、デバッグログを切ったことによるスコア向上だけでフィニッシュ

リモートソレイユと味集中カウンター

うちのチームはいつも1つの物理空間に3人で集まって戦うスタイルだったのですが、今年は初のリモートソレイユでした。

物理空間の大きな特徴として、ブロードキャストが容易であるという点があります。たとえば同じ空間にいる場合、その場で声を出せばなんの工夫がなくても他の2人に伝わるでしょう。

一方で、互いが遠隔地にいる状況ではそうではありません。音声はなんらかの通信手段を用いて意識的にブロードキャストしなければ伝わりません。これは物理空間と比較して(直感的には)不便な点です。

しかし私はこの特性を逆手に取り、従来あったコミュニケーションの課題を解決することにしました。

物理空間では「ブロードキャストが容易である」と述べましたが、裏を返すとそれは、受け取る情報のフィルタリングが難しいということでもあります。

私達のチームでは、しばしばペアプログラミングが発生します。難しい変更でバグが出たときなど、ひとりではパニックや視野狭窄に陥ってしまいがちなシチュエーションでは、ペアプログラミングはひとりよりも冷静に問題に対処できるため、2倍の人的リソースを投入してもなお結果として効率的なことがあるためです。

ペアプログラミングをすると必然的に声によるコミュニケーションの量が増えます。そしてこの声は、物理空間では必ずブロードキャストになります。ペアでないもう一人にも声は到達します。他人が悩んでいる声というのはとても気になるものです。特に私は気になってしまうタイプです。

たいていは2人だけで解決できますし、たとえ3人の知恵を同時に投入した方が解決が多少早かったとしても、3倍の人的リソースを投入するのが合理的な課題というのはそう多くありません。多くの場合、ただの野次馬根性で首を突っ込み、貴重な時間を浪費するだけになります。それくらいなら、チームメイトを信頼して背中を任せ、もうひとりは別の問題の解決に向けて深い思考をすべきでしょう。

やや遠回りな説明になってしまいましたがまとめましょう。従来あったコミュニケーションの課題とは、物理空間ではどうしても声が気になってしまい(主に私)、ひとつの問題に余計に多くの人数で取り組むことになって効率が悪くなることがある、ということです。

今回、私達のチームではコミュニケーションツールとして Discord を選びました。そして、上述の問題を解決するため、ひとり専用のボイスチャンネル「味集中カウンター」を用意しました。

チームのDiscordサーバーには、ひとりしか入れないボイスチャンネル「味集中カウンター」がある

他のチャンネル名が意味不明なのは見なかったことにしてください。語ると長い経緯はありますが、深い意味はありません。

ひとりで集中したいとはいえ、すぐに声をかけられないのでは不便です。時間の限られた ISUCON という競技では、メンション送ったけど反応がないというようなことがあると貴重な時間を無駄にします。

さて、Discord には他のツールにない機能として、あるボイスチャンネルにいるユーザーを強制的に別のボイスチャンネルに移動させるという機能があります。味集中カウンターはこの機能を使ったアイデアです。

まず、集中したい人は味集中カウンターに入ります。そして黙々と作業をします。もし、他のメンバーが一蘭を食ってる人に用があるときは、先述のボイスチャンネル間の強制移動機能で「再起動試験(あっさり)」チャンネルに呼び戻します。するとすぐに声をかけて相談等ができます。相談が済めば、麺が伸びないうちにまたカウンターに戻ります。

こうして、「集中したいときは他人の声をシャットアウトできるが、用があるときはすぐに声をかけられる」という仕組みが完成しました。

本戦に向けて

まずは Ruby を本戦に連れて行けて嬉しいです。やはり本戦に出るからには目指すは優勝です。Ruby にもう一度てっぺんの景色を見せてあげられるまで諦められません。

おまけ

感想戦配信のアーカイブがあるのでよかったら観てってください。はじめて凸待ち配信をしたのですが、たくさんの方とお話しできて楽しかったです。

youtu.be

パスタの茹で方

この春から一人暮らしを始めたみなさんへ。

とりあえず「DJ WILDPARTY パスタ」で検索して欲しい。

検索しても見つからなかった人は下記のリンクを開いて欲しい。

note.com

以上にすべて書いてあるのでここで特段解説すべきことはない。

特段解説すべきことはないので、つまり以下は完全に蛇足なのだが、暇な人は読んでもいい。

改めて、この春から一人暮らしを始めたみなさんへ。まず火には気をつけた方がいい。火を付けている間は目を離さないほうがいい。デスクに戻って Twitter をしている間にも火は広がり、人生が終わりかけているかもしれない。

ところでパスタのゆで時間は10分弱くらいある。10分もの間キッチンに立って火を見張っているのは退屈だし、家でパスタを食べようってときはたいていそんなに元気がないときだ(私の場合)。

そこでこのテクニックだ。火をつけておかなければならないのは最初の1分だけだ。つまりそのあとは見張りは不要で、Twitter をしようがデイリーを回そうが自由だということだ。ただし油断して放置するとのびきったパスタを食べるはめになる。タイマーは忘れずに。

ちなみにオススメのパスタソースはキユーピーのこのシリーズです。ソースを温めなくていいし、1袋が1人前サイズで使いやすい。この世には1袋が2人前のパスタソースが多すぎる。

www.kewpie.co.jp

ではよき一人暮らしを。

GitHub Projects (beta)のデータをSQLでクエリ・更新できるツールを作った

これは KOBA789 日記 Advent Calendar 2021 - Adventar 21日目の記事です。

GitHub Projects (beta)

みなさん GitHub Projects (beta) は使っていますか? 私はめっちゃ使っています。

しかし beta ということもあってまだまだ使いづらかったり機能が足らなかったりすることがありますよね?

マウスでポチポチしながら、SQL で操作できたらラクなのになぁと妄想したりもします。というわけで作りました。

ghsql

GitHub Projects (beta) のデータを SQL でクエリ・更新できるツールを作りました。SELECT はもちろん、UPDATE や DELETE もできます(INSERT は未実装)。

github.com

インストール

まだ crates.io とかには公開してないので、git clone して cargo install --path . してください。

使い方

Personal Access Token や Installation Access Token などを環境変数 GITHUB_TOKEN に設定してください。

コマンドの引数は以下の通りです。

USAGE:
    ghsql <OWNER> <PROJECT_NUMBER> --github-token <github-token>

SQL の例

テーブル名は items 固定です。

とりあえず全部列挙:

SELECT * FROM items;

もちろん件数も数えられる:

SELECT count(*) FROM items;

StatusIn Progress なやつだけ列挙:

SELECT * FROM items WHERE Status = 'In Progress';

リポジトリと Issue 番号を指定して StatusDone に更新:

UPDATE items SET Status = 'Done' WHERE Repository = 'owner/repo' AND Issue = 123;

StatusDone なアイテムを全部削除:

DELETE FROM items WHERE Status = 'Done';

もちろん、Iteration にも対応しています。

イテレーション(Iteration 10)の積み残しを全部次のイテレーション(Iteration 11)に持ち越し:

UPDATE items SET Iteration = 'Iteration 11' WHERE Iteration = 'Iteration 10';

その他

思いついた勢いで一気に作ったので、気合いでごり押した設計と実装になっています。

終わりに

もうアドベントカレンダーも終盤じゃん。そろそろ書かなくていいかな。

穴を開ける、柱を立てる

これは KOBA789 日記 Advent Calendar 2021 - Adventar 18日目の記事です。

日記

今日は大学の後輩の部屋にツーバイフォー材を立てるのを手伝いました。DIY 大好きマンにはお馴染みのアレです。

ホームセンターに木材を買いに行き、軽トラを借りて持って帰ってくるという、いつものやつをやりました。

プロジェクターを設置したいということだったので、私が引っ越す前に使っていたプロジェクターとその固定金具一式を押しつけました。10年前に買った物体だし、今の部屋にはデカい有機 EL ディスプレイがあるしで、邪魔だったんですよね。

プロジェクターを適当な木ねじだけで吊るすと強度にやや不安があるので、柱に貫通穴を空けてボルトを通しました。引越し前の私が使っていた、実績のある構造です。

今持ってる電動ドリルはちょっと非力なので、マキタのやつとか欲しいですね。まぁ年に数回しか使わないんですけど。ドリルを買いにきた客が本当に欲しいのは穴ではなくマキタ。

終わりに

明日も書く。 久しぶりに暇な日曜日なので新しい DBMS でも作ろうかな。

SWDやCMSIS-DAPとにらめっこした

これは KOBA789 日記 Advent Calendar 2021 - Adventar 17日目の記事です。12月って16日がないらしいです。

日記

SWD や CMSIS-DAP とにらめっこしていました。SWD や CMSIS-DAP というのは、Arm マイコンデバッグをするときに使うやつです。にらめっこの結果として rust-dap のバグを見つけたので PR を出しました。

rust-dap は CMSIS-DAP v2 に対応してるし実装小さくて移植しやすいしで最高です。実際に手元の bluepill に移植して活用しています。

これは私がやっつけ移植した bluepill 対応版(fork): GitHub - KOBA789/rust-dap: CMSIS-DAP Rust implementation

自分が使うソフトウェアが自分の慣れた言語で書かれている安心感は何物にも代えがたいですね。多少バグってても自力で直せるので身軽です。

そうそう、CMSIS-DAP ではどうやってフラッシュへの書き込みをしているんだろうと思って調べたところ、なんとフラッシュ書き込み用の小さなファームウェアを RAM 上に展開してそれを呼び出して書き込ませるという仕組みでした。その小さなファームウェアのことを Flash Programming Algorithm と呼ぶらしいです。またひとつ賢くなりました。

終わりに

(12月に18日があれば)明日も書く。

ひさしぶりに「データ指向アプリケーションデザイン」を読んだ

これは KOBA789 日記 Advent Calendar 2021 - Adventar 14日目の記事です。

日記を書くには早すぎる時間なんだけれど、久しぶりに「データ指向アプリケーションデザイン」を読んだら気持ちが高まりすぎてしまったので、書く。

データ指向アプリケーションデザインと私

「データ指向アプリケーションデザイン」(以下、本書)は "Designing Data-Intensive Applications" の和訳であり、2019年に発行された書籍です。

原著の方は2017年には発行されていたらしいのだけれど、恥ずかしながら当時は知りませんでした。和訳が出たということで話題になっていたのを見て知り、買って読みました。

初めて読んだときの衝撃といったらすごいものでした。学術的なバックグラウンドがほぼ皆無な私が趣味・業務内の試行錯誤のみから思索してぼんやりと仮説を立てていたものについて、その答えが根拠とともに載っていたのです。

私の感覚は間違っていなかったんだという安堵感と同時に、なぜ文献にあたらずにひとりだけで問題に向き合っていたのかと後悔し、また列挙されている参考文献の多さを見て、私と興味を共有する人々が世界中にはこれほどいるのかと嬉しくなったのを覚えています。

その感動をより多くの人と分かち合いたくて、すぐにその場で追加でもう1冊追加で買い(布教用というやつです)、とりあえず当時の職場の CTO の机の上に置いたりもしました。

ペーパーバック版は大きくて持ち歩きづらいので、友人とのふとした会話の中で参照するには不便です。そのため電子版も買ってスマートフォンに入れてあります。電子版は全文検索が効くので、紙の本をシークするときの索引としても便利ですね。

ひさしぶりに読んだ

そんな本書ですが、今朝2年ぶりくらいに読み直しました。きっかけは若干仕事で必要だったからなんですが、勢いで頭から第Ⅱ部(400ページくらい)まで読みました。思い入れのある1冊の割にはレビュー(?)をしたことがなかったことに気づきました。せっかくなので書きます。

12ページ目くらいで早速 Twitter のタイムラインの話が出てくるあたりで泣きそうになりました(一度読んでるはずなんですけどね)。データシステムに興味があれば、誰しも一度はスケーラブルなタイムライン機能の設計を考えるものです。読者のハートをガッチリ掴んできます。

日常的にデータシステムと向き合っていれば、システムの信頼性を高めるにはどうすべきか、スケーラブルにするにはどうすべきか、という勘が多少なりとも育まれます。しかし、実際に信頼性の低いコードやスケーラブルでない設計を目の当たりにしたときにその問題をわかりやすく説明できるでしょうか。あるいは妥当な修正案を出せるでしょうか。必ずしもそうとは限りません。

本書ではそうした勘を言語化するための強力な武器になります。課題を分解し、概念を定義し、例を交えて読者に整理されたメンタルモデルを与えます。いずれも現場に向き合ったことがあればスッと心に染みる課題ばかりです。課題が分解され整理される様を追いながら解説にうなずき続けることになるでしょう。読者の悩みを読者より雄弁に語ります。

REST と RPC の話が個人的に好きなので一部を引用します。

RESTの魅力の一部は、それ自身がネットワークプロトコルであるという事実を隠そうとしていないことにあります データ指向アプリケーションデザイン P.145

私が今よりもさらに未熟だったころ、RPC に夢を見すぎて実際に本書で指摘されているような RPC の問題に直面したことがあります。本書では、リモートサービスの呼び出しをローカルの関数呼び出しのように見せかけるアイデアには「根本的な問題がある」とすら書かれています。私もそう思います。具体的にどういう問題があるのかは P.144 を参照してください。

関連して、8章の内容も好きです。特に8.2の、非同期パケットネットワークって不便だよね、というあたり。皆さんご存じの通り、IP ネットワークは非同期パケットネットワークの一種です。なのでいろいろ困ったことが起きます(具体的な困りは P.302 あたりを読んでください)。

でも TCP を習う時って信頼性のあるプロトコルだって説明されませんか? UDP と比較して TCP には順序保証とか到達確認とかがあるってだけなんですが、10年くらい前に勉強したときは、TCP はめっちゃ信頼性がある印象を受けました。信頼性あるなら普通に RPC 乗っければいいって発想になりますよね? 私だけ? まぁ結論としてはそれは幻想なんですが。

第3章、ストレージの話が grep や tail を用いた素朴なストレージエンジンの実装から始まっているのも感動的です。私の書いた記事ではいきなり B-Tree から始めてしまいましたが、誌面の余裕さえあればシンプルなテーブルヒープから始めた方がわかりやすいし、インデックスのありがたみや意味が実感できるよな、と思ったりします。

ストレージエンジンに(当然)魔法なんかなくて、実にエンジニアリングらしいチマチマしたトレードオフの選択でそれっぽい性能特性が得られてるだけなんですよね。その出発点として、まずは(直感的にダメそうでも)素朴な実装から始めてみるというのは理解に大いに役立ちます。ちなみに直感的にダメそうでも特定ケースでは問題なかったりしますし、現実がそのうまくいく特定ケースにハマることはよくあります。自戒を込めて。

この調子で書いていくといくらでも語れそうなのでこのへんにしておきます。あ、Avro のライターのスキーマ・リーダーのスキーマとかも地味ですがおもしろいですよね。まずい、話が終わらなくなる。

終わりに

明日も書く。