Write and Run

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

COVID-19になった

2019年って何年前だよ。

今更ながら COVID-19 になりました。なってましたというか。

1/15くらいからやんわりと体調が変で1/18の夜に喉が痛くなり、1/19の夜に発熱。そのままするすると40℃まで上がり、布団から出れば寒くて震えが止まらない状態で、トイレに行くのも覚悟がいるといった具合。

かぜ薬は飲んだものの、朝になってもロクに熱は下がらず、1/20の夜も同様に40℃。食欲はあったし嗅覚異常とかもなかったのでご飯だけはめちゃくちゃ食べました。

1/21は40℃まで行かないにせよ38℃は切らないみたいな熱が継続。しかしながらこの辺りからじわじわと喉が痛くなってきました。

1/22になると喉が爆裂痛くなり、一方で熱は38℃弱くらいまで下がりました。喉の痛みと同時に咳が出るようになって体力と精神力が削られ始めました。抗原検査をしたのがこの日。

咳でロクに眠れないまま1/23になり、熱は微熱まで下がったものの寝不足と疲れでヘロヘロに。熱下がってやめてた風邪薬を改めて飲んだらこれがなかなか喉に効き、一筋の光に。

というわけでまだまだ調子悪くて回復しきってませんが、忘れる前にメモ。

相槌すらも喋れないレベルで喉痛いのはほんとにしんどいのでなんとかなってほしい。

総括2023

年末。 KOBA789 です。

本年もさまざまなことがありました。思い出したり反省したり、やんわりと近況報告したりするために書き残しておこうと思います。

年初

2023年はロケットの打ち上げから始まりました。私が管制システムの一部を担当した衛星が1月3日の夜中に打ち上がり、初の人工衛星運用を体験し、我々の至らなさあるいはコンピュータの活躍の余地について解像度を高める経験となりました。 仕事なので詳細は書けない。

また、友人に煽られて新車の購入を決意。煽られた翌週にディーラーに行き、即日サインしました。 買ったのはスズキ・スイフトスポーツ。よくも悪くも私に似合ってるのではないでしょうか。車のチョイスは残酷なほど当人の趣味が出ます。 このタイミングで車を買ったのは、欲しかったからだけではありません。仕事ばかりで遊んでおらず貯金が貯まっていたので、このままだとまたすぐ会社を辞めそうだなと思ったからです。車を現金で買えば一気に貯金は減るし、走りたくなって無駄遣いするはずなので好都合です。労働意欲を維持するために欲しい車を買うのは有効です。

古い方の車は必ず北海道に連れて行くと決めていたんですが、新車を衝動買いしたので時間的猶予がなく季節は真冬。詰んだかと思いきや、水戸のタイヤ屋さんがスタッドレスのレンタルをやっていたのでそこで借りて走って行きました。

仕事では採用活動に奔走していた……ような気がします。 思い返すと、今チームで共に仕事している同僚にはこの時期に入ってもらった人が多く、一年前の心細さをもはや思い出せないほどに毎日助けられています。

趣味の方だと、YouTube を放置して旅行三昧でした。5月の連休には新しい車で九州の南端、佐多岬まで自走で行きました。はじめ、MT 車を買うことに多少の不安があったのですが、3泊4日で3300kmも走ると左半身が意識の外でシフトチェンジをしてくれるようになり、実質 AT です。

また鹿児島旅行の翌々日には富士24耐にも行きました。テント張って観戦してたのですが、寝ても覚めてもエンジンの爆音が響いているというのはそれだけでおもしろいです。目で追いやすい速度で走る道路公団リバリーのフィットが最後まで楽しませてくれました。 現地とはいえ広いサーキット全体のことは見渡せないので、観戦は YouTube Live を観ながらです。セルラー回線でやると安定しないしギガが爆発してしまうしでいいことないので、Starlink を持ち込みました。いやー、快適。テクノロジーってすごい。

この季節も例に漏れず、いろいろありました。 私が作っていたものの一部が OSS になり、社外の人と仕事の話をしやすくなった……というのが一番嬉しかった出来事かもしれません。

夏はやっぱり北海道でしょ、ということで(一般人が簡単に到達できる)日本最北端に行きました。札幌の北の石狩あたりから稚内に向けての海岸線沿いには、オロロンラインという景色の綺麗な最高の道路があります。しかしオロロンライン周辺には宿町が少なく、ホテルというと留萌あたりにしか期待できません。 というわけで、人生初のキャンプをしました。先述の富士24耐で一応練習していたとはいえ、知らん土地でひとりでテント張って寝るというのはなかなか緊張します。ランタン忘れて真っ暗だし。 オロロンライン沿いに点在するそれぞれの町には、道の駅とキャンプ場と温泉がセットで存在しがちです。しかもキャンプ場は無料だったりします。車でのアクセスもよくて便利です。 1泊目のサイトが快適すぎて、2泊目にナメてかかったら、1泊目の撤収時にコオロギをテントに巻き込んでいたようで、いきなりペルソナノングラータとの戦いになりました。

手軽に行ける南端と北端に到達したので、ここらで旅行欲はおさまり、今年の秋の行楽シーズンはちょっとした日帰り旅行程度になりました。

仕事はボチボチというか、相変わらず忙しい日々が続きますねという感じでした。年始の打ち上げで燃え尽き気味だったけど、この頃チャレンジングな仕事がまた始まって、少しモチベーションが回復したかな。

プライベートの方では7,8年くらいお付き合いしていた相手と別れるなどのイベントがあり、仕事が一切できないくらいに精神はグラグラでした。相手に迷惑がかかるので詳細は書きませんが、大きな衝突があったとかではないので、双方を知っている各位に関してはあまり気にせず接してもらえると幸いです。 落ち込んでいる時期に支えてくれた古い友人各位には本当に感謝しています。持つべきものはよき友人です。

ここまで来ると最近の出来事です。 ISUCON は散々な結果でしたね。チームの力を引き出せなかったなぁという反省があります。1人だけ気張っていても勝てないというのが、チームで挑む ISUCON の難しいところです。それでも、チームメイトはいた方が楽しいのでチームで出場しています。ひとりだったら勝てるというわけでもありませんが。

年末には特番と称する長時間配信もやりました。年末特番という言葉の響きになんとも言えぬ憧れがあったり、RiJ のように風物詩化してる企画が好きだったり、年末年始って暇な時間多いよねという下心だったりで、年末の長時間配信は毎年やりたいと思っています。昨年末のように仕事の繁忙期が重なってしまうと難しいんですけどね。

企画の趣旨としては、Z80 という有名な CPU を(私が)知りたい、みんなにも知って欲しい、素朴な CPU を通じて低レイヤーに興味を持って欲しい、非定型な試行錯誤の記録を動画として残しておきたい(これはいつも)、みたいな感じでした。 長時間かつゴールデンタイムを外した配信にも関わらず、想像の倍くらいの方に興味を持ってもらえて大成功でした。 ちなみに「知って欲しい」「興味を持って欲しい」というのはつまり、同志を増やしたいということです。話の通じる友人は多いほど楽しいので、いつもそういうモチベーションで企画を考えています。私の企画が誰かにインスピレーションを与え、何かに挑戦するきっかけになれば嬉しいです。

来年に向けて

2024年の目標はすでに決めており、すでに実現可能性の検証も進めているところです。しかし、ここに書くのは恥ずかしいので、秘密にしておきます。

2024年の KOBA789 にもご期待ください。

非公式ISUCON練習プラットフォームISUNARABEを公開した

あなたの予想に反して、この記事の大半はタイトルとおおよそ関係のない感想文である。感情の発露に任せて書いているのであって、読ませるために書いているわけではないため、読みづらくても耐えて欲しい。

表題のとおり、非公式 ISUCON 練習プラットフォーム「ISUNARABE」をリリースした。
これは、ISUCON の素振り*1をより簡単にすることを目指して開発されたシステムで、近年の ISUCON 予選のポータルと似たような機能を持っている。
具体的には、過去問環境の CloudFormation テンプレートの自動生成・チーム編成・ベンチマーク実行・ベンチマーク結果確認などである。自動生成された CloudFormation テンプレートでスタックを作成すると、GitHub から取得したチームメイトの公開鍵が焼かれた状態のインスタンスが上がってくる、というあたりが重要なポイントだと個人的には思っている。公開鍵をチームメイトから収集して数台のサーバーに撒くというのは結構ダルい作業である。

こだわりポイントという意味だと、ベンチマークの実行と結果確認の UX にはちょっとしたこだわりがある。
私はかなりせっかちな性格である。となりに階段があるなら、エレベーターを待つよりも階段を上ることを選ぶ。結果的にエレベーターの方が早かったとしても、何も手を下せずボタンを押して待つことしかできないという時間が嫌いなのだ。鉄の箱が扉の前に到着するまでの間、人間が自由を奪われているのだ。許せるわけがない。
したがって、ベンチマーカーの実行は即時に開始され、ベンチマーカーのログや結果は「直ちに、遅滞なく」UI に反映されなければならない。
Pull 型でポーリングをするアーキテクチャは負荷分散という点において優れているのは知っている。それが理由かはわからないが、AWS のサービスは極端にストリーミング API を避けている。なんでもかんでもポーリングである。まぁこれは課金単位のわかりやすさのためもあるかもしれない。すべて邪推である。 さて、本物の ISUCON ではベンチマーカーは共有資源である。他チームの利用で埋まっている場合には即座に実行開始できない。これは仕方がない。また参加するチームの数は数百のオーダーであり、競技の参加者は千を超える。さらには、100万円という賞金(よりもプライド)が懸かった真剣勝負であるため、競技時間中のシステムダウンは可能な限り避けたい。つまるところ、ベンチマーク実行リクエストはキューイングされるし、ベンチマーカーのログを厳密にストリーミングするのは難しい。
しかしながら、ISUNARABE は練習プラットフォームであり、安定性よりも快適性が優先される。なぜなら、快適でなければ人はどんどん素振りをしなくなるからである。したがって、ベンチマークの実行リクエストは即座に受理され、ログは完全にストリーミングされる。ベンチマーカーは CloudFormation テンプレートに含まれており、チームに1台ずつ用意される。共有リソースではないのでキューイングは不要である。

この体験を実現するアーキテクチャは、意外にも自明ではない。最もナイーブには、各ユーザーのベンチマーカーが HTTP API を露出させ、ポータルのフロントエンドがその API を呼び出す方法である。ストリーミングが必要なため、これは WebSocket になるかもしれない。
残念ながらこのアイデアはいくらかの問題がある。どうやってベンチマーカーの Public IP を発見するのか、HTTPS で通信するなら証明書はどのように発行するのか、そもそもベンチマーカーのポートを開放して安全なのか、認証はどうするのか、等である。
そこで私は Reverse HTTP Transport のようなプロキシがあったらよいのではないかと考えた。私が管理するサーバーにプロキシを立てて、プロキシとベンチマーカーの間にトンネルを掘り、ユーザーはそのプロキシ経由でベンチマーカーにリクエストを送るという方針である。
この場合、プロキシで認証と認可を行うチャンスがあり、またベンチマーカー側からプロキシ側に向けてセッションを開始するようにすればポート開放も要らず、サービス検出も達成できる。
Reverse HTTP Transport を真面目に実装してもよかったのだが、証明書の管理が面倒ということと、私のサーバーは Cloudflare の裏側にあり TLS が貫通しない(クライアント証明書による認証ができない)ということから、アイデアだけを拝借して独自のプロトコルを作ることにした。

独自に開発したトンネリングプロトコルの基本的なアイデアは以下の通りである。

  • HTTP/1.1 (HTTPS)の Connection: Upgrade を用いて、ベンチマーカーからプロキシに向かってコネクションを張る
  • HTTP/1.1 なので、Authorization ヘッダに含めたトークンを用いて認証・認可をする
  • 開いたコネクションの上には HTTP/2 のリクエストを流す
    • リクエストの向きは プロキシ→ベンチマーカー

ポイントは、生の TCP ではなく HTTP/1.1 を用いることで、認証情報を乗せるチャンスを得ることと、その上のプロトコルを HTTP/2 とすることで、1本のコネクション上に複数のリクエストを多重化することである。
ここまでくればあとは gRPC のリクエストをこのトンネルの中に通すだけである。gRPC の Server Streaming を使えばログをストリームするくらいはなんの工夫もなく達成できる。

かくして私が納得できる品質の ISUNARABE はリリースにこぎ着けたわけである。趣味で作ったウェブサービスを一般公開するという行為は大変久しぶりであり、ヘタすると10年ぶりとかそういうレベルである。

サービスをひとりで作り、またそれを公開するというのは私にとって特別な意味がある。これは、ワールドワイドウェブが、なんらかの力に独占されているわけではないという事実を噛みしめ、技術者ひとりの持つ力の潜在的な大きさを再確認するための行いである。メモ帳で <CENTER> を書き、DynDNS で半固定 IP に無料のドメイン名を割り当てていた頃と、ウェブは本質的にはなんにも変わっちゃいないんだと感じることで、ある種の全能感を得られる。

ウェブサービスを作ろうとすると、あらゆる難しいことが襲いかかってくる。AWS やら Docker やらといった技術的なこともそうであるが、法律的な部分や、炎上したらどうするのか、攻撃されたらどうするのかといった本当に考えたくないことが多すぎる。法律やセキュリティの知識が不要であるとは言わないが、無知が責められる光景を日常的に目の当たりにしすぎているが故に、過度に萎縮しているのではないかと感じることがある。

個人開発者にはビッグテックのような優秀な法務部もなければ、インシデントレスポンスチームもない。SNS で盛り上がってしまっているユーザーとうまくコミュニケーションをとってくれるカスタマーサポートもいない。でもそれらがなければウェブサービスを公開してはならないのかといえば、そうではないと思う。

私はウェブがこれほど堅苦しくなる以前にだいぶ自由にやらせてもらった側であり、それによって今のキャリアがある。言ってしまえばイタズラを繰り返していたらいつのまにか職をもらえていたという状況である。自身がイタズラ坊主出身であるからこそ、スーツの奴らが嫌いである。結局ギークとスーツという古典的な二項対立かよと思われるかもしれないが、そうではない。ギーク出身であっても、若いギークが生まれる土壌を維持しないやつは全員ダサいスーツであるということだ。大人の事情とかはどうでもいいのである。イタズラ坊主のイタズラを邪魔するスーツだけは絶対に許せないのだ。

この文章にオチはないので、必然的に綺麗にはまとまらないのだが、ひとりでサービスを開発して公開するという行いは、ウェブが未だにイタズラ可能な土地であるかを確かめることであり、ウェブがイタズラ可能でなくなりそうなときは立ち上がらねばならない。さもなくばスーツである。

*1:ISUCON の練習のことをこう呼ぶ

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

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';

その他

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

終わりに

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