#tv6 開発エンジニアが知っておくべきシステムの負荷対策をGunosy/Sansan/Supershipの3社が語った勉強会まとめ!
Gunosy/Sansan/Supershipの3社がシステムの負荷対策について語る勉強会「【Gunosy/Sansan/Supership】開発エンジニアが知っておくべきシステムの負荷対策」に参加してきたのでその時のまとめです。
進化の読めないシステムの負荷対策
Sansan株式会社
永井様
- 各種データ
- 1日30万枚をデータ化
- 400万タスクを処理している
- 現在1億枚以上の名刺がある
- 600万PV/日
- 夕飯時間がピークだが、特定時間に集まることはない
- 1600人が同時に使っている
- ひとつのタスクは5秒程度で利用
- 100req/secきている
- ruby railsを使っている
- 画像処理系はC++とC#を使っている
- EC2は20インスタンスで3つのRDSを使用している
負荷対策の目的
- 増えるタスクに対して各種処理がスケールできることが必要
- オペレータのUIの応答性能の維持向上
DBシャーディング
- サービス開始前は名刺入力サービスは前例ないので予想がしづらかった
- 設計当初はシャーディングできればうまくいくとおもっていた
- 1年以上経過してスロークエリが気になりだしたが、シャーディングしたくはなかった
- 正しい選択ではないとわかっていた
- 蓄積されたデータは検索するだけではだめで、活用法は多様化していた
- 当時はMySQL5.5でオンラインでインデックス追加できなかったのが課題だった
- シャーディングすることでこれらの課題は解決しない
データの性質
- 名刺のデータはデータ化中データとデータ化完了データの2つある
- データ化中データは参照したり更新したりする
- データ化完了データは集計したりする
- 用途だったり性質が具体的になれば、データ化中はこれまでRDBでやり、検索はCloudSearchにしたりなど解も選べる
- 設計するには十分な情報が揃わないのであれば、作りこみをしない
- 十分な情報とはデータの状態の変化とそれに伴う使われ方
- シンプルなクエリで作りこめば1億行でも捌ける
Microservices
- データ化は複数の工程で成り立つが、各種工程をワークフローとして実現した
- SimpleWorkflowを採用し、工程を独立したアプリケーションとして作った
- 拡張、保守がしやすかった
- DBを意味のあるまとまりで物理的に分割できた
スケーラビリティの確保
- 大事なこととしてはできるだけ複雑にせず、小さく作るのを常に意識する
- “正しいプログラムを速くする方が、速いプログラムを正しくするよりも、はるかに、はるかに簡単だ。”
UI応答性能の維持、向上
クエリチューニング
- スロークエリの監視とクエリチューニング、サービス応答性能のモニタリングをやってコツコツ改善した
- クエリの把握
- O/R mapper使っていると発行されているクエリがわからないので、把握するようにしよう
- クエリは適切か
- N+1問題はjoinで解決できないか
- 30件の情報を取得するのに31回クエリ発行していないか
- gemを使ってこの問題は検出している
- 重たい複雑なクエリは分割
- 一つの大きな思いクエリを複数の小さな軽いクエリに分割
- 例えばID取得とデータ取得のSQLにわけたりなど
- ネクストキーロック
- isolation levelはrepeatable readのお話
- プライマリインデックスで範囲検索をするさいに、必ずしもロックしようとしている行がロックされるとは限らない
- lockwaitタイムアウトがでればいいが、lockwaitが積み重なるのはよくない
- 複合インデックス順番
- はったインデックスが本当に適切であるかを気にしていること
- a,bの順番で複合インデックスをはったとき、b=xだけの条件にインデックスは効かない
- a=x and c=xはaのみのインデックスと同じ効果がある
- 範囲検索を含む場合はa>x and b=xとやるとa以降のインデックスが使われない
監視
- スロークエリの監視と対応
- 0.5秒で監視している
- 監視したクエリをチューニングする時に気にしていることは、1日で見た時に総時間が大きい物を優先的に対応している
- 1つのSQLで60秒かかっているものよりは1回0.5秒だけど1日10000回発行されているものなど
- サービス応答性能のモニタリングはNewRelicを使っている
- メソッド単位、クエリ単位、どこのレイヤーで時間がかかるのかを把握できる
- これからはネットワークがネックになっているので無駄な通信を排除する
- Assetsにミニファイルと圧縮はできているが、それ以外のことをやる必要がある
まとめ
- 正しく把握して設計、実装する
- 設計に自身がなければ小さくシンプルに作る
- SQLはポイントを抑えてチューニング
- サービス応答性能を可視化してピンポイントにチューニング
DSP「ScaleOut」の成長と負荷対策
Supership株式会社
石橋様
- 広告枠をオークション形式で購入する広告配信プラットフォームのお話です
- DSPはリクエスト回数がとんでもなく多い
- レスポンスの制約がすごく厳しい(0.1秒以内に返答を返さないといけない)
- ピークタイムはインターネットのトレンドと同じで20:00-24:00
- 使っているのはc++,js, nginx, apache, kvsなどなどつかっている
- 広告配信では基本的にはRDBは使わず、KVSを使う
インフラの変化
- 全体的にオンプレ
- 配信サーバは48台でサービス開始したが、現在配信サーバが500台、集計サーバは150台強になった
- 最初は8億req/monthだったものが今では2000億強req/monthのリクエストをさばいています
- QPS(1秒辺りにうけているリクエスト数)は70000req/sec
- 売上も数十億/yearになった
- システム構成は基本的に構成はそんなに変わらず、ずっとスケールアウトしてきている
- nginx -> adserver -> 転送サーバ->HDFSといったような構成
- 初期設計が大事
問題
- サーバの熱問題
- 最初ははんだこてでサーバを作ったりしていたが、排熱性能が悪く、ピークタイムになるとcpuのクロック数が下がっていたりした
- どうやってサーバを冷やすかを議論したが、教訓としては自作はしないほうがいい
サーバは急に足せない問題
- オンプレだとクラウドより安く調達できるがすぐに増やせないので作りで改善した
- リクエストが急に増えてきた時の基本的な戦略は、世の中のリクエストはたくさんきているので、売上に意味のないリクエストから順番に削ったりしていった
- ある程度自由に制御できるトラフィックの確保をした
- SSPというサーバから送られてくるリクエストが多いので、頼み込んでリクエストを止めてもらったりもした
- オークションに参加する枠や人には優先順位をつけておく
- 広告クリックしない人やほぼ買ってくれない枠などは処理をせずに買いませんと返したりした
- 処理時間が60msを超えたら処理を打ち切って買いませんとした
- ユーザが登録する話だが、ターゲティングが厳しくて渋谷の会場に来ている人に広告を出すといった時は、全部を処理せず時々さぼったりした
- ユーザが案件を無駄にたくさん登録できないようにした
- 前処理のところでandroidのリクエストで、iosの広告だったら処理しないなどにした
- 500台もあるとキャッシュがきかなくなるので、特定のユーザは極力同じサーバで処理するようにした
- オークションなので価格決定はコアな機能だが、高度にしつつもシンプルに保った
集計の問題
- 無駄に付加をかけないようにした
- ResourceManagerを用いてHadoopクラスタで処理をさせると遅れることがあるのでqueueを分けたりした
- 優先度によってはクリティカルタスクで動かしたりもした
- 毎日データとしては4Tくらいたまっており、正直にクエリをなげると帰ってこなくなるので、サンプリングしたデータを用意して処理したりしている
- uu数を出す場合は、単純にサンプリングするだけでは出せないが、ユーザを何個かのグループに分割するようにして集計する
まとめ
- 負荷対策は重要というか必須でやらないとサービスが止まるので頑張ってます
- 利用者が増えると負荷は勝手に上がるので定期的に対策が必要になる
- 重要なのは対策に開発リソースを一定に保ちながらやる
- 広告業界は競争が激しいので競争力がないプロダクトは淘汰されます
- 極力新しいサービスや機能に割り当てられるようにしましょう
Gunosyの負荷アクセスを支える
株式会社Gunosy
松本様 @y_matsuwitter
- 1100万DL突破しました!
2013年Ruby時代のGunosy
- シンプルなWebサーバ群だった
- nginxの後ろにRailsを使っている形だった
- iosアプリを出したところでピークアクセスに耐えられなくなった
- pushがくるときにアクセスに耐えられない
- rubyを用いて出来る限りサーバを小さく、コストをかけないように設計した
- ユーザの増減は線形だが、サーバを小さくしたほうが小刻みにスケールできるのでコストが小さい
- t2.smallやt2.mediumをメインで使っている
- DBに対するIOから疑い、キャッシュの確認、FWのオーバーヘッドがないかを疑っていく
- DBのIOを減らすためにRedisやVanishなどのキャッシュをいれた。
- RDBの中身をRedisへいれて場合によってプリセットする
- フレームワークのオーバーヘッドが多かったのでSinatraを組み合わせてカバーした
- Rackアプリを直接書いてオーバーヘッドを削減した
- 1台あたり100qpsになった
2014年TVCM
- 今後の展開に対して性能が大きく不足していた
- 当時の性能は200/sec
- 大幅なUI変更も行った
- 配信するデータ量の増加があり、ユーザが見れる記事数が数倍以上になった
- 配信頻度、データのトラフィックも増加
- 1000req/secくらいの性能は欲しかった
- スケールは出来なくはないが、コストがかかる
- 費用かけた時にサーバが増えた時に運用するエンジニアがいない
Goの決定と理由
- 選定候補
- Nginx-lua、Haskell, node,jsと比較した
- open resty、Haskellは性能がよかったが、書く人が限られた
- Goは…
- 高パフォーマンス
- rubyと比べて処理性能が10倍
- 非同期処理が楽
- rubyやpythonを書いても使えるのは1coreだが、goは1プロセスでCPUを全部使いきれてメモリ空間も全て使える
- 依存関係は全てバイナリに含まれるのでクロスコンパイルで大体のマシンで動作する
- GoogleやSoundcloudなどで運用実績があった
- githubでのライブラリ増加数が急増した
- goに置き換えたところ、rubyの時と同じ台数で性能がよくなった
1つ上を行く負荷対策
- Goでプロセスキャッシュとchannelが簡単にできた
- DBにアクセスしないAPIが作れる
- シリアライズのコストの削減
- メモリ空間を効率的に使える
- channelの容量を利用することができるので、ハンドラ外のgoroutineでdequeueして処理できる
- データフローに不整合がおきやすいので注意しないといけない
- 書き込みがあった時に返すデータが反映されていないなどがあるかもしれない
- Goとプロセスキャッシュについて
- 1個のgoroutineで大元データのシンクをかきながら、httpはオンメモリのデータを参照して返す
- httpのハンドラ内から排除して、cpuもメモリも効率的
- syncパッケージで安全にアクセスできます
- fluentdで投げる時間もさきたかったのでchannnelでためて、workerで使っているchannelを使ってfluentdに配信しているようなことをしていた
- 一つのインスタンス内であれば似たようなものを作った
2015似非Microservices時代
- チャットで旅行予約できるシステムをつくったりなど、gunosyをplatformとして様々な機能を載せた
- 機能ごとに用いられる要件が違う
- チャットだとリアルタイム性が求められたりなど。
- 画面単位で分割した
- gunosy漫画ではDynamoDBつくったり、Gunosy占いはMySQLと外部APIを使ったりした
- 共倒れを防ぐことが出来た
- 複数のサービスを効率よいアーキテクチャをやるというコストがかかるのでOpsworkで効率化した
- ミドルウェア選定から構築までのコストをGUIで選べるようにできる
- アプリのエンジニアも参加してクラスタ構築したり、メトリックス収集できたりする
大切なこと
- 目標を明確にする
- ボトルネックを見極める
- 問題にぶち当たる前にスケールに必要な技術などを知っておくべき。対策に入ってから調査では遅い。
- 正しいツールを使って対策しましょう
FAQ
- Gunosy: Opsworksで、スケールするとき、基本的にスタンバイさせているがコードに乖離があったときのデプロイはどうするのか
- 時間でピークが決まっているのでタイムベースでインスタンスを用意してぶら下げている
- Gunosy: サーバの構成が変わった時にnginxの設定を変えているのはどうしているのか
- カスタムjsonに設定値をつっこんでいる
- どのバイナリをデプロイをするかなどはルールで決めている
- Sansan: 専用クライアントでrailsに触れているところが気になった。専用クライアントにもっていったほうがいいのでは
- 指摘通りだが今はやっていない
- 入力する対象はガンガン変わる
- アプリケーションにしたところで事前にデータキャッシュするのが難しいのでwebからやった
- 入力のパフォーマンスを上げるところは考えなければいけないところ
- Sansan: 今あるスロークエリの原因となるクエリを特定してリファクタリングするような形にしたのか、システムの設計段階で複雑なクエリを発行しないようにしたのか
- pkでデータをとるようにしたりしたりと、最初からシンプルなクエリを使っていた
- アクティブレコードを使っているので、非正規化もあった
- Gunosy: railsで作ったアプリをgoにリプレースするにあたって大変だったところや、解決ノウハウはないか
- nginxの後ろにrailsをぶらさげていたが、nginxのコンフィグベースでパス毎にだんだんgoに振り分けるようにしていった
- 最初は1台だけリプレースしてデータ不整合がおきないかをチェックしていたりした
- 理想はE2Eテストをやるべき
- UIが必要ないところはgoを使うといいと思う。用途に応じて作るのがオススメ
- セッションの共有で悩んだことがあった
- Gunosy: channelを使った通信でELBで複数プロセスが上がっていて、プロセスの間で通信する場合、goはどういうものを使えばいいか
- サーバをまたいでのchannelのアクセスはしていない
- nsqueueを使ってやったりするところはあるが、redisのpub/subと似たような形
- dockerのだしてるとリブチャンでやるとchannnelをまたいでくれるらしいが運用したことはない
- オススメはrabit-enqueueなどのメッセージキュー系のサービスを使うのがいい
- channelと同じ使い勝手は達成できないと思われる
- Sansan: lockwaitingの検出はどうしてるか
- lockwait timeoutしたときのアプリのエラーで見ている
- Sansan: クエリログのいい出し方は?
- クエリのレビューはしているので発行しているクエリを複数人で見て共有している
- Sansan: ベンチャー的に気になるのは予算管理だとおもうが、「使っていい」などの基準はあったか
- 基準はないが、一枚の名刺をデータ化するのにいくら単価影響あるかでジャッジしている
- コストを下げるのを大事なところだが、サービスを安定的に稼働させるのに注力している
- クオリティとコストとデリバリ、キャパシティ、デリバリなどの観点があるが、コストが一番動かせる
- Sansan: 求めている職種などはあるか?
- Sansan: c#/.netなどいろんな職種を求めている
- Gunosy: マネジメントも含めて全ての業種を求めているが、go書く人募集。
- Supership: 全般的に募集しているが、データマイニングに関わる部分を強く募集している。オークションを最適な価格で決定する必要が有るため
- 設計の正解を導くルールはあるか
- Supership: 問題起こりつつある時に、対処法だけ考えておく
- Sansan: 一人で考えない。複雑なところがあるときは突っ込んで話をする
- Gunosy: データフローを如何に構築するか。うまくデータフローが回る仕組みをつくるべき。ユーザが増えた時にここにDBアクセス貯まると予想したら対策をしておいたりする。初期から負荷対策をするではなく、サービスを伸ばすためのことをするのが大事
@mogmetの所感
microservicesと連呼されるように一つ一つの機能を切り離して作っていくのがうまくスケールアウトさせていくコツなのかと感じました。
LINEでもmicroservicesと連呼していたので大きく成長しているところは設計段階でそういった仕組みがシンプルにできているんだなと思いました。
ものを作る際はできるだけシンプル!microservices!を意識して作ろうと決心いたしました。