KINTO Tech Blog
ISUCON

KINTO Technologies社員でISUCON14に参加しスコア1万点を超えた話

Cover Image for KINTO Technologies社員でISUCON14に参加しスコア1万点を超えた話

この記事は KINTOテクノロジーズアドベントカレンダー2024 の21日目の記事です🎅🎄


はじめに

こんにちは。KINTO FACTORY開発グループのうえはら(@penpen_77777)です。
2024年の7月に入社し、KINTO FACTORYのバックエンドの開発を担当しています。

今回は12/8に開催されたISUCON14にKINTO Technologies社員で参加してきたので、その内容や結果を共有したいと思います。

ISUCON14とは

ISUCONとは、LINEヤフー株式会社が運営窓口となって開催している、お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトルです。
優勝すると賞金100万円を獲得できます!
今年は12/8(日)の10:00〜18:00で開催されました。
私(うえはら)は昨年のISUCON13からパフォーマンスチューニングの知識習得はもちろん、エンジニアとしての腕試し・スキル向上を目的に参加しています。

チーム「ktc-isucon-bu」

社内Slackでうえはらがメンバーを募集し、チーム「ktc-isucon-bu」を結成しました。
メンバは以下の通りです。

  • うえはら(@penpen_77777)
    • ISUCON13にも参加しており、今年で2回目
  • 古谷
    • ISUCON初参加
  • 西田
    • ISUCON初参加

ISUCONでは初期実装で使われている言語がいくつかありますが、今回はGo言語を選択しました。
また、初参加のメンバーが多いチームのためランキング上位を狙うというよりもスコアが10000点を超えることを目標にしました。

参加に向けての事前準備

ISUCONでは競技本番での改善タスクに集中できるように、典型的な作業は自動化もしくは容易に実行できるようにするのが重要です。
今回は、以下の準備を行いました。

  • 環境構築・デプロイコマンドの整備
  • ドキュメント生成ツールの整備
  • 計測ツールの整備
  • 個人練習/チーム練習

環境構築・デプロイコマンドの整備

go-taskはターミナルでタスクを実行するのに特化したツール、いわゆるタスクランナーです。
https://taskfile.dev/
従来だとmakeコマンドをタスクランナーとして使われることが多いと思いますが、個人的にはgo-taskの方がより便利に感じたので今回はgo-taskを使用しました。
(makeと異なりインストールが必要なのが難点ですが、そこを除けば業務でも十分活用できるかなと思います)

例えば以下のようなtaskfile.yamlを作成し、setupタスクとdeployタスクを定義します。

version: '3'

tasks:
  setup:
    cmds:
      # 環境構築用コマンドを記述
      - echo "setup"
  deploy:
    cmds:
      # デプロイ用コマンドを記述
      - echo "deploy"

作成後、以下のようにtask タスク名でコマンドを叩くことでタスクを実行できます。

# 環境構築タスクを実行
$ task setup 
setup

# デプロイタスクを実行
$ task deploy
deploy

また、タスク実行時にコマンドライン引数を渡すこともでき、受け取った引数をタスク内に埋め込むこともできます。

version: '3'

tasks:
  setup:
    cmds:
      - echo "setup {{ .CLI_ARGS }}"
  deploy:
    cmds:
      - echo "deploy {{ .CLI_ARGS }}"
# サーバisu1に対してsetupタスクを実行
$ task setup -- isu1
setup isu1

# サーバisu2に対してdeployタスクを実行
$ task deploy -- isu2
deploy isu2

上のコードは簡単な例ではありますが、実際使用する際にはコマンドライン引数を用いて反映先のサーバを自由に切り替えられるように作っておくことで チームでの分担作業が捗ります。

その他、go-taskを使うメリットは以下の通りです。

  • サブディレクトリで作業していても、親ディレクトリに存在するtaskfile.yamlを検出してタスクを実行できる
    • ディレクトリの位置を気にせず、タスクを実行できる
  • タスクからタスクを呼び出すことができるため、定義したタスクの再利用性を高められる
    • deployタスクを実行する前にsetupタスクを実行するなど、タスクを組み合わせて実行できる
  • ISUCONで使用するツールを全てtaskfile.yamlに記述しておくことで、ツールの使用方法(必要なオプションなど)を知らなくてもtaskコマンドを叩くだけで実行できる

事前にtaskfile.yamlのテンプレートを作成しておき、本番では一部の変数を書き換えるだけであらゆる問題がきても柔軟に対応できるようにしておきました。

ドキュメント生成ツールの整備

以下の2つのツールを使用しました。

https://github.com/k1LoW/tbls

  • DBの中身を読み取り、ER図とスキーマの説明が入ったドキュメント(markdown)を生成
  • データベースに繋ぎに行かなくてもスキーマ定義を確認できるため、DBの構造を理解するのに便利
  • CI/CDパイプラインでドキュメントを自動生成することで、DB構造の変更を追いやすくなる

https://github.com/mazrean/isucrud

  • アプリケーションコードを読み取り、DBテーブルとの関係を可視化・ドキュメント(markdown)を生成
    • 関数が多くなってくると図がわかりにくくなるのが欠点だったのですが、webブラウザ上でインタラクティブに見たい箇所を絞り込めるようになったのでとても使いやすくなった

計測ツールの整備

以下のツールを使用しました。

https://github.com/kaz/pprotein

  • アクセスログ、スロークエリログ、プロファイルデータを収集し、可視化するツール
  • ベンチマークを叩くとwebhookによって自動的にデータ集計を始めてくれるようになるので便利
  • データ収集時点でのコミットハッシュも記録してくれるので、どのコミットがスコアアップに寄与したかがわかりやすい[1]

プロファイルデータ集計結果
アクセスログ集計結果
スロークエリログ集計結果

個人練習/チーム練習

練習に関して、初参加のメンバーがいきなりISUCONの過去問を解こうとしても難易度の高さから挫折してしまう可能性があります。
まずはパフォーマンスチューニングの雰囲気を感じ取ってもらうため、達人が教えるWebパフォーマンスチューニングという本を読んでもらいました。

https://gihyo.jp/book/2022/978-4-297-12846-3

書籍を読み進めつつ、以下を練習問題として週末チームで集まり解いていきました。
https://github.com/catatsuy/private-isu
(書籍でもprivate-isuを使ってどのようにチューニングしていけば良いか紹介されています)

大体10~20万点くらいまでスコアを取れるようになってきたところで、昨年のISUCON13の問題に変えて解いていきました。
ここまできたら本番を意識した練習にスライドしても特に問題なく進められるようになったかなと思います。

今回のお題

https://www.youtube.com/watch?v=UFlcAUvWvrY

  • ライドチェアサービス「ISURIDE」の改善
    • チェアオーナー(椅子の所有者)が椅子を提供
    • ユーザはアプリから椅子が配車され、その椅子を使って目的地まで移動できる
  • スコアは移動距離やライド完了率などから計算される
    • ユーザ満足度を高めるように改善を進める必要がある

https://github.com/isucon/isucon14

結果

結果は以下の通りです。目標の1万点を超えることができました!

  • 135位 / 831チーム中
  • 12514点
    自チームのスコア
    スコアの時間変動

できたこと

  • デプロイスクリプト等の準備(うえはら 10:00~10:30)
  • 当日マニュアル(古谷 10:00)
  • 動作しているプロセスを確認しておおよその構成を理解する(古谷 10:00)
  • アプリケーションマニュアルを読む(西田 10:00)
  • MySQLにログインし、テーブルサイズを調べる
  • インデックスを追加(西田 11:21)
    -- chair_locations
    CREATE INDEX idx_chair_id ON chair_locations(chair_id);
    CREATE INDEX idx_chair_id_created_at ON chair_locations(chair_id, created_at);
    -- chairs
    CREATE INDEX idx_owner_id ON chairs(owner_id);
    -- ride_statuses
    CREATE INDEX idx_ride_statuses_ride_id_chair_sent_at_created_at
        ON ride_statuses (ride_id, chair_sent_at, created_at);
    
  • pproteinの仕込み (うえはら 11:48)
    • nginxの設定を修正するのに手こずり、1時間以上かかってしまう
  • インデックス追加(西田 11:54)
    CREATE INDEX idx_ride_statuses_created_at_ride_id_chair_sent_at
        ON ride_statuses (created_at, ride_id, chair_sent_at);
    
    -- rides
    CREATE INDEX idx_chair_id_updated_at ON rides (chair_id, updated_at DESC);
    
  • 動的パラメータを有効化・jsonパッケージを高速化(うえはら 12:38)
    // import文を修正
    "encoding/json""github.com/goccy/go-json"
    
    // DB接続時の設定でInterpolateParamsをtrueに
    dbConfig.InterpolateParams = true
    
  • インデックス追加(うえはら 13:05)
    -- chairs
    CREATE INDEX idx_chairs_access_token ON chairs(access_token);
    
    -- ride_statuses
    CREATE INDEX idx_ride_statuses_ride_id_app_sent_at_created_at
        ON ride_statuses (ride_id, app_sent_at, created_at);
    
    -- rides
    CREATE INDEX idx_rides ON rides (user_id, created_at);
    
    -- coupons
    CREATE INDEX idx_coupons_used_by ON coupons(used_by);
    
  • ユーザ・ステータスキャッシュ(古谷 14:30)
    • usersとride_statusesテーブルをインメモリキャッシュ
  • トランザクション範囲の修正(西田 14:48, 15:09)
  • notificationのポーリング間隔調整(古谷 16:27)
    • appGetNotificationとchairGetNotificationで返しているRetryAfterMsを30 msから300 msに変更
  • 椅子とユーザのマッチング間隔を短くした(西田 17:18, 17:28)
    • ISUCON_MATCHING_INTERVALを0.5 sから0.1 sに短くした
    • スコアが2倍近く上がった(一番効いた施策)
  • binログ停止(古谷 17:24)
    • MySQLの設定ファイル(/etc/mysql/mysql.conf.d/mysqld.cnf)でbinログの出力を停止
      disable-log-bin=1
      innodb_flush_log_at_trx_commit=0
      
  • ログ出力を無効化(うえはら 17:43)
    • nginx、mysqlのログ出力を停止
  • アプリケーション側のログ出力停止
  • ownerGetChairsの改善(西田 17:43)
    • distanceをメモ化するようにした
  • コネクション数を調整(うえはら 17:49)
    db.SetMaxIdleConns(50)
    db.SetMaxOpenConns(50)
    

時間が足りない or スコアがあがらずできなかった施策

  • マッチング処理の修正(うえはら 13:00~16:00)
    • 実装したが椅子が配車されないエラーの解消を乗り越えられず、断念…
  • chairsのアクセストークンをキャッシュ(古谷 15:36)
    • 叩かれるクエリ数は大幅に削減できた(30000→100)が、うまくスコアが上がらなかった
    • 別の箇所にボトルネックが移ったのかもしれない
  • nginxパラメータチューニング(古谷 17:39)
    • ベンチマークのエラーでうまく動かず…
  • サーバ分割(うえはら 16:00 ~ 17:00)
    • nginx+goとMySQLの2台構成に変更しようとしたら、ベンチマークでデータ不整合のエラーがおきうまく動かすことができなかった
    • 2台ともchairとrideのマッチングを行うサービスが動いていたため、データ不整合が起きたことに競技終了後気づいた…(止めておけば…)

良かった点

  • 1万点を超えるという目標を達成できた
    • isucon2回目1名+初参加2名のチームで135位、12514点というのは結果としては上々なのではないかと感じる
  • 全員で改善タスクを進められた
    • 何も手をつけられないということがなく、全員が何かしらの改善を進められた
  • サーバ上への環境構築、デプロイスクリプトの仕込みがほぼ問題なく進められた
    • デプロイ面で特に問題が生じることはなかった
    • ただpproteinの仕込みでnginxの設定がうまくいかず1時間ほど手こずってしまったので、手順書に注意書きを書いておきたい
    • ツールを作っておくと快適に改善を進められるので今後もツール面を充実させていきたい

反省点

  • あまりアプリケーションの中身を理解できずに改善を進めてしまった
    • アプリケーションの仕様理解が重要な今回の問題だと、機械的にスロークエリログやアプリケーションログを元に高速化してもスコアアップに繋がりにくく、手を出せる箇所が少なかった
    • アプリケーション理解が深まるような何らかの仕組みづくりをしたい
    • 普段の業務でも仕様理解は重要なので、今後も意識していきたい
  • マッチング処理をバグらせてしまっていつまでも実装できなかった
    • テーブル構造を変えずにクエリだけで実装しようとして辛くなってしまったので、テーブルに都合の良いカラム追加してアプリケーションコードを単純にして実装しやすくするのが大事だなと思った
  • SSE(Server-Sent Events)について全く知らなかったので勉強しておきたい
    • ユーザの椅子の状態をリアルタイムに更新するのに使える技術として紹介されていた

これからの業務で生かそうと思う点

  • 技術的な知識習得だけでなく目の前のアプリケーション理解は何よりも大事
  • タスクランナー(go-task)やドキュメント自動生成ツール(tblsなど)を使って業務効率化を図っていきたい
  • SSE(Server-Sent Events)についてプロダクトに組み込めるようなところがあれば使ってみたい

まとめ

ISUCON14に参加し、チーム「ktc-isucon-bu」として12514点、全チーム831チームのうち135位という結果を残すことができました。
反省点はありますが、ISUCON初参加のメンバーがいる中でスコア10000点を超えるという目標を達成できた点は良かったです。
この反省を糧に、次回のISUCON15ではさらなる高みを目指していきたいと思います。

初ISUCONにもかかわらず参加いただいたチームメンバーの古谷さん、西田さん、また今回参加までには至らずとも興味・関心をお寄せいただいた社員の皆さんありがとうございました!
最後にこの素晴らしいイベントを開催して下さった運営の皆さんに感謝いたします。

脚注
  1. pproteinでうまくGitコミットハッシュが取得できない現象に遭遇し、fork・コードを修正してPRを出しました。
    https://github.com/kaz/pprotein/pull/37 ↩︎

Facebook

関連記事 | Related Posts