memo: ChatWorkのScala採用プロダクト “Falcon” リリースまでの失敗と成功の歴史 #ScalaMatsuri
- 去年末にリリースできたFalcon その経緯
- Scala歴6年くらい ChatWorkのアーキテクトを担当
アジェンダ
- チャットワークの説明
- Falconの歴史
- プロダクト開発
- プロジェクト再起動
- devops
- 最終的にどうなったか
チャットワークとは
- タスク管理のできるビジネスチャット
- 導入企業 12万社
導入地域 205
データの規模 1年間に生成するデータ量
- 1年前10億メッセージ
- 去年末 18億メッセージ
どういう経緯で開発されたか
- 発端は2010 社内プロジェクト PHPで開発されていた
- 内製のフレームワーク
- ユーザーファーストで技術的な負債を抱える
- 現行のシステムではデータやロードに耐えられなかった
技術的負債
- システムダウン
- 納期の遅延
SPoF
現行チームががんばってきたけどすべて対症療法的
作り直しが決定した。が、そんなに簡単じゃない
再実装→言語選定
- いくつかの言語を選んで、Scalaが選ばれた
- AWSで運用するとなるとJVM言語がよい
- チャットのリアルタイム性にも相性が良い
- PHPエンジニアが多いけど訓練すれば実用に耐えれるレベルまで書けるようになるであろう
- そのタイミングでかとじゅんさんが入る
- 2014年7月ごろ
ライブマイグレーションプロジェクト
数年の歴史を持つチャットワークをどうやって再実装するか
安定した言語システムを使うこと
- 既存システムを極力変更しない
- 無停止で移行すること
- データについてマイグレーションしない
- 今のシステムで提供しているものはそのまま
わりと広範囲なリプレイス
- Falcon Team 作り直しチーム 8人
- Phoenix Team 運用チーム 5人
- iOS Team iOSアプリチーム 6人
採用をがんばってチームを増やした
機能スコープ
- spray/akka/Dynamo
- 腐敗防止層を用意して、旧システムと新システムでやりとりする
- ニューバージョンは新APIで、旧バージョンは旧APIをつかう
IOイベント(ドメインイベント) 起こったイベントをSQS経由で旧システムに渡す
新システムチーム、旧システムチーム、iOSチームとでコミュニケーションがしんどい
- 下流は上流の成果を保証できない
- 上流から下流へ遅延が伝搬する
- 何階層にもつながる関係性なのでコミュニケーションがつらいしプロジェクト全体を把握するのも難しい
発生した様々な問題
- Scala自体には問題ない
- スコープの問題 運営の問題
- 結合テストが難しい
- 本番との結合テストが遅れる
苦渋の決断
- 当初半年の予定がどんどん遅延していく
- 2016/1 プロジェクトをいったん停止する決断
- 振り返りをして立て直しを図る(3日かけた)
- 良い結果もあった(ネガティブになりがち)
- 自分たちの挑戦の大きさ、優秀なエンジニアで良いチームを作ることができた Akka/DDDのプラクティスを得ることができたこと
プロジェクトの再起動
- 基盤となるシステム
- 堅牢なアーキテクチャを作ることを考える
- 技術課題
- POCがワークするか確認すること(この確認はスタートアップではやりにくい)
ただしPOCは難しい
機能要件に加えて、非機能要件を具体化すること
- 現行の100倍さばけるようにしたい
POCはパフォーマンス(スループット/レイテンシ)を中心に
急速に増加するデータについて根本的に解決しないといけない
- ダウンタイムの許容 データマイグレーションをやる
POCブートキャンプ
- proof of oncept
- POCの開発の方向性
- 高スループット低レイテンシ
- レジリエンス
- 障害が起きたら自己回復できるシステムを作る
- DDDは継続的に採用して保守性を維持すること
- コスト
- ここが重要
- 低コストであること システム規模とコストの相関が1000未満
- POC
- AWS
- CQRS
- Akka
- イベントソーシング
リスクヘッジのための検証
- コンセプト的にワークするかどうかを検証
- チャットの特性を鑑みて、writeよりreadのほうが多いのでcqrsを採用
- akka persistence/akka persistence-query
- cqrs write api/read api
- ヘキサゴナルアーキテクチャ
- デプロイはConductR
- WriteDB Cassandra
- ReadDB Aurora
- 負荷試験をやる
細かいアーキテクチャ
- APIはakka http上で実装
- DB 書き込みはCassandra
- DB 読み込みはAurora
- ストレージの分離
- skinnyをakka streamでラップ
back presureつきのakka stream
write側はakka cluster 透過性が発揮できる
クラスターシャーディングを行う
actorは1個のエンティティ
- create message/update messageを受け取って、actorが状態を変更する(akka persistenceの一機能)
- 非同期に読み込み用のテーブルに書き込む
- 受け取ったリクエストからステートレスにデータを返す
POCの結果
- Cassandra x 3
- Aurora x 3
- スループット 3ノード2000ユーザ
30msの驚異的なパフォーマンス
運用の観点から考えると短期的なプロダクションのサービスレベルまでもっていくのは難しいのでakka clusterは見送り
- akka clusterが必要な場合はDCが3つ必要になるので、障害に対応するのが不可能
stateful actorまでは要求していない
- 運用コストを考えても厳しい
- ほかで代用できる
cassandra: オペレーション上の問題 障害の発生したノードの再生成に24時間がかかる
- Aurora: 書き込みのパフォーマンスに不安があってスケールしにくい
- シャーディングしても開発と運用のコストが馬鹿にならない
- 運用を間違えると大事故につながるので限られた人間しか触れない
プロダクション開発
- write db Kafka ← Cassandra
- read db HBase ← Aurora
チーム構成(2016/7)
- Falcon 4名
- Data Migration 1名
- Sparrow 3名(Falconとの連携)
Infrastructure
ストレージは変更したけどCQRSは継続
- Kafka/HBase/Akka/Spark
- Kafka→ドメインイベント→HBaseのリードモデル
- PHP側で処理が終わっていないので通知する必要がある
Kafka→Sparrow→HBase
コミュニケーションの齟齬がへった
- スループットが現行の37倍さばけている
- データマイグレーション Aurora→HBase
- 移行は基本マイグレーションと差分マイグレーション
- 基本 4日前のデータをすべてマイグレーションする
- 差分 前回のマイグレーションとの差分(binlog)を入れる
- HBase/Auroraのデータをツールを使って一致しているかどうか確認する
- パフォーマンス 16億メッセージを要件通り処理できた
depops
- kube-aws Kubernetes
- 開発者がいた
- スケーラブルなクラスタが作れた
- CI Jenkinsからconcourse Pivotal
- ビルドしてチャットワークに通知する
- concourseのタスクとして実装する
最終リリース
- 2016/12/29 深夜
- リリース後のパフォーマンス 高スループット低レイテンシを実現(でも微調整は必要)
まとめ
- 紆余曲折を経てリリースできた
- Scalaじゃなくてプロジェクトの運営とか検証の進め方が問題
- Akka/Kafkaがないと実現できなかった
- 良いアーキテクチャが作れた