リクルートテクノロジーズ メンバーズブログ  R-ISUCON 2018 Winter解説記事

R-ISUCON 2018 Winter解説記事

この記事はリクルートエンジニアアドベントカレンダーの9日目の記事です。

解説記事をアップします。

R-ISUCONとは

ISUCON(性能チューニングコンテスト)のリクルート版です。

詳しくは以下の記事を見てみてください。

チューニングに王道あり、R-ISUCON 2018 Springレポート

今回の問題「チャットサービス」

チャットサービスをお題にしました。WebSocketでリアルタイム通信あり、既読通知あり、投稿はPOSTだけど、既読通知もメッセージ通知も 1秒間以内 という仕様だけ聞いたら仕様側に「なんとか時間を伸ばしてくれ」と言いたくなるような仕様です。

実際弊社でもリアルタイム通信が多いアプリケーションが増えてきました。チャットであったり、広告配信であったり、検索クエリの補完であったり。

で、チャットとかでよくやってしまいがちな実装の問題も増えてきていて、よくあるのが、「WebSocketイベント送り過ぎちゃう問題」です。

  • 既読通知を送り過ぎちゃう問題(読んだら即時に既読を送る)
  • 受け取らなくて良い人にまで毎回送る(チャンネルに入ってない人に送ってしまう)

この手の問題は WebSocket に熟れてない開発者から来る問題ですが、ナイーブに実装すると起きてしまいます。既読通知等は即時じゃなくても問題ない訳ですが、今回は仕様側(ベンチマーカ側)で「1秒以内に既読送ってね」、と決められてます。

また、今回は初めてR-ISUCONで複数台サーバがレギュレーションに含まれています

これは実際の本家ISUCONではもう「複数台サーバが予選も本戦も前提」となっているため、本家に合わせてこうした形です。

運営が想定するチューニングポイント

/groupsとチャット一覧

まずは、チャット一覧を表示するエンドポイント “/groups” がめちゃくちゃ重いです。

ここは初期実装だと、ユーザーのログインしてからユーザー自身が所属しているグループ一覧を見つけ、さらにチャット数と参加者数を表示しているのですが、グループ一覧を見つけるのも N+1 Query ですし、チャット数と、参加者数は SELECT COUNT(*) FROM belongs_chat_userで毎回テーブルをなめて取ってきているので負荷が高いです。

ここはIndexを貼るとか、JOINしたクエリーをちゃんと作るとか、チャット数はRedisでキャッシュしてしまうとか、非正規化してテーブルを一つにしてしまうなどの対応が見込まれます。

同様にチャットの一覧画面もチャット全体を取得するのにN + 1Queryで実施しています。

ここも同様に非正規化するか、JOINしたクエリーを作るなどの対応が必要です。

WebSocket部分

ここは、非常にナイーブに作ってあります。 まず、接続している人全員に必ず毎回送信されたメッセージや既読などのイベントを送ってしまいます。その上でクライアントのJavaScript側で関係のないイベントは間引いてますが、性能的にもプライバシー的にも問題のある設計ですね。

本来で言うと、接続している関係のある人(チャットのチャンネルに所属している人)にだけ送ればよいはずなので、そのフィルターを作ってイベントを送るだけで良いです。

これをするだけで、そもそもチャンネルにはそこまで人数が居ないので何もしなくてもCPU自体が厳しかったのはあるかもしれませんが、これをちゃんとやればCPU負荷がだいぶ低くなるはずです。

天然の罠

運営側で想定していない天然の罠がいくつか用意されてました(単に想定漏れの不具合です)。

  • メモリが1GBでSwap領域なし
  • そのため、メモリが一定量を超えると何もできなくなる…(サーバ本体を再起動しないといけなくなる)
  • Java実装では空cssがあるとファイルの実態はあってもContent-Lengthが0だと404になる
  • たまーに未来の日付でchatが投稿されることがある
  • Ruby実装ではBundlerが動かない
  • 最初にPythonに実装が動かない
  • サービスとDBを再起動しないと負荷が高まった時にサービスが動かなくなる
  • ベンチマーカがCPUを食い過ぎる問題
  • エラーが発生しすぎるとベンチマーカが結果を記録できなくなってしまう問題

などなど、運営側で準備不足が原因の問題がいくつかありました。

特にメモリが1GBなので、最初にDBとアプリとnginxなどが共存している環境だとそれぞれがメモリを奪い合うので、安定しません。
この問題に対処するために早めに複数台サーバに移行しているチームも多かったです。

結果優勝したチーム

ふんばり温泉チームで、リクルートライフスタイルのチームが優勝しました。

このチームは /groups のチューニングにいち早く実施し、非正規化したことで10万点超えをしていました。他のチームも後から10万点に到達していましたが、今回のコンテストでは、「最新の点数(最新の点数から遡って+10%の誤差のある点数)」を基準にしてランキングが作られるので、ベンチマークを回すかどうかがシビアになります。この点、最初の方にある程度高得点を出してしまうと悩むポイントでした。「ふんばり温泉チーム」は11万点近くを出した後、最後の数十分間は回さないという選択肢をとってました。このへんの戦略もポイントでしたね。