REQLY開発ブログスマートフォン用の表示で見るREQLY開発ブログプロの料理レシピ専門検索エンジンREQLY(レクリー)の開発ブログです。この広告は、90日以上更新していないブログに表示しています。CONtest)というWebサービスの高速化を図るコンテストにREQLYで参加してきました。唯一インフラが分かる人。なんとなくSQLが分かる人。してpixiv社内ISUCONを2回に分けて解きました。query解析やalp等の計測ツールの基礎を導入方法から勉強し、そのあとは静的ファイルの処理などを試しました。インフラが分かる人がyoco以外にいなかったので、アプリケーション側をm2kuとkenboの2人で担当するフォーメーション。SQLに関しては、m2kuがインデックスとJOINを嗜む程度。kenboが普段書き慣れているPythonを採用しました。本番参加記それぞれの備忘録を貼っておきます。最初の1時間まずサーバーが3台もあったので怯える。こいつは自分のものだと宣言する。notebookが使えるようにpython環境を整えたりbitbucketにwebアプリのコードを上げたりした。残りの担当はmysqlのslowqueryの設定だったが、こっちのサーバーにはmysqlがたってないので何もできない。yocoにmysqlのサーバー3への移籍を優先でやるようにお願いする。その間は、webサイトをいじりつつ、たぬきうどんを食べる。sheetとseatのスペルミスについてkenboがドヤ顔をしているので殴る(10:48)。flaskのコードをみて基本的にシングルページで/api以下にアクセスすることで描画に必要なjsonをもらっていそうだ、ということが分かる。しているうちにサーバー3でmysqlがたつ。練習では/etc/mysql/my.cnfをいじったがファイルが見つからなかったので怯える頑張ってググると(10:56)includedirみたいなことをやってどっかのディレクトリを読み込んでるみたいだった。check_mysql.shなどの便利スクリプトも仕込む。ベンチマークベンチを回そうとなったが、サーバー1からサーバー3へのdbのアクセスがうまくいっていないらしく、手こずっていた。/api/event/10を叩くと直接jsonが見れることを確認eventにもpriceカラムがあって、sheetにもpriceがあることが分かる実際にwebサイトを見ると、2つの和になっていそうなことを確認どのeventを選んでも、Sの席数などは変わらないことを確認。/loginと/usersと/eventsが遅いな、という空気感になる。/usersと/eventsが遅いのはクエリ的(スロークエリ、N+1)にも妥当だが、/loginが遅いのはクエリも速いし納得が行かないと主張する。誰も信じてくれないので、pythonのloggingを用いて、関数の実行時間を取得するコードを書く。実際にこの関数は数ミリ秒で終わっていることが分かる(12:30)。次回はアプリケーションの関数の速度を直に観測するツールの準備が必須だと感じる。kenboと唐揚げを買いに行く。道中/usersと/eventsは2時間くらいあれば自分が直せそうなので引き受けると主張する。/loginは全然わからないのでkenboに調査をお願いする。仕事を始める(12:54)。/usersの方はスロークエリの上位に入っていたのでindexを貼れば簡単に治りそうだと思い先に着手する。HAVINGとか初めて見て怯える。合わせてこれが諸悪の根源であることに気付く。方針転換して、get_event()のN+1を最初に直すのが良さそうだと考える。notebookでget_event()をリニューアルすることを始める(13:46)。最初はDBとの接続に苦労したが、yocoに聞いたら「flaskを捨てろ」といって華麗に解決してくれる。新しくsheet情報を一括で取得するクエリを作成し、結果を元のget_event()と返り値が同じになるように整える。jupyter上で%%timeで計測したら、4倍くらい早くなっていたので、良さそうだと感じる。ドヤ顔をして30分休憩する。totalの席数やランクごとの値段の加算がeventによって変わらないということをあらかじめ知っていたので、コードを単純化できたのも良かった。event残り2.5h残りの2.5hで何をするか話し合う。/usersと/adminの2つがネックになっているという話になる。/usersの方を取り、/adminを2人に任せる。/eventsでは自分以外のsheet情報がいらないのでget_event()を呼ばずに専用の小さいクエリでアクセスするようにするuser_idでindexを貼るなどの細かい修正をしたが、ベンチマークがしばらく走らなくなってしまっていたこともあり、改善がどれくらいあったのかは分からない。ここに関してはmaster一本開発より、各々featureブランチを切っても良かったかもと思った。あと、indexを手動で貼っても、schemaに書いておかないと再起動後には失われてしまうということに気付いていなかったため、重いクエリを読み間違えたりしていたのが勿体無かった。サブクエリ内で使われると思っていたINDEXが、実際にEXPLAINで確認すると使われてなかったりして、本戦までにはSQL力を上げておきたい。BYしなくても良い説をなども唱えたが、これはどうなんだろうか。そもそもカラムがすでにsortされているかを確認するSQL文を知らないので、調べておく。最初の2時間h2oに対してalpを仕込もうとしたが、alpはltsv形式での出力を要求している。nginxの設定ファイルをみながら、h2oのログの出力をalpがパースできる形に変換しようと考えたが、うまくいかない。そこでいろいろ調べてみると、kataribeというロギングツールがあることがわかったので、kataribeを導入することにした。kataribeは、h2oのもとのログフォーマットに、request_timeを追加するだけであったので、容易に導入することができた。3台体制であったので、いままでの練習会で試していないパターンであって動揺するが、すぐにサーバ1とサーバ2をアプリケーションサーバ、サーバ3をdb用にすることに決めた。アクセスしたときに実行されるが、その初期化コードが参照するmysqlが、デフォルトではサーバ1のものであったためである(これが俗に言う鈴木明エラーである)。このサーバがさす向きをipアドレスを直で指定してサーバ3に切り替えるという修正を行った。お手製のpython再起動スクリプトrestart.shと、チームslackに通知するためのスクリプトslack-notifier.shを、サーバ1およびサーバ2にも導入した。repositoryでpythonサーバのコードを管理しておいて、アプリケーションを再起動したいときはそのコードをpullしてsystemctlでrestartする環境を整えることができた。ここまで2時間かかってしまったことは誤算であった。それ以後試したことインフラ側のチューニングを行った。今回の初期設定ではgunicornというアプリケーションサーバで、flaskのアプリはサーブされている。これは、もとの設定ではワーカ1、スレッド1で動いていたので、1コアしか利用することができなかった。connections;をしたところ、1ワーカにつき1つのコネクションしか貼らないという挙動がみえた。この挙動通りだとすると、スレッド数を増やすよりワーカの数を増やしたほうが、mysqlのスロークエリがコネクションを占有することが少ないのではないかという仮説を持った。そこで、少し変則的であるが、スロークエリが多いことからワーカー数を増やして並列化することにした。これにより、アプリケーションサーバで1コアだけでなく、2コアを使うことが可能になり、スコアが多少増加した。h2oはnginxに比べてほとんどチューニングする余地はない。アクセスだけを、サーバ2に送ることにした。これは、結果的には/admin以下にある巨大なCSVを吐くエンドポイントがワーカを占有することを回避できたといえる。(mysqlのトランザクション分離レベルの問題か?)。その後、kenboと、遅い箇所について検証した。/loginが遅いのはクエリも速いし納得が行かないと主張する。誰も信じてくれないので、pythonのloggingを用いて、関数の実行時間を取得するコードを書く。いうところから、ロギングで実行時間として計測していない部分、今回の場合ではreturn文でjsonifyしているところが遅いのではないかと考えた。jsonを吐くところが律速になってたようで、python-rapidjsonを導入することで1リクエストあたりの所要時間が激減し、スローリクエスト順にソートされたログの上位からは姿を消した。UPDATEでロックをとっていたことが律速であったのでそれほど意味がなかった。m2kuがget_eventのN+1クエリを華麗に解決したので、3万点に到達した。m2kuがクエリ修正をやっている間に、yocoとkenboではSQLをほとんど触れなかったので、それが今後の課題となる。UPDATEリクエストが混じっているせいで、点数の入るリクエストの実行が妨げられていたと思われる。そのため、いかにうまくそれらを捌くか、ということが重要であったように見える。対するアクセスが1回につき数千アクセス発行されるようになった。これらのアクセスは、点数にはほとんど寄与しないが、全体の処理速度を遅くしてしまうという問題があった。これに対して、これ以外のエンドポイントをサーバ分けてしまうと、reservation更新/参照系のクエリに不整合が起きる(例えばサーバ1に更新クエリを送ったとき、それが処理されるまえにサーバ2で参照している?)らしいということがわかったので、色々と考えてみたが、failを完全に防ぐ切り分け方は当時は見つからなかった。failに苦しんでいたようだった。)コメント今回の勝因は「細かい修正箇所に惑わされず、計測結果を信じて最もボトルネックとなっていたget_eventを修正したこと+/admin以下の遅いクエリを別サーバに待避させた」という点にあると思っている。再起動確認はビビらずにさっさとやればよかった。おわりに運営の皆さんありがとうございました。エンジニアのkenboです。レシピ検索エン...2018-07-01codeFlyer本選参加記こんにちは。エンジニアのm2kuです。卵といえば親子丼だろう。»プロフィールid:reqly-tokyoプロの料理レシピ専門検索エンジンREQLY(レクリー)の開発ブログです。アーカイブはてなブログをはじめよう!reqly-tokyoさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。