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

SpringBootでメッセージキュー&非同期処理を使ったノウハウ紹介

Recruit
July 20, 2023

 SpringBootでメッセージキュー&非同期処理を使ったノウハウ紹介

2023/06/04に、JJUG CCC 2023 Springで発表した、西村と山田の資料です。

Recruit

July 20, 2023
Tweet

More Decks by Recruit

Other Decks in Business

Transcript

  1. 先輩、上司の活動事例 ・肥大化、複雑化コードのリファクタリング https://codezine.jp/article/detail/11570 https://codezine.jp/article/detail/11445 ・プロセス改善 https://speakerdeck.com/poohsunny/devsumi2018 https://www.slideshare.net/i2key/devsumi-152929762#36 ・技術負債除去 https://speakerdeck.com/rtechkouhou/taunwaku90mo-yuan-gao- falsejie-zai-wozhi-eruregasibatutipahuomansutiyuningu-number-

    devsumi-number-devisumid https://youtu.be/qgVG8AVrM7M ・自動テスト強化 https://www.slideshare.net/i2key/devsumi-152929762#56 https://speakerdeck.com/rtechkouhou/kokoshu-nian-jian- falsetaunwakuiosapurifalseenziniafalsetiyarenzi?slide=32 これまでレガシーシステムの発表が多かったですが 今回はクラウド&新規サービスのお話になります
  2. 最近の弊社のHR事業について アーキテクトとしてレガシーシステムでの活動をメインとしていましたが、ここ数 年はクラウドで構築された新規システムでの活動も徐々に増えてきている状況 (もちろん事業価値の規模が大きいレガシーシステムを維持しながら) カスタマー クライアント 応募 検索, 閲覧 求人作成,

    入稿 HR事業のリボンモデル 最近リリースのカスタ マー向けサービス レジュメ管理 サービス 既存入稿,ATSシステム 既存メディアシステム 10年以上選手 のシステム’s 最近リリースのクライ アント向けサービス オンプレ構築の システム (これまでの主戦場) AWS構築の システム (最近増えてる)
  3. 今回お話する「非同期処理」について WebAPI 別スレッド ①リクエスト受付 ②スレッド起動 ③レスポンス ④DB登録など 非同期化したい処理 WebAPI 非同期

    プロセス ①リクエスト受付 ④レスポンス ⑤ 非同期処理 キュー ② キューイング ③ キュー取得 Javaのスレッドを用いた非同期処理 ではなく メッセージキューを用いた非同期処理 になります ⑥ キュー削除
  4. クラウドでのメッセージキュー クラウドだとメッセージキュー自体の用意が比較的楽になる Amazon SQS Amazon SNS Amazon MQ AWS Azure

    GCP Azure Queue Storage Azure Service Bus Google Cloud Pub/Sub Google Cloud Tasks これのSLAが高くなければ ならない メジャーなクラウドベンダーとメッセージキュー関連のサービス
  5. 例えば以下のような機能が必要となったときの従来の選択肢 バッチやWebAPIがメインの選択肢だったレガシーシステムでは色々な工夫により機能を実現 結果としてコードや仕様などの複雑度を上げていく傾向が多かった DB データ登録 API 外部連携 バッチ 応募受付 API

    オンプレ ①X分ごとに起動して データを刈り取り外部 連携するバッチ 外部連携処理 カスタマーからの応募 ②DB登録、メール送信、 添付ファイル登録.. など多くの処理を一括実施 ストレ ージ
  6. 「非同期処理」のメリット WebAPI 非同期 プロセス ①リクエスト受付 ④レスポンス ⑤ 非同期処理 キュー ②

    キューイング ③ キュー取得 時間のかかる処理を非同期化 → Webの応答速度低下抑制 ⑥ キュー削除 特定の機能実現において上記のようなメリットがあると認識 適切な使い方をすることで柔軟かつ堅牢なシステム構築に役立つと考えた 処理失敗時も再実行可能 → リトライ容易性向上 定期バッチ DB 非同期処理 非同期処理 非同期処理 まとめてバッチ型 随時キュー型 キュー 実現するためにcron設定や 同時起動などのケアが必要 1件ずつ随時実行可能 再実行も1件単位で可能 スケールもしやすい
  7. 弊社Webサービスでの代表的な技術スタックについて ECS ELB (外 部用) アプリケーション データソース BFF (NextJS) WebAPI

    (SpringBoot) ELB (内 部用) その他マネージドサービス アプリケーション実行環境としてECS+Fargate バックエンドではNextJS, SpringBootをBFF, API層として採用 非同期処理(MessageAPI)はSpringBootで実装 ※ 運用/体制を踏まえてシンプルさを重視した技術選定を心がけている
  8. プロデューサーはSpringBootのみとした ECS ELB (外 部用) アプリケーション データソース BFF (NextJS) WebAPI

    (SpringBoot) ELB (内 部用) その他マネージドサービス メッセージキュー SQSへのメッセージ登録に制約を設ける プロデューサー
  9. コンシューマーもSpringBootで構築するようにした ECS ELB (外 部用) アプリケーション データソース BFF (NextJS) WebAPI

    (SpringBoot) ELB (内 部用) MessageAPI (SpringBoot) その他マネージドサービス さらにメッセージ受信処理にも制約を設ける プロデューサー コンシューマー メッセージキュー
  10. ECS ELB (外 部用) アプリケーション データソース BFF (NextJS) WebAPI (SpringBoot)

    ELB (内 部用) MessageAPI (SpringBoot) その他マネージドサービス 「非同期処理」もWebAPIと同じ設計指針で構築 処理の起点が異なるだけの同じ “API” 扱いすることで 開発者にとって設計/実装ハードルを下げる方針を採用 Webリクエストを処理するAPI = WebAPI キューリクエストを処理するAPI = MessageAPI という名称で社内では呼んでいます Webリクエストを処理するAPI = WebAPI キューリクエストを処理するAPI = MessageAPI という名称で社内では呼んでいます APIの設計指針イメージ プロデューサー コンシューマー 仕上げに実装における設計指針も合わせた メッセージキュー
  11. 結果、現場エンジニアで実装可能な状態を構築 非同期処理はXXなメリッ トがあるのでこのように 構築お願いします システム (成果物) アーキテクト 現場エンジニアチーム IN OUT

    結果としてスムーズな導入が完了! うおおお! アーキ テクチャ ラジャー! 作り方、指針、SQSルール デプロイ方法..etc
  12. Spring Application 自動的にQueueをポーリングする仕組み ① AWS Lambda イベントソースマッピング “イベントソースマッピングとは、イベントソースから読み取り、Lambda 関数を呼び出す Lambda

    リソースのことです。イベントソースマッピングを使用して、Lambda 関数を直接呼び出さないサー ビスのストリームまたはキューから項目を処理できます。” キュー AWS Lambda イベントソース マッピング AWS Lambda Function ポーリング メッセージが あれば呼び出し ② Spring Cloud AWS の アノテーション駆動型リスナーエンドポイント キュー Spring Cloud AWS @SqsListenerを つけたメソッド ポーリング メッセージが あれば呼び出し
  13. Spring Application 自動的にQueueをポーリングする仕組み ① AWS Lambda イベントソースマッピング “イベントソースマッピングとは、イベントソースから読み取り、Lambda 関数を呼び出す Lambda

    リソースのことです。イベントソースマッピングを使用して、Lambda 関数を直接呼び出さないサー ビスのストリームまたはキューから項目を処理できます。” キュー AWS Lambda イベントソース マッピング AWS Lambda Function ポーリング メッセージが あれば呼び出し ② Spring Cloud AWS の アノテーション駆動型リスナーエンドポイント キュー Spring Cloud AWS @SqsListenerを つけたメソッド ポーリング メッセージが あれば呼び出し WebAPIと合わせてSpringBootを利用したいため こちらを採用
  14. SQSは、メッセージを受信しただけではキューからメッセージは削除されず、 明示的に削除する必要がある DeletionPolicyの設定 コンシューマ メッセージ受信 1 2 3 1 2

    3 1 2 3 1 2 3 コンシューマ メッセージ削除 1 キューにメッセージがたまる 2 メッセージを受信 3 処理が完了したらメッセージを削除
  15. 正常に処理ができなかった場合は、キューにメッセージを残しておくことで 次回のメッセージ受信時に再度処理を行うことができる DeletionPolicyの設定 コンシューマ メッセージ受信 1 2 3 1 2

    3 1 2 3 1 2 3 コンシューマ 2 メッセージ削除 1 キューにメッセージがたまる 2 メッセージを受信 3 処理が完了したらメッセージを削除 2のメッセージは処理に失敗 したので削除せず残しておく 1,3 のメッセージは正常に処理 2のメッセージは処理中に例外発生
  16. SpringCloudAWSでは @SqsListenerによって処理したメッセージはライブラリ側 で自動で削除までやってくれる その際の挙動をDeletionPolicyで指定することができる • ALWAYS ◦ 処理が成功、失敗(例外がスローされた)どちらの場合もメッセージを削除する • NEVER

    ◦ メッセージを削除しない(手動で削除する必要がある) • NO_REDRIVE(デフォルト) ◦ リドライブポリシーが定義されていない場合、メッセージを削除する • ON_SUCCESS ◦ 処理が成功した場合のみメッセージを削除する DeletionPolicyの設定 ※ リドライブポリシー: メッセージが一定回数以上受信された際に一時的に別のキ ュー(デッドレターキューと呼ばれる)に移動させるSQSの設定
  17. SpringCloudAWSでは、@SqsListenerによって処理したメッセージはライブラリ 側で自動で削除までやってくれる その際の挙動をDeletionPolicyで指定することができる • ALWAYS ◦ 処理が成功、失敗(例外がスローされた)どちらの場合もメッセージを削除する • NEVER ◦

    メッセージを削除しない(手動で削除する必要がある) • NO_REDRIVE(デフォルト) ◦ リドライブポリシーが定義されていない場合、メッセージを削除する • ON_SUCCESS ◦ 処理が成功した場合のみメッセージを削除する DeletionPolicyの設定 ※ リドライブポリシー: メッセージが一定回数以上受信された際に一時的に別のキ ュー(デッドレターキューと呼ばれる)に移動させるSQSの設定 我々のプロダクトでは、デッドレターキューを設定 した上でON_SUCCESSを指定している
  18. その他WebAPI, MessageAPIを両立するためのプロジェクト構成 画面 web-api Service Repository DB 外部API Controller Service

    Repo Controller Service Repo Usecase Usecase SQS message-api web-api、message-apiどちらからも 共通のロジック(Service, Repository)を利用できる
  19. MessageAPI実装はうまくいった 概ねWebAPIとMessageAPIをうまく使い分けられた 1.扱いやすさの代償 WebAPI MessageAPI ①応募リクエスト SQS ③キューイング ⑤ キュー取得

    ⑦ キュー削除 DB ② データ 一時保存 ④レスポンス 例: カスタマーの応募情報受付 ⑥応募データ登録 [再掲]WebAPI, MessageAPIは同じ設計指針 両方とも同じような 作りで実装可能 非同期にすることで時間のかかるチェ ック処理やメール送信などが可能また 問題発生時もSQSから処理リトライが できる
  20. ただし、一部のシステム連携にて「非同期」を無視した実装が生まれたりもした 1.扱いやすさの代償 システムA システムB DB WebAPI MessageAPI 更新API 取得API 更新フロー

    取得フロー ここの処理 完了前に データ取得しに いっちゃう システム間のデータ更新→ 参照で起きたケース
  21. システム連携方針とAPIという抽象化によって発生した設計不備という認識 1.扱いやすさの代償 システムA システムB DB WebAPI MessageAPI 更新API 取得API 更新フロー

    取得フロー システム間のデータ更新→ 参照で起きたケース 疎結合を目指してシステム間連 携はMessageAPIでやることに していた 連携する側の実装者からすると 両方とも同じ “API” という認知 になってしまった
  22. システム連携方針とAPIという抽象化によって発生した設計不備という認識 1.扱いやすさの代償 システムA システムB DB WebAPI MessageAPI 更新API 取得API 更新フロー

    取得フロー システム間のデータ更新→ 参照で起きたケース 疎結合を目指してシステム間連 携はMessageAPIでやることに していた 連携する側の実装者からすると 両方とも同じ “API” という認知 になってしまった 【チームのスキル底上げ】 MessageAPIの特性やトレードオフを事前に周知徹底する 【方針再検討】 連携方針に制約を設けず、WebAPIでの連携も可能とする 【運用で検知, カバー】 システム連携が必要な実装が出てきたら我々に相談をしてもらう 【パターンの提示】 別システムからのデータ更新が本当に必要か?を見直す
  23. 2.監視, スケールについて SQS A用 Controller MessageAPI (SpringBoot) SQS B用 Controller

    SQS C用 Controller SQS A SQS B SQS C タスク SQSごとにSpringBoot側でControllerを用意するとこのようになる この状態において問題を検知するにはどうするのがよいか? ECSの世界では タスク = SpringBootのプロセス というイメージ ECS
  24. SQS A用 Controller MessageAPI (SpringBoot) SQS B用 Controller SQS C用

    Controller SQS A SQS B SQS C タスク (MessageAPIが吐くエラーの監視はするとして) MessageAPIは処理遅延が起こるとキューにメッセージが溜まる構造 そのためキューの状態の監視が重要になる MessageAPI側のスルー プットを超えるメッセ ージが登録されたり 何らかの理由で MessageAPIが遅延した りすると ここのキューにメッセ ージが溜まりだす 2.監視, スケールについて ECS
  25. SQS A用 Controller MessageAPI (SpringBoot) SQS B用 Controller SQS C用

    Controller SQS A SQS B SQS C タスク 処理遅延を解消するには水平スケールが楽 (ただしMessageAPIの処理自体が常態的に遅い場合は処理改善をする必要もあり) SQS A用 Controller SQS B用 Controller SQS C用 Controller SQS A用 Controller SQS B用 Controller SQS C用 Controller SQS A用 Controller SQS B用 Controller SQS C用 Controller SQS A用 Controller SQS B用 Controller SQS C用 Controller 2.監視, スケールについて 処理遅延に対する簡単 な対策はSpringBootの プロセスを増やして水 平スケールすること ECSだとタスクの数を 簡単に上げることがで きたりもする 余談: このつくりだと特定のSQS詰ま りだけを解消したい場合に全部の SQSのコンシューマが増える。 その場合は特別詰まりやすいSQSの コンシューマだけ別プロジェクトと して構築してSpringBootのプロセス 自体を分けるのがよいかも。 ECS
  26. 3.段階的リリース あるAPIエンドポイントが叩かれ、データXが更新された時に データXをPDFに変換してそのPDFをS3に格納するという機能追加の場合 小さい単位に分割すると ① 更新されたデータXを取得 ② データXをPDFに変換 ③ PDFをS3に格納

    ①のみを同期的に実装すると public void updateDataX() { // エンドポイントXの既存処理 // ①の処理を追加 (結果は使わない) } ・処理が失敗(例外が発生)した場合、エンドポイント全 体が失敗してしまう(または try catch が必要) ・①の処理時間の分だけ、レスポンスタイムが遅くなる
  27. 3.段階的リリース あるAPIエンドポイントが叩かれ、データXが更新された時に データXをPDFに変換してそのPDFをS3に格納するという機能追加の場合 小さい単位に分割すると ① 更新されたデータXを取得 ② データXをPDFに変換 ③ PDFをS3に格納

    SQSを用いて非同期的に①のみを実装すると public void updateDataX() { // エンドポイントXの既存処理 // SQSへのメッセージ送信処理を追加 } キュー ①の処理を実装 ①の処理の失敗や処理時間は更新APIに影響しない この処理のみ、正しく動くことを試験しておく
  28. 3.段階的リリース あるAPIエンドポイントが叩かれ、データXが更新された時に データXをPDFに変換してそのPDFをS3に格納するという機能追加の場合 小さい単位に分割すると ① 更新されたデータXを取得 ② データXをPDFに変換 ③ PDFをS3に格納

    我々のプロダクトでは、以下の順番での段階リリースを実施 ・SQSにメッセージを送信する機能 ↓ ・更新されたデータXを取得する機能(①) ↓ ・データXをPDFに変換する機能(②) ↓ ・PDFをS3に格納する機能(③) キューが正しく処理されることを確認 PDFの作成にどれくらいの処理時間がかかるのか、 どの程度インフラリソースが必要なのかを確認 (本番環境で性能が検証できた)