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

月間数十億リクエストのマイクロサービスを支える
 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 のアップデート版です。

TATSUNO Yasuhiro

July 14, 2021
Tweet

More Decks by TATSUNO Yasuhiro

Other Decks in Technology

Transcript

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

    ◦ Serverless Framework コミッター • 近況 ◦ 庭いじりでイチジクを植えました ◦ 監訳した Scala の技術書が発売 !! 自己紹介 2/39
  2. Chatwork のリンクプレビューはこんな機能です 7/39 ①チャットに URL が含 まれていると ②タイトル、画像、ファビコ ンを自動で表示 投稿した人

    見る人 共有したいページのタイトルや 内容を入力する手間が減る 開く前にどんなページか分かり開 くかどうか判断しやすくなる
  3. プレビューする情報は 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
  4. 日常的に見る「あたり前」機能、作る側にはけっこう悩ましい • 何をどうプレビューするか? ◦ どんなサイトをどんな風にプレビューできるとユーザーの業務が捗る? ◦ OGP 以外の規格は? メタデータはないけど有用なサイトは? ◦ 1メッセージに複数

    URL が含まれていたら、どこまでプレビューする? ◦ プレビュー取得に時間がかかる場合、チャットの表示はどれくらい遅れ てよい? • 性能やコストは? ◦ ユーザーがバンバン URL を共有しても安定・高速にさばける? ◦ 膨大なリクエストをさばく大量サーバーのコストは? • 外部の Web サイトに迷惑かけないアクセス方法は? ◦ Web サイトに robots.txt があったら? ◦ Web サイトがエラーを返したらリトライする? その間隔は? 10/39 本日の お話
  5. Chatwork バックエンドの構成(2020/1当時) 1. PHP 系サービス ◦ 2011年から続く主要サービス(チャットルーム、権限、通知…) ◦ さまざまなバッチ処理、ストリーミング処理… 2.

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

    チームは新規の周辺サービスに注力 ◦ 現行アーキで Scala で主要サービスの担当範囲を広げるには、PHP と Scala を連携させる基盤整備する必要があった。 ▪ 新アーキで全面刷新予定のため、2〜3年で捨てる基盤整備は避けた。 ◦ 周辺サービスは Scala のおかげで堅牢で機能追加も少なく、余力があった • こうした事情から、リンクプレビューはなんとしても Scala チームが巻き取りたい 13/39
  7. 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. メッセージ要求
  8. 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 チーム単独での新規機能開発、 別プロジェクトで検証していた サーバーレスの恩恵が大
  9. • ざっと必要なもの ◦ エンドポイント(Web サーバー) ◦ ビジネスロジック(アプリサーバー) ◦ プレビュー用データやログを保存(DBサーバー、ファイルサーバー) •

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

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


    • Lambda関数: 入力(今回はHTTPリクエスト)に対し、出力(プレ ビューに必要な JSON や画像)を返す • 同時に何千〜何十万リクエストでもさばけるスケーラビリティ ◦ リンクプレビューでも毎月数億 URL を安定してさばけている • 性能x処理時間で課金。処理してない間は課金されない ◦ メモリはわずか 256 MB(あとで詳しく) ◦ 従来のサーバー起動しっぱなしと比べて超安い Lambda
 19/39 外部サイト
  12. 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
 外部サイト
  13. DynamoDB: 同じ URL への同時アクセス防止に使用 23/39 CloudFront
 🌏外部サイト API Gateway
 DynamoDB


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

    のリンクプレビューを一定時間キャッシュ ◦ キャッシュなし 平均 1.00 秒 → あり:平均 0.02 秒 • キャッシュがある間はアクセスしない=外部サイトへの配慮 • CloudFront より後段のリソースの利用コストを数十分の1に削減 • デスクトップ or モバイル、ユーザー言語に合わせたキャッシュ。URL に加え、HTTP ヘッダーなどもキャッシュキーにできるため。 • WAF と連携して DDoS 攻撃などにも耐える。 • 画像などもキャッシュできるので、本サービスでは S3 未使用! API Gateway
 Lambda
 キャッシュ前 キャッシュ後 24/39 外部サイト
  15. プログラミング言語 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
  16. 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 言語
  17. Java/Scala 開発者から見た AWS Lambda Java ランタイム • 長所 ◦ 使い慣れた静的型付け言語で堅牢に開発

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

    イベントを処理できるインスタンスがいないときに発生(=スケールアウト時) 29 z
z
z
 レスポンス ①コールドスタート ②実際の処理 ③イベントループ ④しばらくイベントが来なかったら停止
  19. AWS 利用者側でコールドスタートを短くする涙ぐましい努力① • 定期的にリクエストを投げて事前にウォームアップしておく ◦ ウォームアップ済み以上のリクエストが来たらやはり遅い ◦ お金をかければ Provisioned Capacity

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

    Node.js と付き合っていく覚悟が必要 ◦ ネタではなく真面目にプロダクションでやっていました 32/39 https://speakerdeck.com/exoego
  21. ランタイム 登場 現在のバージョン 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
  22. AWS Lambda カスタムランタイム • 従来 AWS がマネージしてくれてたランタイムを自前で実装できる仕組み • ざっくりいうと以下のファイルを実装して、ZIP でかためてアップロード

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

    などで生成したバイナリファイル • AWS がサポートしていないランタイムバージョン ◦ たとえば AWS 公式 Node.js ランタイムは Node.js のサ ポート終了に合わせて2年周期で退役していくので、退役 を先延ばしできる(そのリスクを受け入れるなら) • etc... 35/39
  24. 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
  25. ネイティブ化するまでの地道な作業 • リフレクションを使用しているライブラリを使うために設定が必要 ◦ 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$"}] }, ... // こんなのが数百行 ]
  26. Scala と GraalVM Native Image の相性 • 基本的には GraalVM Native

    Image と相性が良く感じた ◦ コンパイル時の解決を好み、実行時リフレクションは極力避ける文化 ◦ たとえば JSON ライブラリ ▪ Scala Circe:設定なしですんなり動く ▪ Java Jackson:リフレクションがっつりなので設定必要 • 本プロジェクトでリフレクションの設定で苦労したところ ◦ ロガー slf4j logger ◦ 非同期処理ライブラリ akka ▪ 社内で知見多く、非同期処理のリトライやタイムアウトが書きやすい ▪ また akka-http で定義された HTTP ステータスコードやヘッダーなどが Web API を作る上で便利 ▪ GraalVM のさまたげになるので、現在は akka を使わず実装しなおした 38/39
  27. リンクプレビューでの 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 性能も下がるので、検証しましょう
  28. ちょっと苦労してでもカスタムランタイムを使うメリット • 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
  29. その他のトピック)AWS Lambda Layer • Lambda で使いたい何らかのファイルを Layer として登録しておき、 個々の Lambda

    から参照して、再利用する仕組み ◦ 画像処理や機械学習などで使う巨大なバイナリ ◦ 大量の依存関係(node_module など) ◦ カスタムランタイム • リンクプレビューでは ImageMagick を Layer にしている 41/39
  30. Chatwork と AWS Lambda の歩み • Lambda が 2014年12月に登場してすぐ検討してきた ◦

    魅力:スケーラビリティ、コスト体系、イベント駆動のシンプルさ • 2015年の第1次 Chatwork 全面刷新でも Lambda を採用したがお蔵入り ◦ 全面刷新から一部刷新に変わったため • メイン機能開発でたびたび検討し、見送り ◦ 技術的制約:コールドスタート、IaC の対応遅れ ◦ 不安や抵抗感:ノウハウ不足、既存技術やワークフローとのギャップ • サブ機能や社内ツールに採用にとどまっていた 43
  31. Chatwork で現在進行中のサーバーレス開発 • 対象:2015年に中止したアーキテクチャ全面刷新に再挑戦してお り、そのいくつかのサブシステム • 技術的なチャレンジ ◦ 秒間30万以上のリクエスト、月間1000万ユーザー ◦

    CQRS(システムを特性の異なる書込系と読込系に大分割) ◦ 適材適所でサーバーレスとコンテナを使い分け ◦ メイン機能の 90% 以上(アクセス数換算)で Lambda、 DynamoDB を活用する見込み 46
  32. サーバーレスアーキテクチャ最高 !! • 毎月数億URLをさばく API を Scala+GraalVM+Lambda で新規開発。 さらに CloudFront

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