Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

【Ruby】初めてのメンバーを誘って福岡開発拠点で ISUCON14 に参加しました!

この記事は、Money Forward Engineers Advent Calendar 2024の12月16日の記事です。 13日目はM-YamashitaさんのCloudNative Days Winter 2024 運営対応記でした。

こんにちは クラウド経費 テックリードの 宮村(みやむー) @miyamura.koyo です。

昨年は マネーフォワード福岡開発拠点 で ISUCON13 に参加しました!

moneyforward-dev.jp

ちなみに ISUCON12 にも参加しています。

moneyforward-dev.jp

そしてもちろん、今年も参加しました! Ruby で!ということで ISUCON14 参加ブログを書きたいと思います。

なんと今年は過去最高の834チーム参加ということで例年よりも盛り上がっております!

isucon.net

参加と仲間集め

さて、今年も参加するぞ!ということで、毎年恒例(?)の参加受付、通称0次予選に臨みます。

こちらは Discord を準備してなんとか突破し、無事参加を決めることができました!

※ 申し込みは先着順となっており、開始から数分で上限に達してしまい参加できないことも。PC の前に張り付くスキル(?)が求められ、0次予選と呼ばれています。

さて次に仲間集め。今年はいつもと違うメンバー、特に ISUCON に参加したことのないメンバーを加えよう!ということでマネフォの同僚を誘っての参加を行いました!

※ ISUCON は最大3人参加できます

チーム名は昨年の「ネオガーディアン」改め「ガーディアンズv」です!

※ 「ガーディアン」は筆者が過去にリーダーを務めていたチーム名です!

当日までの準備

例年通り、業務でバタバタしていたので休日にチームで集まって何回か練習会を行いました。

練習会では毎年の秘伝のタレである Makefile を拡張したり、当日行う作業をリハーサルしたりしていました。

今年の秘伝のタレはこちらになりました。

あとは毎年恒例の当日用のカンペを作成しました。

github.com

オススメ練習の方法

今年は練習方法に革命が起きました。我々は毎年 ISUCON の練習には実際にサーバーを建てる手法を採用していましたが、今年はチームメンバーが OrbStack を使用することで容易にローカルに ISUCON 環境を用意できるということを教えてくれました。

まず、毎年 cloud-init に対応した環境で ISUCON の練習ができるように整えてくれているありがたいリポジトリがあります。

github.com

ここに色々な方法で環境構築する方法が書いてあるのですが、こちらに OrbStack を用いる方法も載っています。

これがなんと以下のコマンド一発で生成できる優れものです。独立した VM をローカルに建てることができ、さらに ssh キーなども自動でホストのものがコピーされます。

orbctl create -u isucon -c isucon13/isucon13.cfg ubuntu:jammy isucon13

もちろんサーバーが1台なのとベンチマークサーバーと競技サーバーがローカルに同居しているので、厳密には ISUCON を再現できませんが、一人で練習するには全く問題ありません。

なお複数台構成も、ローカルで複数の VM を建てると再現できるので簡易的な複数台構成の練習にもどうぞ。ただし、サーバーリソースとしてはローカルの PC マシンの性能がボトルネックになるため、実際の ISUCON とは異なり複数台構成にしてもおそらくベンチマークの点数はあまり変わらないとは思います。この点は実際の ISUCON とは異なる点ですね。

ちなみに個人的には練習には ISUCON11 予選がクセのない問題なのでおすすめです。ISUCON13 も DNS サーバー周りがクセがありますが、例年 ISUCON は何かしらの要素で一捻り入れてくるため一つくらいはやっておいた方がいいかなと思うので、練習をオススメします(ISUCON12 予選は SQLite と MySQL が同居したかなり変化球なので慣れてきたらでいいと思います)。

いざ当日!

当日はオフィス2名、オフライン1名の構成でした。今どきのリモートも交えた開発体制です(?)。

今年は ISURIDE という某タクシー配車アプリのようなテイストの題材でした。例年、近年のトレンドを踏まえた出題なので毎年ワクワクします。

午前中にはカンペを見ながら alp pt-query-digest を駆使して、スロークエリログやアクセスログと睨めっこして、少しずつ INDEX を貼ってベンチを回すところまでスムーズに辿り着きました。

しかし、インデックスを貼っても top コマンドで見た、DB のリソースが足りず CPU 使用率などが張り付いている...。

どうもスロークエリログを見ると、以下のようなメチャクチャな SQL が出ていることが原因のよう。。。数万レコードあるテーブルをフルスキャンしてたり、インデックスが効きにくいクエリになっていたりしていて、抜本的な解決が必要そうです。

SELECT id,
owner_id,
name,
access_token,
model,
is_active,
created_at,
updated_at,
IFNULL(total_distance, 0) AS total_distance,
total_distance_updated_at
FROM chairs
LEFT JOIN (SELECT chair_id,
                    SUM(IFNULL(distance, 0)) AS total_distance,
                    MAX(created_at)          AS total_distance_updated_at
            FROM (SELECT chair_id,
                            created_at,
                            ABS(latitude - LAG(latitude) OVER (PARTITION BY chair_id ORDER BY created_at)) +
                            ABS(longitude - LAG(longitude) OVER (PARTITION BY chair_id ORDER BY created_at)) AS distance
                    FROM chair_locations) tmp
            GROUP BY chair_id) distance_table ON distance_table.chair_id = chairs.id
WHERE owner_id = ?

SQL の改善

一旦、私がこの改善を担当し、残りのメンバーは複数台構成の準備や他の箇所を修正することにしました。

まず ChatGPT に聞きながら SQL を解析すると、これは緯度・経度(今回は整数の座標とみなせる)の履歴レコードから総距離を算出していることがわかりました。

ちなみに普段ウィンドウ関数に触れる機会があまりなかったので「LAG ってなんだ...?」となって解析に苦労しました。ChatGPT 先生に具体的な例を交えた講義を作ってもらうことで理解していきました。

「...ということは、総距離を記録するテーブルを新規に作ってしまえばいいのでは?」と閃きましたが、これが中々難しく、初期データを修正するのと、起動中に POST リクエストで追加されるデータに対して総距離を計算するのと2つ考慮しないといけません。3時間ほどかけて何とかプルリクエストを作成し、少しエラーが出つつも一発で改善ができました!点数も向上しテンション爆上がりでした!

github.com

悪戦苦闘

しかし点数があと一歩伸びない...。

今回は競技マニュアルに「通知エンドポイントで SSE(Server-Sent Events) を使ってもよい」ということが示唆されていました。

これらはリファレンス実装では通常のJSONレスポンスを返すエンドポイントですが、SSE(Server-Sent Events)を利用してリアルタイム通知を実装することも可能です。

github.com

しかしこれを単に適用するだけだとコネクションを貼りっぱなしになってしまうのでインフラ周りだったり、実装周りの工夫だったりをしないとボトルネックになってしまいます。競技終了後の感想戦で気づいたのですが、こちらは JSON を返す実装のまましのぐ戦略もあったようで、SSE に詳しくなく時間が足りない今回のチーム構成では JSON のまま進めるべきでした。

またマッチング周りも、普段の業務ドメインで全く出てこないのでうまく勘所を掴めずに決定的に有効なチューニングにならないまま時間が過ぎていきました。

初の複数台構成

そうこうしているうちに、チームメンバーが DB サーバーを別サーバーに分ける作業を進めてくれており、こちらが何とか競技時間内に間に合いました!

これまで参加した ISUCON では2台目以降のサーバーを有効活用できてなかったので、個人的には大きな前進です。

ちなみにこぼれ話ですが、作業途中でチームメンバーが .ssh ディレクトリを吹き飛ばしてしまい、誰も ssh できなくなってしまい焦る場面がありました笑

私もだいぶ焦ったのですが、冷静に考えると AWS コンソールから EC2 に root ユーザーで直接入れることに気づきました。そこで生きているサーバーから .ssh/authorized_keys をコピペして何とか復帰しました。30分以内で復旧できたのでこれはファインプレーでした笑

これで少し点数が伸びたところで競技時間も後わずか。最後の悪あがきをしつつ、ログ周りなど切って競技終了となりました。

結果

今年もガッツリ8時間使って終了!94コミットを積むことができました。

今年はリポジトリ公開したので作業ログは以下に置いています。

github.com

結果は... 約232位/834チーム (上位約 27.8%) でフィニッシュしました。

isucon.net

今年は昨年よりスコアが伸びず悔しいです><

やはり得点に結びつきやすい通知とマッチングアルゴリズムの改善で有効打が出なかったのが敗因でした。

通知機能にはとりあえず SSE を入れてしまってインフラ周りがチューニングできなかったですし、マッチングアルゴリズムは得点を大幅に伸ばす改善まで至りませんでした。総距離の修正も3時間かけてしまいましたが、もう少しスムーズにできれば他のところに時間をかけられたので惜しかった...。

とはいえ、チーム的には去年できなかった複数台を活用することだったり、個人的にはアプリの難しい改善を時間内に実現できたのは昨年よりも前進したポイントでした!

また、今回誘って初参加してくれたメンバーも ISUCON を通じて楽しさや、学ぶことが多くあったと言っていて、新たなメンバーが経験を積むという点では良い機会となりました。

全体的に、結果は奮わなかったですが得るものの多い大会となりました。来年はリベンジするぞ!

まとめ

今年も忙しい中、なんとか時間を作って参加してよかったです!

近年はクラウド化・コンテナ化が進んで、サーバーに ssh して何か作業することも少なくなってきた今日この頃。

年に1回はこうやって感覚を取り戻していきたいですね。

ちなみに、マネーフォワードでは他にも何人か参加したエンジニアがおり、彼らはもっと高いスコアを出していました。普段の業務の忙しさがそこまで変わらない社内で負けるのは悔しい...!ですが仲間として頼もしくもありますね。

来年はもっといいスコアを取るぞ!


マネーフォワード福岡開発拠点では、ISUCON 大好きなエンジニアを募集しています!

ぜひ一緒に参加しましょう!

hrmos.co

福岡開発拠点のサイトもあるのでぜひみてください!

fukuoka.moneyforward.com