Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Chatworkでリアクション機能をリリースした話_scala_ks.pdf

kamegu
October 26, 2019

 Chatworkでリアクション機能をリリースした話_scala_ks.pdf

kamegu

October 26, 2019
Tweet

Other Decks in Programming

Transcript

  1. Chatworkでリアクション機能をリリースした話
    Scala関西 Summit 2019
    Chatwork株式会社
    勝野 徳

    View full-size slide

  2. © Chatwork
    自己紹介
    ● 勝野 徳

    ○ 「めぐむ」って読みます

    ● Chatwork株式会社 サーバサイド開発部

    ○ 2016/08入社 => PHPエンジニア

    ○ 現在はScalaチーム(このPJ期間中に変更)

    ○ 元々はJavaを少々

    ● 趣味はランニング

    ○ 年に1回くらいフルマラソン

    ○ まだガチ勢ではないと思っている

    ● その他

    ○ twitter: kamegu3


    View full-size slide

  3. © Chatwork
    会社紹介
    ● 拠点
    ○ 大阪
    ○ 東京
    ○ リモート
    ● 大阪オフィス、引っ越しました
    ○ https://news-ja.chatwork.com/2019/07/chatwork.html
    ○ 梅田エリア(福島)
    ■ ランチ充実
    ■ 少し(20分)歩けばランニングステーションあり

    View full-size slide

  4. © Chatwork
    Chatworkサービスの紹介

    ● ビジネスチャットサービス
    ● 導入企業235,000社突破!(2019年9月末日時点)

    View full-size slide

  5. © Chatwork
    今日話すこと
    ● 話すこと
    ○ PJの開始からリリースまでを時系列で
    ■ Scala開発の目線
    ■ なるべくありのままに
    ○ つまずいたこと
    ■ 特殊なものが多いかもしれませんが雰囲気だけでも

    View full-size slide

  6. © Chatwork
    目次
    ➢ 要件定義〜アーキテクチャ検討

    ➢ PoC

    ➢ 実装、負荷試験

    ➢ リリース


    View full-size slide

  7. © Chatwork
    目次
    ➢ 要件定義〜アーキテクチャ検討

    ➢ PoC

    ➢ 実装、負荷試験

    ➢ リリース


    View full-size slide

  8. © Chatwork
    リアクション機能
    ● メッセージに対してカジュアルに反応するための機能
    ● 7/17リリース
    ○ 世界絵文字デーに合わせて https://news-ja.chatwork.com/2019/07/chatwork-662617.html
    ○ キャンペーンも
    利用イメージ

    View full-size slide

  9. © Chatwork
    リアクション機能の要件(一部抜粋: サーバサイド関連)
    ● メッセージにリアクションできる / 削除できる
    ○ 1メッセージに対して1人1種類だけ
    ● 常にメッセージとセットでリアクション情報を返す
    ○ Room内で連続する1つ以上のmessage
    ● ほかの人がおこなったリアクションもほぼリアルタイムで反映される

    View full-size slide

  10. © Chatwork
    リアクション機能の非機能要件(一部抜粋)
    ● 性能
    ○ 少なくとも5年後想定まで耐えうる
    ○ スループット
    ■ GET: メッセージ取得と同程度
    ■ PUT: メッセージ書き込みの1/10程度を想定
    ○ レイテンシ
    ■ (Scala部分で)GET:30ms以下、PUT:50ms以下
    ● 運用
    ○ コスト(インスタンス、データストア等)
    ○ 障害耐性

    View full-size slide

  11. © Chatwork
    アーキテクチャを考えるにあたって
    ● 既存のChatworkのアーキテクチャに依存
    ○ リクエストをPHPで受ける(認証や権限チェック)
    ○ メッセージ保存/取得はFalcon(Scala製サブシステム)へリクエスト
    ○ 更新通知(リアルタイムでwebクライアントに反映させる)はPHP
    REACTIONはどこに入れる??
    Pusher
    更新通知 Falcon(かなり省略)

    View full-size slide

  12. © Chatwork
    今回のアーキテクチャ設計におけるポイント
    ● Scala vs PHP
    ○ PHPは通らざるを得ない
    ○ マイクロサービス(Scala) vs モノシリック(PHP)
    ■ 工数比較で言えばPHPが有利
    ● データストア
    ○ RDBMS or KVS
    ○ Managed or not

    View full-size slide

  13. © Chatwork
    アーキテクチャ検討(Scala vs PHP)
    ● ChatworkとしてはScalaを推していきたい
    ○ 実際はまだまだPHPで動いてる物の方が多いが
    ○ 出来るだけScalaでやることに意味がある
    ● マイクロサービス的にすることのメリットもある
    ○ スケーラビリティ
    ○ 障害の考え方
    ● 結果 => Scala案採用

    View full-size slide

  14. © Chatwork
    アーキテクチャ(データストア)
    ● 当初の候補
    ○ 社内の運用実績
    ○ RDBMS
    ■ データ量が何年後まで耐えられるか
    ○ HBase
    ■ 運用コスト
    ● 第3の候補
    ○ DynamoDB

    View full-size slide

  15. © Chatwork
    DynamoDBの検討
    ● 候補理由
    ○ RDBMSとHBaseの問題点をクリアしている
    ○ コストが不安だったが、試算してみたら予算内
    ● 問題点
    ○ 高トラフィックな箇所で社内での運用実績なし
    ● => スペック上は問題なさそうだから検証

    View full-size slide

  16. © Chatwork
    アーキテクチャ決定(仮)
    ● Scala (+PHP)
    ○ マイクロサービス的に
    ● DynamoDB

    View full-size slide

  17. © Chatwork
    アーキテクチャ決定(仮)
    ここまでScalaの話なし

    View full-size slide

  18. © Chatwork
    目次
    ➢ 要件定義〜アーキテクチャ検討

    ➢ PoC

    ➢ 実装、負荷試験

    ➢ リリース


    View full-size slide

  19. © Chatwork
    PoC
    ● 目的
    ○ プロトタイプ実装
    ■ GET
    ■ PUT
    ○ 全体の性能
    ○ DynamoDBの検証
    ■ 性能
    ■ 特性

    View full-size slide

  20. © Chatwork
    PoCシステム構成
    ● Scalaアプリケーション
    ○ Akka HTTP
    ○ AWS SDK for Java 2.0 (DynamoDB)
    ○ Monix (Task)
    ○ Kamon
    ● Dockerイメージ作成
    ○ sbt-native-packager
    ● Kubernetes on AWS
    ○ Falconの時から運用しているものをアップデートしながら (not EKS)
    ● Gatling on ECS

    View full-size slide

  21. © Chatwork
    アプリケーションの構成
    ● Clean Architecture をベース
    ○ だいぶ省略したbuild.sbt =>
    ○ contract-xxxxはInterfaceを定義
    ● 今回は
    ○ ドメイン知識がほとんどない
    ○ もっとシンプルにしても問題ないはず

    View full-size slide

  22. © Chatwork
    AWS SDK for Java 2.0
    ● 2.0からNon-Blocking対応
    ○ netty
    ○ Blocking IOだと色々面倒なので助かる
    ○ AkkaがNon-Blockingベースなので
    ● 1系=>2系ではpackage名も変わっている
    ● アップデートがかなり頻繁

    View full-size slide

  23. © Chatwork
    Monix Taskの紹介
    ● Monix
    ○ high-performance Scala / Scala.js library for composing
    asynchronous, event-based programs. (原文そのまま)
    ● Monix Task
    ○ 極端なことを言えば、Futureをええ感じにしたやつ
    ○ Scalaz.Taskにinspired

    View full-size slide

  24. © Chatwork
    Monix Taskの紹介
    ● Futureと何が違うか
    ○ Futureは処理が開始済み、Taskは遅延評価
    ■ 先行評価やFutureを受け取ったりもできる
    ○ Taskはmap等の処理にExecutionContextが必要ない
    ■ (MonixではExecutionContextを継承したScheduler)
    ■ 最終的に評価するときだけ必要
    ● 特定の処理だけ別のExecutionContextを使用することも可能
    ■ Taskを受け取る/返す関数に対してExecutionContextをimplicitパラ
    メータで渡す手間が省ける

    View full-size slide

  25. © Chatwork
    Monix TaskのリアクションPJでの使用イメージ
    ● Taskインスタンス生成
    ○ repository層
    ○ AWS SDKの呼び出しをwrap
    ○ 遅延評価
    ○ ExecutionContext不要
    import scala.compat.java8.FutureConverters._
    Task.deferFuture {
    dybamoDbAsyncClient.query(queryRequest).toScala // Future
    }.map(convertToReaction)

    View full-size slide

  26. © Chatwork
    Monix TaskのリアクションPJでの使用イメージ
    ● DynamoDBからの取得結果を加工
    ○ usecase層
    ○ ExecutionContext不要
    ● Taskの評価
    ○ controller層
    ○ Schedulerを使用
    val result: Task[Reaction] = repository.xxx() // 上の処理を実行
    result.map(convertToModel)
    def zzz(implicit scheduler: Scheduler) {
    val result: Task[Model] = usecase.yyy() // 上の処理を実行
    result.runToFuture // ここで評価が開始される

    View full-size slide

  27. © Chatwork
    Gatlingの紹介
    ● Scala製の負荷試験ツール
    ● ChatworkではScalaプロダクトの負荷試験でのデファクト
    ○ v2.2
    ○ 全PJでほぼ同じ使い方
    ● sbtで実行可能
    ○ https://gatling.io/docs/2.2/extensions/sbt_plugin/
    ● 実際にsbtで負荷試験しようとすると色々限界がある
    ○ ローカルからだとリソース、NW遅延、アクセス権、etc
    ○ ChatworkではAmazon ECS上で実行する仕組み

    View full-size slide

  28. © Chatwork
    ChatworkでのGatlingの使い方
    ● 指定時間(rampDuration)をかけて指定したユーザ数を追加する
    ● 増え切ったらその後の指定期間(holdDuration)はそのまま
    ● ユーザ数 = リクエスト数/秒
    rampDuration holdDuration
    numOfUser

    View full-size slide

  29. © Chatwork
    ChatworkでのGatlingのコード例
    setUp(
    getByRangeScenario.inject(rampUsers(numOfUser) over rampDuration)
    ).protocols(httpConf).maxDuration(rampDuration + holdDuration)
    val getByRangeScenario = scenario("getByRange").forever(
    pace(1)
    .feed(randomFeeder)
    .exec(getReactionsByRange)
    )

    View full-size slide

  30. © Chatwork
    PoC負荷検証1
    ● 実装ができたから負荷をかけてみた
    ● ある程度は捌けてそうだが、遅い
    ● サーバ側のメトリクスはここまで悪くなかった
    ○ network経路が怪しい
    注:このスライドで使用しているGatlingのレポートのスクリーン
    ショットについて、負荷や検査対象のサーバ構成等の条件が
    異なっています

    View full-size slide

  31. © Chatwork
    PoC負荷検証2
    ● Gatlingがk8sクラスタとは別VPCにいた
    ■ 過去PJのドキュメント通りにやってしまったのが原因
    ● 修正後
    ○ かなり改善
    ■ 200ms => 45ms
    ○ まだ不十分

    View full-size slide

  32. © Chatwork
    PoC負荷検証3
    ● スループットをあげるとレイテンシが悪化する現象あり
    ● これだけ聞くと普通と思うかもしれないが
    ○ 線形で上がりすぎる
    ○ サーバ側のメトリクスでは詰まってる感じはない
    ● 過去のPJでGatlingを使用した際の結果を確認
    ○ 同様の現象は発生していない

    View full-size slide

  33. © Chatwork
    PoC負荷検証4
    ● 今回PJで変えた点を確認
    ○ Gatling
    ■ pause => pace
    ○ 負荷をキープするためにシナリオ内で同じユーザに一定間隔ごとに処理を
    ループさせている箇所
    ● pauseに変更して実行
    ○ 結果 => 劣化してない

    View full-size slide

  34. © Chatwork
    なぜpaceだとダメだったのか1
    ● pauseとpaceは何が違う?
    ○ https://gatling.io/docs/2.2/general/scenario
    ○ pause
    ■ 前の処理が終わってから次の処理に進むまでの待ち時間
    ○ pace
    ■ 一定間隔で実行
    ● 目標負荷で一定にするという目的ではpaceが正しそう
    ○ pauseだとレスポンスを待つ分targetとするスループットまで達すること
    がない

    View full-size slide

  35. © Chatwork
    なぜpaceだとダメだったのか2
    ● ここでキーとなるポイントはinjection
    ○ https://gatling.io/docs/2.2/general/simulation_setup/
    ○ rampUsers(nbUsers) over(duration)
    ■ Injects a given number of users with a linear ramp over a given
    duration.
    ■ 例えば5users - 5secondsとすると
    ● t0, t0+1.0,,,t0+4.0 でinjectされる
    ■ 例えば25users - 10secondsとすると
    ● どうなるか
    ● t0, t0+0.4, t0+0.8,,,,みたいになる?

    View full-size slide

  36. © Chatwork
    なぜpaceだとダメだったのか3
    ● 25users - 10secondsの場合
    ○ 10秒の中で均等に分散するのではなく、まず25ユーザを10個に配分
    ■ 1,3,,9秒 => 3users => t0+t, t0+t+0.333, t0+t+0.667 (t=0,2,,8)
    ■ 2,4,,10秒 => 2users => t0+t, t0+t+0.5 (t=1,3,,9)
    ○ injectされると、すぐ最初のリクエスト
    ● この状態でpace(1 second)とすると同じタイミングに集中する
    ○ msだけ切り取ると、0, 0.333, 0.5, 0.667の4パターン
    ○ それ以外はリクエストが空白の時間
    ○ Gatlingのログを見ると、実際に同じタイミングで送られていた

    View full-size slide

  37. © Chatwork
    なぜpaceだとダメだったのか4
    ● 他の値を指定しても同様
    ○ 原理的には十分短い時間に多くのユーザをinjectしない限り同じ
    ○ 1000users - 1minuteでも32パターン程度
    ● pauseを使うと、レスポンスタイム分の遅延が挟まれるので結果的にいい感
    じに分散されていた
    ○ こういう理由で過去PJでpauseを使っていたかどうかは不明のまま、、、

    View full-size slide

  38. © Chatwork
    なぜpaceだとダメだったのか5
    ● 本来やりたかったことを実現するためにはこうすべきだったのかも
    ○ foreverを外し、リクエストし終わったらそのユーザは終了
    ○ paceやpauseは負荷安定のためには使わない
    setUp(
    getByRangeScenario.inject(
    rampUsersPerSec(0) to numOfUser during(rampDuration),
    constantUsersPerSec(numOfUser) during holdDuration
    )
    ).protocols(httpConf)

    View full-size slide

  39. © Chatwork
    PoC負荷検証5
    ● 結果
    ○ PoCとしては十分な性能が出たと判断
    ■ 最初からシステム自体に大きな問題はなかった
    ■ アプリの修正は小さなもの中心
    ○ DynamoDBの特性にも気になる箇所はなし

    View full-size slide

  40. © Chatwork
    Reaction
    Pocを踏まえたシステム構成案
    Pusher
    更新通知 Falcon(かなり省略)

    View full-size slide

  41. © Chatwork
    目次
    ➢ 要件定義〜アーキテクチャ検討

    ➢ PoC

    ➢ 実装、負荷試験

    ➢ リリース


    View full-size slide

  42. © Chatwork
    設計・本実装
    ● Scala側はPoCの際の実装に機能追加、修正する形
    ○ 結果的にリライトした箇所も
    ○ リクエスト/レスポンスフォーマット(設計から)
    ○ k8s用のendpoint (liveness, readiness)
    ○ logging
    ○ メトリクス
    ● 正直、PHP側がヘビーだった
    ○ アーキテクチャの選択でPHPモノシリックを選んでいても生じたもの
    ○ 逆に全部Scalaだったら楽だったのかもしれない

    View full-size slide

  43. © Chatwork
    密かにリリース
    ● 開発期間中から少しずつ本番環境で使えるようにしていく
    ○ 本番環境構築
    ○ PHPも含めた疎通・動作確認
    ○ モバイルアプリ等の開発時の検証用
    ○ 性能確認
    ● 最初はPJの開発メンバーのPHPリクエストだけScala側にアクセス
    ○ 順次、社内全体まで公開
    ○ 都度 機能追加、修正
    ○ 機能が揃ったところで社内検証

    View full-size slide

  44. © Chatwork
    リリースしてみたものの
    ● レスポンスが遅い
    ● PJの開発メンバーにreadを適用したタイミングで発覚
    ○ (つまり最初)

    View full-size slide

  45. © Chatwork
    レスポンス遅い問題1
    ● PHP側のメトリクスだけでなくScala側のメトリクスでも遅い
    ● DynamoDB側のメトリクスは問題ない
    ○ Scala->DynamoDBへのアクセスが遅い
    ● 調査した結果
    ○ スループットが低い場合に起こる
    ○ PoC時はある程度負荷をかけていたので気づかなかった

    View full-size slide

  46. © Chatwork
    レスポンス遅い問題2
    ● 怪しい箇所にログを仕込む
    ○ Scalaコードはもちろん
    ○ ライブラリ内の.javaファイルもsrc/main/javaへコピーして上書き
    ● AWSのcredentialを取得している箇所が怪しい
    ○ kiamというツールを経由してcredential情報を取得
    ○ このkiam側のキャッシュの設定がSDK側のキャッシュと相性が悪かった
    ○ この設定を調整することで解消

    View full-size slide

  47. © Chatwork
    レスポンス遅い問題3
    ● 安定した
    ○ 山も現れなくなっている
    ● 結果として全体の性能も向上
    ○ 1.5倍程度のスループット

    View full-size slide

  48. © Chatwork
    さらに機能の追加や改善を進めている矢先
    ● credentialを取得できないエラーが発生
    ○ 原因不明
    ● kiamを使う方式だとリスクがありそうということで別の道を探ることに

    View full-size slide

  49. © Chatwork
    credentialどうするか
    ● credential_process
    ○ https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
    ○ credentialのファイルではなく、credentialを標準出力で返すコマンドを
    指定する
    ○ kube-aws-iam-controllerというツールがこれに対応
    ○ AWS SDKはv2.5.26 (2019/4/16)から対応
    ■ v1は(2019/1/24)から対応
    ■ 当時のプログラムでは2.4.0を利用
    ● なのでIDEでライブラリのソースコードを見ても気づかなかった
    ○ SDKをアップグレードして問題回避
    ■ なんともヒヤヒヤするタイミング (この時5/15)

    View full-size slide

  50. © Chatwork
    ちなみに
    ● EKSを使っていればcredentialsまわりでこんなに苦労しなかったかも

    View full-size slide

  51. © Chatwork
    負荷試験
    ● メモリリーク発見
    ○ readとwriteのpodを分けたことで発症
    ○ ヒープダンプ調査
    ○ Kamon 1系
    ■ 詳細な原因特定までには至っていない
    ■ 社内で0系の運用実績があったのでそちらに切り替え
    ● それ以外は大きな問題なく進む

    View full-size slide

  52. © Chatwork
    負荷試験
    ● 各種パラメータも調整
    ○ AWS SDKとかAkka HTTPとか
    ■ ほぼデフォルト値で問題なし
    ■ 値を大きく変えると劣化することもあるが、多少変えるくらいでは性能
    変化ない(逆に上がることもなかった)
    ● コンテナのサイズの調整
    ○ CPUやメモリの割り当て
    ○ 1podあたりのリソースを小さめにしてスケールアウトさせる戦略

    View full-size slide

  53. © Chatwork
    負荷試験
    ● ある程度の時間、ある程度の負荷をかける
    ○ 想定負荷を十分満たせることを確認
    ● 結果 => リリース Go

    View full-size slide

  54. © Chatwork
    目次
    ➢ 要件定義〜アーキテクチャ検討

    ➢ PoC

    ➢ 実装、負荷試験

    ➢ リリース


    View full-size slide

  55. © Chatwork
    いざ、リリース
    ● サーバサイドは3週間前から少しずつ公開
    ○ 本番環境への負荷を増やし、万が一問題があった場合に備える
    ○ 1% => 10% => 30% => 100%
    ○ 2週間前には本番相当の読み込み負荷
    ○ フロントエンドやモバイルアプリのリリースを待つ状態

    View full-size slide

  56. © Chatwork
    リリース当日
    ● 7/17 朝リリース
    ○ フロントエンド
    ○ モバイルアプリ
    ● メトリクスを眺める
    ○ Writeリクエスト数がどんどん増えていく
    ○ CPU等は安定
    ● 安堵の時
    リアクション数/日の推移(リリースから1ヶ月)

    View full-size slide

  57. エンジニア募集してます
    Scalaエンジニアはもちろん、
    PHPやモバイルアプリやフロントエンドのエンジニアも
    https://corp.chatwork.com/ja/recruit/

    View full-size slide

  58. © Chatwork
    感想
    ● MonixとかZioとか色々あるけど、結局何がいいんだろう
    ● 負荷検証むずかしい
    ○ ドキュメントに残し切れないノウハウ
    ● 時間の都合上、割愛した内容もあって何を残すか悩みました
    ○ 例えばデータストアの検討や設計の話
    ○ Gatling on ECSの詳細とか
    ○ 問題解決に向けて色々試したりもしました
    ○ 興味ある方はぜひ

    View full-size slide

  59. © Chatwork
    まとめ
    ● Scalaは十分速い
    ○ DynamoDBも十分速い
    ● 新しいものにするといいことあるかも
    ○ ライブラリのアップデート
    ○ EKSのような新しいサービス
    ○ その逆も然り
    ● ChatworkではScalaエンジニアはいろんなことに詳しくなれます
    ○ kubernetesとか
    ○ どこまで深入りするかは自分次第

    View full-size slide