ISUCON での言語移植 (Rust)

ISUCON10 に続き今回の ISUCON11 でも初期実装の Rust 移植を担当したのでそのへんの話を書いてみます。

ISUCON とのかかわり

ISUCON4 から ISUCON7 までは選手として参加していて、何度か本選にも出場しました。ISUCON8 以降に参加しなくなった理由はいくつかあるんですが、Web のインフラやバックエンド界隈を盛り上げて学生の興味を惹く素晴らしいイベントだと思っています。そんなわけで ISUCON からはしばらく離れていたんですが、ISUCON10 では同僚が作問するということと新たな試みとして初期実装に Rust を加えると聞いて、Rust 移植担当に応募して採用されました。そして今回の ISUCON11 でも Rust か Ruby の移植に応募し Rust の移植を担当しました。

言語移植を担当するモチベーション

自分の場合は ISUCON が盛り上がってほしいことに加えて、自分の好きな言語が盛り上がってほしいというのが主なモチベーションです。自分は Rust や Ruby が好きなんですが近年の ISUCON は Go に一極集中していて、元々プログラミング言語自体が好きな自分にとって1つの言語にあまりに偏りすぎる状況はつまらないように感じます。ISUCON11 ではこの傾向が更に加速してしまって、本選出場者のうち9割が Go でそれ以外は Rust と Node.js (TypeScript) のみという結果でした。

isucon.net

Rust に限って数字を見ると ISUCON10 と比較して予選での利用者が大きく増えていて本選通過も2チームに増えたので、Go 一極集中になんとか対抗していきたいところです。

あとは移植作業を通じて色々な言語の差を感じられるというのも楽しみの1つです。Go ではこういう書き方をする・できるんだなぁというのを見ながら、Rust に移植するならどう書くか考えたり、また自分のあまり詳しくない TypeScript や Python ではこう書くんだなというのを眺めるのは楽しいです。

ISUCON での移植作業

問題のテーマが決まってオリジナルである Go の初期実装の原型があるくらいの段階から参加して、Rust に移植する上で十分なライブラリがあるか等の移植する上での問題点のチェックから始まります。この時点で気になった点は作問者にフィードバックして初期実装をどうするか決めてもらいます。ISUCON10 本選では gRPC を扱えるかというのがありましたし、ISUCON11 予選では JWT、本選では UUID *1 や zip の扱いをチェックしました。ある程度初期実装が固まってきたら Rust 版を書き始めて、同時に開発が進んでいるベンチマーカーを手元で実行して通るか確認したりブラウザ上での表示を確認したりしながら移植を進めていきます。その間にも問題の改定が進むので Go 版の初期実装に変更が入ったらそれに追従したり、ベンチマーカーの実装が進んでチェックが厳密になったら失敗するようになった箇所を直したりといった作業が続きます。ISUCON11 はかなり穏やかに進みましたが、ISUCON10 本選のときは https://github.com/isucon/isucon10-final/issues/136 ということがあってだいぶ緊張感がありました。 あとはコードレビューを受けたり、本番環境で Rust 版に対してベンチマークを実行して完走するかチェックしたり初期スコアを確認したりすればだいたい終わりです。

これは厳密には移植担当の仕事ではないんですが、ISUCON11 では事前の試し解きにも参加して、実際に動くポータルの環境とマシン1台を用意してもらってその時点での問題を解き、こういう改善を入れてこれくらいスコアが伸びたみたいなメモを残したり問題に対するフィードバックを書いたりして作問に少しだけ協力していました。自分の場合は移植の動作確認も兼ねて Rust に移植した状態で問題を解いてました。

Go から Rust に移植する上で大変だなと思うのは、ライブラリの細かい挙動の差はもちろんあるんですが、Go から Rust 特有の話としてはエラーの扱い、nil やゼロ値の扱い、ポインタの扱いあたりでしょうか。Go の場合どんなエラーが返ってくるのかの詳細が型の上でもドキュメントでも分からないことが多く、Rust のように細かく分類されたエラーのうちどれを拾えばいいのか分かりにくく感じることがあります。これと似た話で Go では失敗したり存在しなかったりするときにゼロ値を使うことがあり、Rust に移植するときに Go 実装を注意深く読んだりベンチマーカーが投げるリクエストを調べたりしないと Option<T> にすべきか単なる T にすべきか悩みます。また Rust は GC が無くライフタイムの概念のある言語なので、Go で自由奔放にスライスを扱ってるような実装を Rust でどう書くか悩んだりしました。具体例としては ISUCON11 予選問題のこのへんを見比べてみてください。

ISUCON で初期実装として Rust が提供されるようになったのは ISUCON10 が初であり、その移植を担当する上でどのフレームワークやライブラリを選択するかも悩みました。Web フレームワークとしては2回とも Actix Web を選んでいます。

actix.rs

他にも warp も検討したんですが、RubySinatra くらいの機能が標準で提供されているものとなると Actix Web かなぁということもあって決めました。ISUCON11 では Actix Web の beta 版が使われていることが気になった人ももしかしたらいたかもしれませんが、今更 Tokio v1 以前に揃えるのもなぁということで beta 版を選びました。

MySQL に接続するライブラリとしては ISUCON10 では mysql を選びましたが ISUCON11 では sqlx を選びました。ISUCON10 のときも sqlx は検討対象でしたが、まだよく枯れてない印象だったので避けました。ただ今回の ISUCON11 では使っても大丈夫そうかなと考えを変え、async/await に対応していて Tokio v1 への対応も終わってるということで sqlx にしました。Actix Web も sqlx も、ISUCON で利用する上で重厚すぎず、またデファクトスタンダードとして広く使われているものという基準で選んでいます。まだ始めてから2回目なので、もしライブラリ選定に疑問があれば声を上げてもらえればと思います。

sqlx の不具合

ISUCON11 本選の移植作業中に sqlx の MySQL 向け実装固有の不具合の影響を受けていることが発覚し、sqlx の実装を読みつつワークアラウンドを入れて不具合を回避するという対応をしました。実は予選のときにも同じ不具合が発生していたんですが見落としていました。競技中のスコアには影響しないと思いますが、競技中に panic のログが出ていて戸惑ったチームがいたら申し訳無いです。何が起きていたかについては本選では https://github.com/isucon/isucon11-final/blob/0bd78572393513f0b6534365f378c34595e2463e/webapp/rust/src/db.rs に説明を書きました。過去問として利用されるときに混乱させないよう、予選のほうにも同様の修正を後日入れておきました https://github.com/isucon/isucon11-qualify/pull/1454

おそらくこうすれば直るだろうというパッチを sqlx に出しています https://github.com/launchbadge/sqlx/pull/1439 が、本当にこの修正でいいのか現時点では不明です。

まとめ

ここ2回の ISUCON での言語移植について書いてみました。移植作業には移植作業の楽しさがあります。今後も ISUCON が続くことを願っていますし、選手として参加する気持ちが復活しない限りまた Rust か Ruby の言語移植に応募しようと思っています。

*1:最終的には ULID に変わりました