テレビを耐え抜く小手先技術

この記事は ex-crowdworks Advent Calendar 2018 の21日目です.

CrowdWorksを退職して8ヶ月くらいが経った.

CrowdWorksにいたときはSREをしていたので,テレビ放映があるときには少し覚悟が必要だったんだけど,その話をしようと思う.

あと,ここで書く話は,ただの昔話 であり,現在ではテレビが突然来ても割と耐えられるくらいの状態にはなっていると思う.ので,こんなことをしなくても凌げると期待している.

基本的にCrowdWorksは,クラウドソーシングのサービスなので,ニュースサイトやゲームのサービスほど,普段から大量の接続をさばく必要はない. そのため,そういう人たちにとっては物足りないくらいの対策でしかないと思うことを断っておく.

テレビはある日突然来る

大人の事情等により,テレビ放映は事前に完全な情報を得られない場合が多い.

これが一番きつくて,「あれ?なんかいきなりサービス負荷上がってない?」と思ったら,お昼からテレビでクラウドワークスの話が放映されていたりすることがあった.

事前にわかっていれば,これから書くような小手先の対応も,多少は講じられる場合があるが,不意打ちで来るとそういう準備ができないので,サービスの実力が試される.

「○日の〇〇という番組に出ます!」という情報を事前にもらえることもあれば,「○日の〇〇の中で,取り上げてもらえるかもしれない」みたいなこともある.

これはきっと大人の事情なので,諦めるしかない.

インフラはもちろん増強する

CrowdWorksはAWSを使っていたので,放映時間がわかっている場合には,前日(もしくはそれ以前)に軽いメンテナンスを行いEC2インスタンスをめっちゃ増やしておく. Unicornだと同時接続数に限界があるので,テレビのように一気に大量の接続が来る場合には,1台のスペックを上げるよりは台数を増やして接続数を稼ぐ方が効果がでかい.

あと,すごく大事なのはRDSのサイズを,金を惜しまず上げておく. これをやっておかないと,前段のUnicornがどれだけさばいたとしても,RDSの処理が詰まって,Unicornのworkerを食いつぶし,新規接続を確立できなくなってしまう. 実際にこれで何回かやられている.

以下のリソースは,特に増強せずに乗り切れていた.

アプリケーション側での対応

Railsアプリで,NewRelic等を入れていると,普段からそれなりに負荷がかかる場所を把握できていると思う.

そして,たとえば「このページを開くとこのくらいのSQLが発行されて○秒くらいかかる」みたいなページを把握していたとする.

テレビでの紹介のされ方にもよるが,もしこのページにユーザが相当数来る可能性がある場合は,アプリケーション側でここをなんとかしておく必要がある.

考えられるのは,

  • キャッシュに乗せてしまう(放映中にキャッシュのリフレッシュが発生しないようにしておく)
  • 遅延読み込みさせる
  • 一部を非表示にしてしまう

くらいしかない.

キャッシュに載せる

Railsで言うと,ページキャッシュに載せてしまうのが最も負荷が少ない. が,おそらく最近のRailsはフラグメントキャッシュを使うのが一般的だが,どっちでもいい.

とりあえず,重そうなSQLで,今すぐに改善不可能で,プロダクト的にキャッシュが許容できる部分であるなら,片っ端からキャッシュに載せる. memcachedに載せ替えるだけで,RDB的には相当な負荷軽減になるので,真っ先にやりたい.

注意点としては,テレビ放映中にキャッシュのexpireが来ないように調節する必要がある. これについては後述.

遅延読込させる

これも実際行っていた.

例えば,縦に長いページで,1ページを表示させるのに複数のSQLが発行される場合を考える. 普段であれば,すべてのコンテンツを読んでほしいが,ぶっちゃけテレビから流入してくるユーザが,最下部まで見るわけではない. 大抵の場合は,ページ上部を見て引き返したり,ちょっとスクロールして引き返したりするくらいだ.

なので,こういうページに遅延読み込みを仕込んでおくと,発行されるSQL数を削減できる.

一部を非表示にする

Railsを使っているとページネーションにkaminariを使っていたりすると思う.

で,kaminariはページングのためにデータが全部で何件あるかを把握する必要がある.こうしておかないと,全ページ数が算出できない. そのため,一度countクエリを投げる必要がある. このcountクエリ,データ構造によっては結構重いページも存在している.

先程も書いたが,テレビから流入してくるユーザで,ページネーションしてあるページの最終ページまで見に来る人は稀だ. もちろん,普段は最終ページまで辿れないと,全データを見せることができないのでよろしくないのだが,テレビが放映される数十分〜1時間程度であれば,問題ないのではないか.

この辺はプロダクトの性質等によるが(そこでお金もらってたりするとダメだけどね),特に問題なさそうなページならcountクエリを削減するだけで,それなりに変化はある.

あと,このcountクエリが重いと,そもそもページ自体が表示できなくなるので,表示できないよりはマシだろうという考え方もある. そういうこともあって,こんなのを作っていたわけだが.

qiita.com

また,このcount結果をmemcachedに載せるというのも実際に行っていた対応だ.

当日の仕事

テレビ放映当日は,PCの前で待機する.

リソースの変化を見ながら,足らなそうならインスタンスを足すし,サービスが落ちたりしないようにできる限りのことを行う.

が,実際にはできることはあまりない.

SQLを眺め続ける

たまに SHOW PROCESSLIST をする. で,普段からどういうSQLが発行されやすいかを把握していると,テレビ放映時に詰まっているクエリが殺していいかどうかを判断できるようになる

DBのSQLが詰まってきて,どうしても死にそうな場合には,要らなそうなクエリを殺すという最終手段がある. こうすると,SQLの結果を待っていた接続がエラーを返し,一時的にエラーが増えるが,DBがSQLをさばき始めるとサービスは復活する.

実際に発生したこと

まぁ実際には何回かサービスを落としたことがあるので,何が原因だったかを振り返る.

キャッシュしてるはずなのにSQLが発行されている

各キャッシュのexpireを正確に把握しておくべきだった.

テレビ放映中にキャッシュのexpireが訪れると,キャッシュしたいレベルの重いSQLが発行される. そして,キャッシュというのはSQLが結果を正常に返したら,それをキャッシュしてくれる

つまり,高負荷時に,SQLの実行が正常終了しない場合にはキャッシュは生成されない.キャシュが生成されないと,次のリクエスがまた来てしまい,再びキャッシュがないのでSQLが発行されて……というのを繰り返す. これが連鎖すると,RDBには重いSQLが大量に発行され,実行待ちになる. すると,UnicornSQLの結果待ちのまままち続けて,いずれユーザからのリクエストを受け付けられなくなる.

また,このレベルになるとDB自体の負荷がヤバイレベルに達するので,そもそもサービス継続が不可能になる.

エラーページでSQLが発行される

Railsのエラーページをカスタマイズして,ちょっとユーザに親切なメッセージを出したりするのはよくあることだ.

しかし,うっかりエラーページを豪華にするために,エラーページにSQLを挟んでいたことがあった.

テレビ放映時には,いろんな理由でDBには負荷がかかる. そして,いろんな理由でエラーが起こることもある.

すると,上記のエラーページを表示するわけだが,エラーページでSQLが発行されると,高負荷でエラーが出たにもかかわらず,そのページからSQLが発行されて更にDBに負荷をかけるという負の連鎖が発生する.

これはマジで絶対にやってはいけない.

まとめ

アプリケーションサーバは台数をいくらでも増やせるが,DBはどうしてもネックになってしまう.だからたいてい落ちるのは重いSQLが大量に発行されて死んでいた.

CrowdWorksは,サービス的に一連の処理を確実に実行することに重きを置いており,同時に大量のアクセス(テレビレベルの)をさばき続けるようなことを念頭に置いて作られてはいなかった. そのため,あまりテレビ放映に向いているサービスではなかったと思う(アーキテクチャ的に). なので,結果としてどうしてもSQLで詰まってしまうのは避けられなかった.

あと,今回書いたようなのはまじで小手先での対応でしかなく,本気で改善しようと思うのであれば,そもそも重いSQLをとことん駆逐していくのが最も効果的である.

ただ,流石に何もしなかったわけでないので,そういう部分は相当数改善されて,後半はとくにテレビでも日和ることもなかったと思う. いや,上場当時よりはテレビに出る回数が減ったのかもしれないが.