非同期処理を使いこなそう !

-第 2 回 非同期処理と同期処理の処理構造

2022-07-04
デベロッパーのためのクラウド活用方法

堀場 隆文

皆さんこんにちは。プロフェッショナルサービス本部のデジタルトランスフォーメーションチームでマネージャーをしています堀場です。

さて、いきなりですが、先日、ふと、頭に思い浮かんだ単語があります。それがこちら。

「機能」「情報」「連絡」「手順」「時間」・・・ 

25 年以上前に覚えた単語がふと出てきたわけで・・・・この後、何が続くかご存知でしょうか ?

答えは、「論理」「暗号」です。 これは、モジュール強度 (または凝集度) の段階を表すもので「暗号」は凝集度が低く「機能」がもっとも高いと定義されています。なぜ、思い出したのかは謎です。ちなみに、10 年前の資料ですが IPA が提供している IT 人材育成用の汎用コンテンツ講義ノート に簡単な解説がありましたので興味がある方は読んでみてください。

凝集度が高いほど、堅牢性、信頼性、再利用性が高く、コードの読みやすさなどの点で好ましく、凝集度の低いモジュールは保守性、再利用性が低い、コードが読みにくい等、好ましくないモジュールと言われており、凝集度を高めるモジュール設計を私も心掛けています。また、モジュールの特性を表す言葉としては「凝集度」とは別に「結合度」があり、これは複数のモジュール間のつながりの特性について示すものです。モジュール間の通信がまったくなければ「無結合」ですが、「マイクロサービス化」・「API 化」をしているモジュールは、他のモジュールと結合するための API をもっており、無結合ではありません。結合があるモジュールは、いかに結合度を低くするかが重要です。それは、モジュール間の結合度が高いとモジュール間の依存性が高いことを意味し、障害の影響、機能改修時の影響が大きくなるためです。逆にモジュール間の結合度が低い、つまり「疎結合」だと、影響は受けにい可能性が高いです。

さて、ふと思い出した単語をもとに「凝集度」「結合度」について触れましたが「強度を高める」「結合度を低める」こと自体が目的ではないと思います。その裏にある目的は「保守性」「堅牢性」「信頼性」「再利用性」の向上や「影響範囲の限定化」になり、さらにその背景には「機会損失の削減」「ブランド力の向上」「ユーザーへの提供価値の最大化」などのビジネス上の重要な目的があると思います。

当記事で取り扱う「非同期処理」も複数のモジュール間で行う処理要求と実際の処理の実行・結果の応答タイミングの結合度を弱め疎結合化し、「応答性能」「堅牢性」「信頼性」の向上や「影響範囲の限定化」を行い、最終的にそのモジュールを利用して実現するビジネス価値を生み出すことが目的だと思います。ぜひ「同期・非同期化」や「疎結合化」を最終目的にせず、それがどのような成果につながるかも考えてみてください。

さて、前置きが長くなりましたが、当記事は「非同期処理を使いこなそう !」シリーズの第 2 回として非同期処理と同期処理の処理構造についてお話したいと思います。今回は同期処理と非同期処理の「処理のステップ」と「技術要素」について整理します。そして、第 3 回では各技術要素に対して AWS サービスを具体例として挙げ解説していきます。

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 非同期処理ってなんだろう ?
  • 第 2 回 非同期処理と同期処理の処理構造
  • 第 3 回 AWS サービスを活用した非同期処理アーキテクチャ

1. 処理の選択

前回の記事 では「非同期処理と同期処理の特性を理解して適材適所で使う」と説明しました。例えば、非同期処理は、処理結果を受け取らずとも「要求元 (リクエスター)」が処理を進められる場合や、サービスの処理結果生成に時間がかかる場合等に適用することで、エンドユーザーにとって快適な Web サービスを構成が可能になると説明しました。

以下、前回の記事で掲載した図を再掲し説明します。

サービス A がサービス B の処理結果を必要とする場合、同期処理として実装しサービス B の応答を待って処理行い、クライアントに応答します。一方、サービス A がサービス B の処理結果を必要としない、または、応答を待つと性能やスループットを達成できない場合、サービス A はサービス B 呼び出しを非同期処理として実装しサービス B の処理完了や処理結果を待つことなくクライアントに応答します。非同期処理にすることで、クライアントへの応答が速いタイミングで実施でき待たせる時間を短縮化することで満足度向上等につなげるわけですね。もちろん、サービス B の処理結果をもってクライアントに応答する必要がある場合には同期処理が適しています。

img_master-asynchronous-execution-02_01

上記の例では、非同期処理・同期処理が分かれていましたが、前回例示した EC サイトを例に挙げ、非同期処理と同期処理を組み合わせた例について下図で説明します。

下図のようにエンドユーザーから EC サイト (ここでは、注文処理サービス) に対しては 1 つのリクエスト・レスポンスでやり取りされています。一方、注文処理サービスの中では在庫管理サービスと配送処理サービスを呼び出し、在庫管理サービスは同期処理、配送処理サービスは非同期処理で呼び出しています。さらに、配送処理サービスと外部システム間は同期処理として呼び出しています。

このように非同期処理・同期処理を必要に応じて組み合わせて使うことが可能です。

img_master-asynchronous-execution-02_02

ここまでの説明では非同期処理・同期処理を使い分ける観点の例として「処理結果」や「応答時間」を取り上げました (もちろん他の観点もあります)。

この  2 つの観点で考えるとこちらの表のとおりの使い分けができます。

img_master-asynchronous-execution-02_03

呼び出すサービスの応答時間が「許容の範囲内」で、処理結果が後続処理で「必要」であれば、同期処理として実装します。また、処理結果が不要な場合でもシンプルな実装としたい場合、同期を選択することもあります。

処理結果が不要な場合や、応答時間が許容の範囲外である場合は非同期処理を選択することが多いです。ちなみに、処理結果が必要だが応答時間が許容の範囲外という場合には、

  • 自システムの要件を再考し、応答時間が許容できるように要件を再調整する
  • 自システムの要件を再考し、処理結果に依存する処理を別のタイミングで実施するよう調整する
  • 呼び出し先の応答時間の改善を依頼する
  • 呼び出し先の処理の分割を依頼し、最低限の処理で応答できないか調整する

などが選択肢となります。ぜひ、ご検討ください。


2. 処理のステップ

様々な観点を考慮し、非同期処理・同期処理の選択を行いますが、非同期処理を選択し、呼び出し元が応答を受け取りたい場合は同期処理に比べて手順が若干多くなります。ここからは処理のステップを整理し実装に必要な要素を整理します。

非同期処理も同期処理もともに要求に基づいて処理を行いなんらか結果を生成・応答します。結果は必要に応じてデータストアへの格納する場合もあるでしょう。ここでは、その処理開始から完了までのステップを非同期処理・同期処理の場合で下図で整理します。

img_master-asynchronous-execution-02_04

非同期処理・同期処理ともに、クライアントからの「1 .処理の要求」で処理が開始されます。その後、サービスが要求を受付、処理を実施、結果を生成し、最後にクライアントが結果を確認する流れとなっています。

非同期処理の場合は、処理の実行を別のタイミングで行い、処理の要求に対しては受付完了を即時応答することでクライアントは待つことなく次に進めるようになっています。ただし、クライアントが後で結果を確認したい場合には、再度、呼び出したサービス、または、結果を保持しているサービスに対して結果確認の要求を行い、結果を受け取ります。上図では、同一のサービスに対して結果確認を実施しています。結果確認方法としては、ポーリングして繰り返し確認する方法やイベント通知を受け取り確認する方法などがあります。

上図のように、非同期処理では同期処理と異なり

  • 受付処理や受付完了応答
  • 別のタイミングで処理を実行するための制御
  • 結果の問い合わせに対して処理状況または結果を応答対応

が追加の制御として必要となります。


3. 非同期処理の構成

ここからは、非同期処理の実装するうえで登場する概念について説明します。前述の通り、非同期処理を実装するには、同期処理を実装するときに比べていくつか必要なことがあります。ここでは、処理のステップで説明した「2. 処理の受付」から「5. 処理の実行」部分について説明します。

非同期処理では、「2. 処理の受け付け」と「4. 処理の実行」が別のタイミングで行われます。スレッドが別というケースもあれば、プロセス、または実行ノードがサーバーという場合もあります。いずれも、受け付けたリクエストを漏らすことなく全て実行する必要があり、受け付けたまま放置しないように制御する必要があります。

このような場合に利用されるのがキューやトピック (Publish / Subscribe モデル) です。キューやトピック (Public / Subscribe モデル) が気になる方は、メッセージキューとはWhat is Pub/Sub Messaging を参照してみてください。異なるプロセス (ソフトウェア) 間でデータの送受信する手法の一つとご理解いただければと思います。

キューやトピックを利用することで、プロセス間 (ソフトウェア間) は直接接続せず、疎結合 (Loosely-coupled) にすることが可能です。

3-1 キュー(Queue)

先ほど処理のステップを整理した例の「2. 処理の受付」から「5. 結果の生成」を少し詳細に示したものが下図となります。

サービス A を

  • プロセス α
  • キュー
  • プロセス β

に分けました。プロセス α とプロセス β  の間にはキューを配置しプロセス α がキューに対してメッセージを送信 (キューイング要求) し、キューは、即時、キューイング完了応答をします。また、プロセス β は随時、キューをポーリングしてメッセージの存在確認を行い、メッセージの応答があった場合には、そのメッセージを利用して処理の実行を行います。

このようにキューを挟むことで、プロセス α は、プロセス β の処理状況・リソース状況を意識することなく、受付完了応答が可能となります。また、プロセス β はプロセス α を意識することなく、キューにメッセージがあれば動く「イベント駆動な処理」として実装ができます。

img_master-asynchronous-execution-02_05

前回の記事でも触れましたが、プロセス β の状況はプロセス α からは見えなくなりましたが、代わりに重要となるのが、「キュー」の稼働状況です。プロセス α が正常に受付を完了するためには、キューが正常に稼働し続けること、キュー内のメッセージが溢れないこと (スケーラブルであること) が重要となります。

3-2 トピック (Publish / Subscribe)

基本的にはキューと同じ役割を果たしますが、実行時のシーケンスが異なるため、下図で説明します。下図ではキューの部分がトピックに変わっていますが、一番重要なのは「c. メッセージ発行」です。トピックに依頼があったメッセージを事前に登録 (subscribe) された購読者 (Subscriber)、ここではプロセス β に発行しています。キューを利用する場合は、プロセス β がキューにポーリングを行いメッセージの有無を確認していましたが、トピックの場合はプロセス β にメッセージが自動的に届く形となりシーケンスが異なります。

img_master-asynchronous-execution-02_06

3-3 キューとトピックの使い分け

キューもトピックも、異なるプロセス (ソフトウェア) 間でデータの送受信する手法の一つです。では、どの場合にどちらを使うべきなのか違いを説明します。

大きく違うのは、プロセス β 側、つまり、メッセージの受信側の動きとなります。具体的には、「メッセージの受信方法」「メッセージの受信者」と「受信タイミング」です。

  キュー トピック
メッセージの受信方法 受信者によるポーリング 受信者による事前の Subscribe (購読の登録)
メッセージの受信者 キューをポーリングした 1 プロセス トピックを Subscribe した全プロセス
受信タイミング 受信者がポーリングしたタイミング
(送信者が送信したタイミングとは限らない)
基本的に送信者がメッセージ発行要求をした直後

メッセージの受信方法

キューの場合はキューの中にあるメッセージを受信するために受信したいプロセスがキューに対してポーリングを行いメッセージがあれば受信します。受信側のプロセスに自動的にメッセージが届くのではなく自分からとりに行く必要があります。一方、トピックの場合には、事前にトピックに対してSubscribe (購読の登録) をする必要がありますが、登録後は自動的にメッセージが送られてくるためポーリングは不要です。

メッセージの受信者

キューに格納されたメッセージはポーリングしたプロセスがメッセージを受領できますが、1 つのメッセージは基本、1 プロセスがを取得する形になります (受信後、処理エラー発生時や、分散キューの場合異なる場合があります)。一方、トピックの場合には、トピックに送られたメッセージは購読者全員に配信されるため、複数のプロセスが同一のメッセージを受信することが可能です。また、送信者の立場で考えると、キューの場合は複数のプロセスにメッセージを送りたい場合には、受信者の数と同じ数のキューを作成しそれぞれのキューににメッセージを送信するという複雑性をうみますが、トピックの場合は、1 つのトピックにメッセージを送るのみで完結するため、受信者の数の増減に依存せず構成が可能となります。

受信タイミング

キューの場合はポーリングしたタイミングでメッセージがキューにあれば受信できるのに対して、トピックは送信者がメッセージを送信した直後にメッセージが自動配信されるため、配信タイミングが異なります。

上記の違いを踏まえ、キューを使いたいケース、トピックを使いたいケースの例を挙げたいと思います。


例 1 : 任意のタイミング (バッチ処理等) でメッセージを一括処理したいケース

送信側が任意のタイミングでバラバラにメッセージを送信すると、受信側は、それに対応する処理、つまり常駐プロセスを起動し可用性を確保する必要があります。しかし、キューがキューイングをしてくれれば、バッチ処理をするときにキューから一括でメッセージを取得すればよく常駐して待機する必要がありません。このように受信側が任意のタイミングで受け取りたい場合、また、まとめて処理したいような場合にはキューが適しています。

例 2 : 受信側システムの稼働時間が、送信側と異なるケース

トピックを利用する場合、即時に受信者にメッセージが送信されます。その際に、受信者がメンテナンス等でダウンしていたらメッセージは処理されません。その場合、キューでキューイングし、受信者が処理できるタイミングで処理する方法が適しています。(ちなみに、第 3 回でご紹介する Amazon Simple Notification Service(SNS) には、送信先がダウンしている場合、リトライする機能があります)

例 3 : 複数の処理をシーケンシャルに処理をする必要がないケース

例えば、EC サイトで受注確定後に「配送処理」「メール通知処理」「請求処理」「ポイント計算処理」などの後処理がある場合、これらはシーケンシャルに順次実施する必要があるでしょうか ? 業務上、あるいは、システムの実行上の依存関係がなければ、「受注」イベントをもとに、並列で処理することが可能です。このような場合、下図のように「注文完了トピック」を作成し、「配送処理」「メール通知処理」などのバックエンドのプロセスが注文完了トピックを Subscribe していれば同じ受注情報が全Subscriver (購読者) に自動的に届き処理が並列で処理が可能となります。並列化することで処理完了までの時間の短縮も可能になりますし、いずれかのプロセスで問題が生じても、他のプロセスは影響を受けることなく処理ができ、影響を限定化することも可能です。

img_master-asynchronous-execution-02_08

例 4 : 受信者が増減する場合

例えば、例 3 のケースでは、注文処理サービスは、「在庫管理サービス」と「注文完了トピック」にデータを送り処理を完了させており、「メール通知サービス」「請求処理サービス」「ポイント計算処理サービス」とは疎結合です。疎結合化により、例えば、メール通知サービスが廃止されても、新たなサービスがトピックの Subscriber となりメッセージを受領するようになっても、注文処理サービスは影響を受けずに済み、追加・廃止対象のサービスで変更を完結できます。このように、トピックを入れることにより、バックエンドの機能追加・変更・廃止がより柔軟にできるようになります。

例 5 : キューイングをしつつ、同時に並列処理もしたいケース

上記の例では、トピックとキューを個別に適用したシーンを説明しましたが、トピックとキューを組み合わせて使うことでそれぞれの良い点を活かした構成も可能です。具体的には、「任意のタイミングで受領したい」バックエンドのプロセスにとっては、「キュー」を採用したいが、一方で、送信側としては、受信者の数に依存しない実装にしたい場合には、「トピック」にメッセージを送りたいと考えます。そのような場合には、下図のようにトピックの Subscriber をキューにすることで、送信者はトピックにメッセージを送り、受信者はキューからメッセージを受信するという方法を実現することが可能です。これは、Fan-out (扇を広げる形) と呼ばれるパターンです。

img_master-asynchronous-execution-02_09

4.まとめ

当記事では、非同期処理や同期処理の処理のステップを説明し、非同期処理の処理のステップを実際に実現するうえで必要な要素を説明しました。また、非同期処理化は、メリットもありますがステップが増えることも説明しました。さらに、同期処理では出てこなかったキューやトピックの概念も今回登場し、キューやトピックには、高い可用性やスケーラビリティが必要なことも説明しました。

次回の記事では皆さんが非同期処理を設計・構築する際にお勧めの AWS サービスを取り上げ、非同期処理を非同期処理のアーキテクチャの実現方法を説明します。

ぜひ、お楽しみに。

この連載記事のその他の記事はこちら

選択
  • 選択
  • 第 1 回 非同期処理ってなんだろう ?
  • 第 2 回 非同期処理と同期処理の処理構造
  • 第 3 回 AWS サービスを活用した非同期処理アーキテクチャ

builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

photo_horiba-takafumi

堀場隆文
アマゾン ウェブ サービス ジャパン合同会社
プロフェッショナルサービス本部 プラクティスマネージャ

クラウドやモダンな開発技術を活用してお客様のビジネス課題の解決やイノベーションの加速化をご支援するデジタルトランスフォーメーションチームをリード。
三児の父。娘とスイーツを食べに行くのが楽しみ。

メンバー登録で毎月抽選で無料クーポンを入手できます

さらに最新記事・デベロッパー向けイベントを検索

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する