Save 37% off PRO during our Black Friday Sale! »

月間数十億リクエストのマイクロサービスを支える
 JVM+AWS フルサーバーレス開発事例 / Now and Future of Fully Serverless development at Chatwork

月間数十億リクエストのマイクロサービスを支える
 JVM+AWS フルサーバーレス開発事例 / Now and Future of Fully Serverless development at Chatwork

2021-7-14 そろそろマネージド、クラウドネイティブで移行!
https://aws-serverless.connpass.com/event/214358/

https://speakerdeck.com/exoego/how-chatworks-uses-scala-and-serverless のアップデート版です。

C0eb1445cda9489ebf8c31c367fec3fb?s=128

TATSUNO Yasuhiro

July 14, 2021
Tweet

Transcript

  1. 2021-7-14 #aws_serverless そろそろマネージド、クラウドネイティブで移行! Chatwork株式会社 プロダクト本部 プロダクト基盤開発部 立野 靖博 月間数十億リクエストの
 マイクロサービスを支える


    JVM+AWS フルサーバーレス開発事例

  2. • 立野 靖博 • Twitter & GitHub: @exoego • サーバーレス歴4年目

    ◦ Serverless Framework コミッター • 近況 ◦ 庭いじりでイチジクを植えました ◦ 監訳した Scala の技術書が発売 !! 自己紹介 2/39
  3. サーバーレス・スペクトラムと Chatwork 3/39 http://bit.ly/3bIT7PX Carmen Puccio et al. を引用。白三角は Chatwork

    で使用している AWS 本日のお話
  4. アジェンダ 1. 開発事例「リンクプレビュー」の紹介 2. サーバーレスアーキテクチャをどうして選んだか 3. 具体的なサーバーレス構成 4. Java/Scala で

    AWS Lambda を活かす工夫 5. Chatwork におけるサーバーレス開発の今後 6. まとめ 4/39
  5. Chatworkは10周年を迎えた日本最大級のビジネスチャット 3月 リリース 29.6万社 突破! 20万社 突破! 導入社数32.1万社を突破! (2021年6月末日時点) 10万社

    突破! 5/39 10年の歴史が あとで効いてきます
  6. 1 Chatwork フルサーバーレス開発事例 リンクプレビュー 昨年 2020年5月27日リリース

  7. Chatwork のリンクプレビューはこんな機能です 7/39 ①チャットに URL が含 まれていると ②タイトル、画像、ファビコ ンを自動で表示 投稿した人

    見る人 共有したいページのタイトルや 内容を入力する手間が減る 開く前にどんなページか分かり開 くかどうか判断しやすくなる
  8. プレビューする情報は HTML から取得しています • URL をフェッチし、その HTML に埋め込まれた OGP 規格

    のメタデータを取得しています • OGP は 2010年に Facebook が公開 https://ogp.me/ • 類似の規格に Twitter Card などがあります(現時点では 未対応) 8/39 <meta property="og:type" content="website" /> <meta property="og:site_name" content="connpass" /> <meta property="og:title" content="[Online]nakanoshima.dev#14 JVM Langs Night Talk" /> <meta property="og:url" content="https://nakanoshima-dev.connpass.com/event/204733/" /> <meta property="og:image" content="https://connpass-tokyo.s3.amazonaws.com/thumbs/(略).png" /> <meta property="og:description" content="# 開催日時 2021/3/12(金)...(略)" /> Open Graph Protocol
  9. 同様のプレビュー機能は SNS やチャットツールでよく見られます 9/39 Slack Twitter Discord

  10. 日常的に見る「あたり前」機能、作る側にはけっこう悩ましい • 何をどうプレビューするか? ◦ どんなサイトをどんな風にプレビューできるとユーザーの業務が捗る? ◦ OGP 以外の規格は? メタデータはないけど有用なサイトは? ◦ 1メッセージに複数

    URL が含まれていたら、どこまでプレビューする? ◦ プレビュー取得に時間がかかる場合、チャットの表示はどれくらい遅れ てよい? • 性能やコストは? ◦ ユーザーがバンバン URL を共有しても安定・高速にさばける? ◦ 膨大なリクエストをさばく大量サーバーのコストは? • 外部の Web サイトに迷惑かけないアクセス方法は? ◦ Web サイトに robots.txt があったら? ◦ Web サイトがエラーを返したらリトライする? その間隔は? 10/39 本日の お話
  11. 2 サーバーレスアーキテクチャを どうして選んだか

  12. Chatwork バックエンドの構成(2020/1当時) 1. PHP 系サービス ◦ 2011年から続く主要サービス(チャットルーム、権限、通知…) ◦ さまざまなバッチ処理、ストリーミング処理… 2.

    Scala 系サービス ◦ 2016年に PHP の一部を置き換え、併用して新規開発。 ◦ 一部主要サービス:メッセージング(2016)、検索(2020) ◦ 新規の周辺サービス(OAuth、Webhook、監査ログ…) 12/39 2011: PHPで開発スタート 2016: メッセージング Scala化 PHP と Scala 併用で開発継続
  13. バックエンド開発チームの懐事情(2020/1当時) • PHP チーム ◦ 10年の歴史あるレガシーコードなので開発も一苦労 ◦ 主要サービスだけあって重要な案件が多いので、いっぱいいっぱいになりがち • Scala

    チームは新規の周辺サービスに注力 ◦ 現行アーキで Scala で主要サービスの担当範囲を広げるには、PHP と Scala を連携させる基盤整備する必要があった。 ▪ 新アーキで全面刷新予定のため、2〜3年で捨てる基盤整備は避けた。 ◦ 周辺サービスは Scala のおかげで堅牢で機能追加も少なく、余力があった • こうした事情から、リンクプレビューはなんとしても Scala チームが巻き取りたい 13/39
  14. Chatwork リンクプレビューのアーキテクチャ検討 リンクプレビュー チャットルーム 1. メッセージ投稿前にプレビュー要求 2. プレビュー作成 3. プレビュー取得

    4. プレビュー付メッ セージ投稿 6. プレビュー付 メッセージ取得 5. メッセージ要求 リンクプレビュー チャットルーム 1. URL入りメッセージを投稿 4. プレビュー作成 6. プレビュー付 メッセージ取得 2. メッセージ要求 3. プレビュー要求 リンクプレビュー チャットルーム 1. URL入りメッセージを投稿 3. メッセージ取得 5. プレビュー作成 6. プレビュー取得 4. プレビュー要求 リンクプレビュー チャットルーム 1. URL入りメッセージを投稿 3. プレビュー作成 5. プレビュー付メッセージ取得 (作成中ならプレースホルダー) 4. メッセージ要求 2. プレビュー 作成要求 バックエンドが密結合 バックエンドが疎結合 遅延影響 小 ガタツキ 有 非 同 期 的 や や 同 期 的 遅延影響 大 ガタツキ 無 ① ② ③ ④ バックエンド開発負荷 大 フロントエンド開発負荷 小 バックエンド開発負荷 小 フロントエンド開発負荷 大 2. メッセージ要求
  15. Chatwork リンクプレビューのアーキテクチャ検討 リンクプレビュー チャットルーム 1. メッセージ投稿前にプレビュー要求 2. プレビュー作成 3. プレビュー取得

    4. プレビュー付メッ セージ投稿 6. プレビュー付 メッセージ取得 5. メッセージ要求 リンクプレビュー チャットルーム 1. URL入りメッセージを投稿 4. プレビュー作成 6. プレビュー付 メッセージ取得 2. メッセージ要求 3. プレビュー要求 リンクプレビュー チャットルーム 1. URL入りメッセージを投稿 3. メッセージ取得 5. プレビュー作成 6. プレビュー取得 4. プレビュー要求 リンクプレビュー チャットルーム 1. URL入りメッセージを投稿 3. プレビュー作成 5. プレビュー付メッセージ取得 (作成中ならプレースホルダー) 4. メッセージ要求 2. プレビュー 作成要求 バックエンドが密結合 バックエンドが疎結合 遅延影響 小 ガタツキ 有 非 同 期 的 や や 同 期 的 遅延影響 大 ガタツキ 無 ① ② ③ ④ バックエンド開発負荷 大 フロントエンド開発負荷 小 バックエンド開発負荷 小 フロントエンド開発負荷 大 2. メッセージ要求 バックエンド開発期間 大 既存の機能の改修、 PHP⇔Scala 連携基盤の整備、 PHP チームが他の主要プロジェクトを いくつも手掛けていて余力ない … ユーザー体験のリスク URL 含んだメッセージがあると数秒遅延、 プレビュー作成失敗時のユーザーフローが複 雑、etc… 迅速なバックエンド開発 Scala チーム単独での新規機能開発、 別プロジェクトで検証していた サーバーレスの恩恵が大
  16. 3 具体的なサーバーレス構成

  17. • ざっと必要なもの ◦ エンドポイント(Web サーバー) ◦ ビジネスロジック(アプリサーバー) ◦ プレビュー用データやログを保存(DBサーバー、ファイルサーバー) •

    どういう台数や構成ならスケーラビリティ出せるか、etc… ◦ 当初、コンテナを検討したが、サーバーコストでキビシイ試算 チャットルーム 1. URL入りメッセージを投稿 3. メッセージ取得 2. メッセージ要求 ゼロからの新規開発で “サーバー” でやること、めちゃ多い リンクプレビュー 5. プレビュー作成 6. プレビュー取得 4. プレビュー要求 ② 17/39 ここの具体化
  18. サーバーレスなフルマネージドサービスがピタッとはまった CloudFront
 外部サイト API Gateway
 DynamoDB
 Lambda
 18/39 チャットルーム 1.

    URL入りメッセージを投稿 3. メッセージ取得 2. メッセージ要求 リンクプレビュー 5. プレビュー作成 6. プレビュー取得 4. プレビュー要求 ② ここの具体化
  19. Lambda: 毎月数億 URL を高速・安価にさばける ”サーバー” CloudFront
 🌏外部サイト API Gateway
 DynamoDB


    • Lambda関数: 入力(今回はHTTPリクエスト)に対し、出力(プレ ビューに必要な JSON や画像)を返す • 同時に何千〜何十万リクエストでもさばけるスケーラビリティ ◦ リンクプレビューでも毎月数億 URL を安定してさばけている • 性能x処理時間で課金。処理してない間は課金されない ◦ メモリはわずか 256 MB(あとで詳しく) ◦ 従来のサーバー起動しっぱなしと比べて超安い Lambda
 19/39 外部サイト
  20. 一般的なサーバーのスケーラビリティとそのコストの課題 • サーバー1台が使用可能になるまで数分かかる(起動数十秒+JVM暖機数分) • できるだけ遅延させないように、ある程度余裕を持って多めに起動しておく • 処理していないアイドル時間もコストは発生し続ける 20 時間→ 予測していない

    スパイク 同時アクセス数 サーバー
  21. AWS Lambda のスケーラビリティとコスト • Lambda関数は最短、数百ミリ秒オーダーで起動(あとで詳しく)し、数十分アイドル • デフォルト 1,000、申請すれば数十万〜のスケーラビリティ • イベントを処理中の時間だけ課金され、アイドル時間は課金されない

    21 時間→ すばやく起動できる小さなサーバーが アクセスの増減に柔軟に対応する
  22. API Gateway: Lambda を Web API として外部公開 22/39 CloudFront
 🌏外部サイト

    API Gateway
 DynamoDB
 • URL パス/メソッドと、データの送り先(今回は Lambda)をひもづけ ◦ POST /link-preview → HTMLをパースして OGP を解析する関数 ◦ POST /ogp-image → OGP にひもづいた画像を取得する関数 • API Gateway 単独でも URL がふられるので利用できるが、作り直すと サブドメインが変わってしまうのがちょっと困る ◦ CloudFront(このあと説明)を前段において URL を固定 Lambda
 外部サイト
  23. DynamoDB: 同じ URL への同時アクセス防止に使用 23/39 CloudFront
 🌏外部サイト API Gateway
 DynamoDB


    • 毎分何万リクエストもさばけて Lambda と相性よい DB • 「このURL処理中」といったアイテムを書込(PutItem) して、排他制御(楽観ロック)に利用 • DynamoDB の書込コストを減らす工夫 ◦ PutItem に ConditionExpression をつけて、すで に排他制御中のときは書込キャンセル(無料) ◦ Time-To-Live 機能を利用して、不要になった排他 制御アイテムを自動で削除(無料) Lambda
 外部サイト
  24. CloudFront: リンクプレビューの保存&高速な配信 CDN DynamoDB CloudFront
 🌏外部サイト DynamoDB
 • 同一 URL

    のリンクプレビューを一定時間キャッシュ ◦ キャッシュなし 平均 1.00 秒 → あり:平均 0.02 秒 • キャッシュがある間はアクセスしない=外部サイトへの配慮 • CloudFront より後段のリソースの利用コストを数十分の1に削減 • デスクトップ or モバイル、ユーザー言語に合わせたキャッシュ。URL に加え、HTTP ヘッダーなどもキャッシュキーにできるため。 • WAF と連携して DDoS 攻撃などにも耐える。 • 画像などもキャッシュできるので、本サービスでは S3 未使用! API Gateway
 Lambda
 キャッシュ前 キャッシュ後 24/39 外部サイト
  25. 4 Java/Scala で AWS Lambda を活用する工夫

  26. プログラミング言語 Scala とは • 来歴 ◦ 2003年、Martin Odersky 博士らが OSS

    として開発 Java 5 ジェネリクスの設計実装に携わったプログラミング言語研究者 ◦ 2009年、Twitter が「Ruby on Rails の次」と採用し話題 ◦ 2021年5月、大幅に強化された Scala 3.0.0 リリース!! • ユーザー企業:Twitter、Netflix、Disney、Spotify、LinkedIn… • 影響を受けた言語:Java、Haskell、OCaml、Erlang など • 影響を与えた言語:Java、Kotlin、F# など • 実行環境:JVM、JS(ブラウザ、Node.js)、ネイティブ 26/3
  27. AWS Lambda ランタイム 27/39 ランタイム 登場 現在のバージョン Node.js 2014/11 14.x、12.x、10.x

    Java 2015/6 11、8 Python 2015/10 3.8、3.7、3.6、2.7 .NET 2016/12 3.2、3.1 Go 2018/1 1.x Ruby 2018/11 2.7、2.5 カスタム 2018/11 任意 コンテナイメージ 2020/12 任意 Scala などの JVM 言語
  28. Java/Scala 開発者から見た AWS Lambda Java ランタイム • 長所 ◦ 使い慣れた静的型付け言語で堅牢に開発

    ◦ 特にエンタープライズでは Java 技術者多い ◦ 豊富な Java/Scala ライブラリを活用 • 短所 ◦ JVM はコールドスタート(Lambda 関数インスタン スの初回起動)が遅い! 28/39
  29. イベント AWS Lambda のコールドスタートとは • 「Lambda 関数のインスタンスがイベントを処理できる状態になる」までの時間 • さまざな要因で変動し、100数十ミリ秒〜数十秒かかる •

    イベントを処理できるインスタンスがいないときに発生(=スケールアウト時) 29 z
z
z
 レスポンス ①コールドスタート ②実際の処理 ③イベントループ ④しばらくイベントが来なかったら停止
  30. コールドスタートとの付き合い方 • コールドスタートはゼロにはできないので、ある程度は受け入れる ◦ 普通の「サーバー」だって、スケールアウトは数十秒〜数分かかる! ◦ コールドスタートは特に目新しいことじゃない • コールドスタートを一切受け入れられないなら Lambda

    が向いていない ◦ 常にサーバーに余力がある状態=サーバーが高コスト • とはいえ、コールドスタートが小さいほど、速くスケールアウトできる 30
  31. AWS 利用者側でコールドスタートを短くする涙ぐましい努力① • 定期的にリクエストを投げて事前にウォームアップしておく ◦ ウォームアップ済み以上のリクエストが来たらやはり遅い ◦ お金をかければ Provisioned Capacity

    でウォームアップ済みを確保 • Lambda に与える性能を高めて、起動を速める ◦ 実際の処理には性能がいらなくてもコストアップ! • 起動が速いランタイムを使う ◦ TypeScript や Go を書く。学習コスト、スイッチングコスト • 1つの関数をいろんな用途に使い回す(通常は用途ごとに関数を使い分け) ◦ CloudWatch Logs やメトリクスが全部1つになってしまうので、用途ごと の違いが分からなくなる(自前で何とかするパワーが必要) 31/39
  32. AWS 利用者側でコールドスタートを短くする涙ぐましい努力② • 使いたい言語から JS を生成し、起動が速い Node.js ランタイムを使う ◦ コンパイラーや

    Node.js と付き合っていく覚悟が必要 ◦ ネタではなく真面目にプロダクションでやっていました 32/39 https://speakerdeck.com/exoego
  33. ランタイム 登場 現在のバージョン Node.js 2014/11 14.x、12.x、10.x Java 2015/6 11、8 Python

    2015/10 3.8、3.7、3.6、2.7 .NET 2016/12 3.2、3.1 Go 2018/1 1.x Ruby 2018/11 2.7、2.5 カスタム 2018/11 任意 コンテナイメージ 2020/12 任意 2018年末にゲームチェンジャーが登場 33/39
  34. AWS Lambda カスタムランタイム • 従来 AWS がマネージしてくれてたランタイムを自前で実装できる仕組み • ざっくりいうと以下のファイルを実装して、ZIP でかためてアップロード

    ◦ bootstrap: ランタイムの入口。ランタイムの仕事をこなす ◦ 任意のファイル: 個別の Lambda 関数、その他なんでも組み込める。 34/39 初期化 設定の取得 関数の初期化 初期化エラー処理 実行準備 イベントの取得 トレースヘッダーの伝播 コンテキストオブジェクトの作成 関数の呼出 終了 成功時レスポンス処理 エラー処理 クリーンアップ λ アプリの 実際の処理 個別の Lambda ランタイムの仕事 イベントループ
  35. カスタムランタイムを使えば何でも Lambda で動かせちゃう • PHP • Bash などのシェルスクリプト • C、Haskell、Rust

    などで生成したバイナリファイル • AWS がサポートしていないランタイムバージョン ◦ たとえば AWS 公式 Node.js ランタイムは Node.js のサ ポート終了に合わせて2年周期で退役していくので、退役 を先延ばしできる(そのリスクを受け入れるなら) • etc... 35/39
  36. JVM 言語でもカスタムランタイムで Lambda を爆速に実行する • GraalVM Native Image で JVM

    言語プログラムをネイティブコードに AOT コンパイルし、単体実行可能な軽量なバイナリを作成(以降 ネイティブ化) • 通常のJVM に比べて、省メモリで高速起動=Lambda にうってつけ • 実行速度は必ずしも JVM より速いわけではない。夢の技術ではない! 36/39 https://docs.oracle.com/en/graalvm/enterprise/19/guide/ reference/native-image/native-image.html
  37. ネイティブ化するまでの地道な作業 • リフレクションを使用しているライブラリを使うために設定が必要 ◦ https://www.graalvm.org/reference-manual/native-image/Reflection/ • ネイティブ化に成功しても実行時エラーでることもあってツラい ◦ 実行時エラーをログに出して地道にデバッグ! •

    GraalVM のバージョン、JDK のバージョン、ライブラリのバージョン (内部のリフレクションの変更)によってもわりと変わるので、バージョ ンアップするときは労力かかる(数人日くらい) 37/39 [ { "name": "akka.actor.typed.ActorRef", "allDeclaredConstructors": true, "allPublicConstructors": true }, { "name": "akka.actor.typed.internal.adapter.ActorSystemAdapter$LoadTypedExtensions$", "fields":[{"name":"MODULE$"}] }, ... // こんなのが数百行 ]
  38. Scala と GraalVM Native Image の相性 • 基本的には GraalVM Native

    Image と相性が良く感じた ◦ コンパイル時の解決を好み、実行時リフレクションは極力避ける文化 ◦ たとえば JSON ライブラリ ▪ Scala Circe:設定なしですんなり動く ▪ Java Jackson:リフレクションがっつりなので設定必要 • 本プロジェクトでリフレクションの設定で苦労したところ ◦ ロガー slf4j logger ◦ 非同期処理ライブラリ akka ▪ 社内で知見多く、非同期処理のリトライやタイムアウトが書きやすい ▪ また akka-http で定義された HTTP ステータスコードやヘッダーなどが Web API を作る上で便利 ▪ GraalVM のさまたげになるので、現在は akka を使わず実装しなおした 38/39
  39. リンクプレビューでの GraalVM Native Image の恩恵 39/39 JVM ランタイム @ 1024

    MB カスタムランタイム @ 256 MB コールドスタート 10,000 ms 300 ms 実際の処理 平均※ 1,000 ms 1,000 ms ※ I/O(外部サイトのダウンロードや DynamoDB)が律速なので、ネイ ティブ化しても平均処理時間はあまり変わらないという理解 JVM だと 1024 MB でもコールドスタートが10秒もかかってしまった。 ネイティブなら 256 MB でも 300 ms と高速に起動! Web API のバックエンドとして十分使える Lambda ではメモリを減らすと CPU 性能も下がるので、検証しましょう
  40. ちょっと苦労してでもカスタムランタイムを使うメリット • 2020年12月1日から Lambda の課金単位が 100ms→1ms に ◦ 速ければ速いほど運用コストも節約! ▪

    リンクプレビューのような I/O 律速な Lambda はネイ ティブ化で必ずしも速くはならないので注意 ▪ Node.js も相当速いので、開発しやすさとのバランス • Rust 言語で Lambda を開発してる会社も • https://github.com/awslabs/aws-lambda-rust-runtime ◦ AWS も実験的 OSS として Rust ランタイムを提供 ◦ Chatwork でも検証したがつまづきが多く、玄人向けの印象 40/39
  41. その他のトピック)AWS Lambda Layer • Lambda で使いたい何らかのファイルを Layer として登録しておき、 個々の Lambda

    から参照して、再利用する仕組み ◦ 画像処理や機械学習などで使う巨大なバイナリ ◦ 大量の依存関係(node_module など) ◦ カスタムランタイム • リンクプレビューでは ImageMagick を Layer にしている 41/39
  42. 5 Chatwork における サーバーレス開発の今後

  43. Chatwork と AWS Lambda の歩み • Lambda が 2014年12月に登場してすぐ検討してきた ◦

    魅力:スケーラビリティ、コスト体系、イベント駆動のシンプルさ • 2015年の第1次 Chatwork 全面刷新でも Lambda を採用したがお蔵入り ◦ 全面刷新から一部刷新に変わったため • メイン機能開発でたびたび検討し、見送り ◦ 技術的制約:コールドスタート、IaC の対応遅れ ◦ 不安や抵抗感:ノウハウ不足、既存技術やワークフローとのギャップ • サブ機能や社内ツールに採用にとどまっていた 43
  44. なぜサーバーレスをリンクプレビューで採用できたか • 既存技術スタックでは実現がむずかしく、サーバーレスが向いている 非機能要件だった • Lambda とエコシステムが成熟し、技術的制約が解消してきた • サブ機能でサーバーレス開発の経験を積んだエンジニアがリードし、 サーバーレス開発できる人を増やしていった

    • PoC をしてイケると確信できた 44
  45. スケーラビリティ良く、コスパ良く、 すばやく開発できる Lambda を もっと広げていきたい!

  46. Chatwork で現在進行中のサーバーレス開発 • 対象:2015年に中止したアーキテクチャ全面刷新に再挑戦してお り、そのいくつかのサブシステム • 技術的なチャレンジ ◦ 秒間30万以上のリクエスト、月間1000万ユーザー ◦

    CQRS(システムを特性の異なる書込系と読込系に大分割) ◦ 適材適所でサーバーレスとコンテナを使い分け ◦ メイン機能の 90% 以上(アクセス数換算)で Lambda、 DynamoDB を活用する見込み 46
  47. 6 まとめ

  48. サーバーレスアーキテクチャ最高 !! • 毎月数億URLをさばく API を Scala+GraalVM+Lambda で新規開発。 さらに CloudFront

    で毎月数十億リクエストを高速、安価に配信 • 色々なサーバーレス系 AWS のおかげで、少人数でも迅速にバックエ ンド開発し、安定稼働できている • Chatwork の次世代アーキテクチャでは、サーバーレス開発をさらに 広げていきます。一緒に挑戦したい方はぜひ連絡してください!! 48/39
  49. 働くをもっと楽しく、創造的に