Write and Run

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

ISUCON11予選に今年もRubyで出場して敗退した

KOBA789 です。

今年もチーム「ソレイユ(osyoyu, koba789, s4ichi)」で ISUCON に Ruby で出場し、敗退しました。

まずは我々の戦法や秘密兵器の紹介から。

伝統と信頼のサーバーサイドプログラミング

ISUCON7 くらいのときから続けている手法で、競技用のサーバーに開発環境を構築し、3人とも同じホストに SSH でログインして同じファイルシステム上のソースコードを書き換えます。

他人の変更をリアルタイムに確認できることや後述するようにソースコードのバージョン管理が不要なことなどがメリットです。

SSH 時に WATASHI という環境変数で自分の名前を渡すことで、同じ isucon ユーザーへのログインであっても各個人の普段使いの dotfiles がロードされるようになっています。

これは「実家システム」と呼ばれており、チームの生産性に大きく貢献しています。

あいかわらず git ほぼ禁止

全員で同じコードベースを編集しているため、分散バージョン管理は必要ありません。 とはいえ分散しないバージョン管理は便利なため、リモートリポジトリなしで git を使っています。

また、ISUCON のような切羽詰まった環境では merge ミスなどで時間を浪費するという考えから、branch を使うことも禁止です。 常に歴史が master 一本になるように運用しています。

mamiya2020

お手製の最強デプロイスクリプト。名前は sora_h パイセンの mamiya にあやかっています。

初めて mamiya と名の付くデプロイスクリプトを用意したのは ISUCON7 のときで、そのときは競技時間中に osyoyu が書いてくれたのですが、この mamiya2020 は私が昨年 ISUCON10 に向けて開発したものです。

この mamiya2020 は、webapp/ruby 以下のアプリケーションのソースコードは当然のこと、/etc/nginx.conf/etc/my.cnf などもデプロイしてくれるというスグレモノ。 しかも .gitignore を考慮して配布ファイルをフィルタしてくれるので不要なログファイルなどを転送してしまう無駄もなし。

ちなみに、deploy などという名前を使わず、わざわざこんなふざけた命名をしているのかといえば、Ruby で優勝した sora_h パイセンへの憧れを忘れずにいようとかそういうことはなく、まぁ単にふざけただけです。

それでも今となってはチーム内の大切なユビキタス言語となっており、競技終了間際では「ベンチ前にマミって!」「マミった!」「ベンチ回します!!」という声が響いています。

isukekka

たぶん詳細は初公開。チーム「ソレイユ」の秘密兵器のひとつ。

ベンチマーク走行終了後、ベンチマーク走行中のログを自動で集計・分析し、結果を通知してくれるというもの。 これも私、KOBA789 作。

ざっくり言うと、/initialize のリクエストハンドラ内で各種ログをローテート、別プロセスで sleep 75 を開始して、75秒後に溜まったログを分析するために alppt-query-digest を起動するということになっています。

alppt-query-digest の出力は autoindex on な nginx でサーブされており、以下のようにアクセスできます。

f:id:koba789:20210822164852p:plain
isukekka が吐き出したファイル

同時にチームの GitHub リポジトリにも上記ファイルへのリンクが書かれた Issue が作られ、その通知が Slack に流れます。

f:id:koba789:20210822165013p:plain

この isukekka により、「Slack の通知が来たらそれを見てプロファイリングすればいい」というワークフローが完成しています。

isukit

環境構築便利ツール集。Itamae レシピの集合体。 ISUCON 9 のときに s4ichi と osyoyu が整備してくれたのが発端。 今回は無職時間を有効活用して私がだいぶメンテしました。

我々は前述のとおりサーバーサイドプログラミング戦法を採用しているわけですが、そのためにはいつも使っている様々なコマンドラインツールが揃っていないと不便です。 それらをチマチマインストールしたり設定していたりしては時間がもったいないので、すべて自動化しています。

また MySQL 8 に強い執着のある我々のために MySQL 公式の mysql-server パッケージをインストールするためのレシピもほぼ自動化されています。 このおかげで、ISUCON11 予選でもなんの迷いもなく MariaDB 10.3 から MySQL 8 に乗り換えることができました。

そういえば元々は Itamae レシピの集合体だったのだけれど、最近はそれらを含む秘密兵器セット全体を指すようになってる、気がする。

osyoyu/stackprof と speedscope

osyoyu/stackprof はその名の通り、osyoyu が改造した stackprof。 speedscope は各種プロファイラの flamegraph をかなりいい感じに可視化してくれるビジュアライザ。

osyoyu/stackprof の詳細は本人が書いてくれる、と思うのだけれど、speedscope での可視化の結果はかなり映えるので貼っておく。

f:id:koba789:20210822170312p:plain
映えるspeedscope。Ruby のプロファイリングができる

yalp

SQL で再実装された alp。KOBA789 作。

本家 alp は非常に手軽に一方で、集計条件などの細かい設定はできない(またはやりづらい)という特徴があります。

KOBA789 は SQL が得意なため、だったら SQL で書きゃいいじゃんということで書き直しました。再実装をすると、alp の集計内容の筋の良さがよくわかります。

SQL で書き直したことで、 「ユニークユーザー数のカラムも欲しい」みたいな要望にすぐ対応できるようになりました。

うちのチームで「alp」と言った場合、この yalp を指します。

たたかいのきろく

あくまで KOBA789 の視点です。見えてないところでも他のメンバーはなんかやってた。

  • 10:00 競技開始
    • koba789: CFn スタックを作る
    • koba789: マニュアルを音読しはじめる
  • 10:20
    • koba789: マニュアルの通読が終わる
    • osyoyu&s4ichi: アプリケーション仕様の理解のための議論開始
    • koba789: インフラ構築開始
  • 10:21 頃
    • koba789: ubuntu-cloud-image のデフォルトパッケージとの差分をとり、MariaDB に気づく
  • 10:28
    • koba789: 2台目のサーバーに実家環境構築完了
      • 他の2名がいつものエディタでコードを読める環境ができた
  • 10:46
    • koba789: 1台目のサーバーで完全な開発環境の構築完了
      • MariaDBMySQL 8 に移行された
      • 上記秘密兵器がすべて利用可能な状態になった
  • 11:04
    • koba789: すべてのサーバーで完全な環境の構築が完了
      • 1台目のサーバーのコード・設定を他の2台に mamiya で配れるようになった
      • いつでもトラフィックを分散可能になった
  • 11:10 頃
    • koba789: osyoyu&s4ichi から読解したアプリケーション仕様の申し送りを受ける
  • 11:30 頃
    • koba789: ISU のメトリクスの write heavy であることに気づく
    • koba789: 構造の抜本的な改善ができるか考えるため、read エンドポイントのロジックを読み解き始める
  • 12:30 頃
    • koba789: graph のロジックに完全に頭をやられる
    • osyoyu: icon を DB から降ろそうとして盛大にバグらせはじめる
  • 13:00 頃
    • koba789: icon 周りのバグは nginx の try_files のミスであることを指摘
    • osyoyu: なお icon 周りでバグが出て悩む
  • 13:30 頃
    • koba789: icon のバグは認証の不備であること指摘。X-Accel-Redirect を提案
    • koba789: graph の読解を諦める
    • koba789: s4ichi に /api/trend は SQL の LATERAL JOIN で解けると提案
  • 14:00 頃
    • s4ichi: /api/trend を非同期化
      • 非同期化というか、別デーモン化(loop do; sleep; end)
      • 定期的に JSON をファイルに吐いて nginx に配らせることに
      • 勝手に E-Tag も付いて便利最高
  • 15:00 頃
    • koba789: 適当にインデックスを貼る
  • 15:30 頃
    • koba789: 2台目・3台目も活用する構成にする
      • ようやく2万点
    • s4ichi: calculate_condition_level 消さないと速くならんよと指摘
    • このへんで mysql2 gem がめっちゃマルチスレッド絡みっぽいエラーを出し始めた
    • s4ichi: puma から unicorn へ切り替え
  • 16:10 頃
    • koba789: is_dirty, is_overweight, is_broken を各カラムに分離、generated column で condition_level を埋めた isu_condition2 テーブルを作成
  • 16:30 頃
    • osyoyu&s4ichi&koba789: isu_condition2 テーブルを使うようにすべてのクエリを書き換え完了
  • 17:00 頃
    • osyoyu: GET /api/isu がめっちゃバグる
      • koba789 の提案に従って LATERAL JOIN を使ったが、LEFT OUTER にならなくて悩む
  • 17:15 頃
    • ベンチマーカー(ポータル)が落ちる
  • 17:30 頃
    • koba789: 冷静になるチャンスをもらったと前向きになる
  • 17:54
    • koba789: 冷静さを取り戻す前に闇雲に apt install redis-server
  • 18:05 頃
    • ベンチマーカーが復活
  • 18:10 頃
    • koba789: 冷静になった末、isu_condition2 テーブルの DB 分割を決断
      • 他のメンバーに LATERAL JOIN 使えとか言っておきながら JOIN が使えないインフラ構成への変更を強行(なお競技終了35分前)
  • 18:39
    • s4ichi: 分割された isu_condition2 テーブルへの対応を完了
    • 過去最高スコアを記録
  • 18:45 競技終了
    • 最後に見たスコアは 83520

まとめ

Ruby で出場すると準備含めてやることがたくさんあって楽しい!!!!!

Ruby で戦っているチームだからこそ見える Go の優位性があり、それが見えるからこその戦法とツール開発があります。

たぶんまた来年も Ruby で出ます。