Amazon Web Services ブログ

Amazon MSK を使用したログベースのアーキテクチャでウェブコンテンツをストリーミングする

ニュース速報やスポーツのスコアなどのコンテンツは、ほぼリアルタイムで更新する必要があります。最新の状態に保つために、ブラウザまたはモバイルアプリをよく更新しているかもしれません。このコンテンツを高速かつ大規模に配信する API を構築することは、困難なことがあります。この記事では、API ベースのアプローチの代替案を紹介します。この記事では、ログベースのアーキテクチャ、つまりコミットログを使用してデータの変更をキャプチャし、他のサービスの完全なデータセットに新しいサービスとデータベースを簡単に構築するソフトウェアアーキテクチャの概念と長所を概説します。このようなデータ変更は、ウェブのコンテンツにもなります。このアーキテクチャでは、このコンテンツをリアルタイムでストリーミングできます。シンプルで拡張も簡単です。

次の動画クリップは、このアーキテクチャの実用例を示しています。

この記事では、Amazon Managed Streaming for Apache Kafka (Amazon MSK) を使用してログベースのアーキテクチャを構築する方法と、ウェブでコンテンツをストリーミングするために必要なその他のテクノロジーをご紹介します。また、マイクロブログサービスの例もご紹介して、すべてを実行に移します。詳細については、GitHub リポジトリをご覧ください。これには、ウェブアプリケーション、Amazon Elastic Container Service (Amazon ECS) の AWS Fargate で実行されるバックエンドサービス、および AWS クラウドでインフラストラクチャを構築するための AWS CloudFormation テンプレートが含まれています。この例は、AWS Cloud9 またはローカルマシンで実行できます。

プッシュモデルの利点

ほとんどのウェブサイトおよびモバイルアプリケーションは、API からコンテンツをプルすることによってコンテンツを配信します。クライアントは、バックエンドで新しいリクエストを行うことにより、新しいコンテンツまたはコンテンツの更新をプルする必要があります。一般的な動作は、ブラウザウィンドウの更新、またはモバイルアプリでのプルトゥリフレッシュです。他にも、定められた間隔で新しいコンテンツを取得するためにサーバーをポーリングする動作があります。これは、プルモデルと呼ばれています。

クライアントがこのモデルを使用すると、コンテンツを更新するたびにサーバーへの新しいリクエストが作成されます。すべてのリクエストはアプリケーションにストレスをもたらします。データベースまたはキャッシュの更新を確認し、データをクライアントに送信する必要があります。これは、最終的にサービスへの新しい接続を作成するのに必要な量を超えて、CPU とメモリを消費します。

別のアプローチは、サーバー側からクライアントを更新することで、これはプッシュモデルと呼ばれています。サーバーは新しいコンテンツまたは更新をクライアントにプッシュします。これは、サーバーとクライアント間の非同期通信です。次の図は、この非同期通信の一般的なパターンであるパブリッシュ/サブスクライブ (pub/sub) メッセージングのアーキテクチャを示しています。

pub/sub パターンでは、新規または更新されたコンテンツがイベントです。そのようなコンテンツはイベントのパブリッシャーから発信され、サブスクライバーに配布されます。このパターンはリーダーと pub/sub サービスで使用されます。これにより、リーダーはサービスにサブスクライブし、サービスが新しい記事を公開するのを待ちます。pub/sub サービスは、記事ログをサブスクライブして、エディタが発行した記事を消費します。pub/sub サービスにサブスクライブしているリーダーは、サービスへの接続を開いたままにして、新しい記事が流入するのを待つだけで済みます。マイクロブログサービスの例では、読者はシンプルな React アプリを使用して記事を読みます。ウェブアプリケーションは、バックエンドサービスへの接続を開いたままにし、新しい記事が公開されるのを待ち、公開時に表示される記事を更新します。

新しい記事がログに公開されると、記事はメッセージとして保存されます。メッセージはキーと値のペアで、ログはメッセージが格納された順序を保持します。具体例のユースケースの場合、ここに格納されるメッセージは記事ですが、シリアル化されたバイトとして格納されるため、どのような種類のデータでもかまいません。最も一般的な形式は、プレーンテキスト、JSONProtobufAvro です。pub/sub サービスは、メッセージが流れ込むときにメッセージを消費し、接続されたクライアントにメッセージを公開します。繰り返しになりますが、これはブラウザのウェブアプリケーションまたは iOS か Android のモバイルアプリケーションです。この例では、ブラウザの React アプリです。サブスクライバーは、コンテンツを取得することなく、新しい記事を受け取ります。

コンテンツをクライアントにプッシュするこの動作は、コンテンツストリーミングと呼ばれます。クライアントが待機してメッセージのストリームから読み取るためです。このようなメッセージには、公開されたコンテンツが含まれています。これは、動画ストリーミングに似ており、動画は連続して分割されて視聴者に配信されます。

ログベースのアーキテクチャの長所

ユーザーがコンテンツにアクセスできるようにするための一般的なアプローチは、API を構築することです。長い間、これは RESTful API でした。最近では、GraphQL ベースの API が勢いを増しています。ただし、API ベースのアーキテクチャには、次のようにいくつかの問題があります。

  • 多くのコンテンツ制作者は、データを表すために独自のスキーマを定義しています。名前とフィールドはスキーマ間で異なります。特にエンドポイントの数が増加し、API が進化するにつれて、それが顕著になります。
  • 利用可能なエンドポイントの動作が異なります。エンドポイントは異なるセマンティクスを使用し、異なるリクエストパラメータを持っています。
  • 多くの API には、新しいコンテンツや更新についてクライアントに通知する機能が含まれていません。この動作を行うには、追加の通知サービスまたは広範なポーリングメカニズムが必要です。

この記事のアプローチは、ログベースのアーキテクチャを使用して、ウェブやモバイルでコンテンツをストリーミングすることです。アイデアは、コンテンツを保存および配布するための一般的なメカニズムとしてログを使用することです。詳細については、「Apache Samza を使用してデータベースを裏返しにする」と「データ集約型アプリケーションを設計する」を参照してください。

多くの異なるログ技術が存在する可能性がありますが、Apache Kafka は業界標準になり、豊富なエコシステムを構築しました。Amazon MSK は昨年一般リリースされた、Apache Kafka を使用するアプリケーションを構築するためのフルマネージドサービスです。この記事で実行するサンプルのマイクロブログサービスでは、Amazon MSK を使用します。Amazon MSK はトピックのパーティションのログに公開された記事を追加します。パーティションを使用すると、トピック内のデータを分割することにより、トピック内のメッセージの規則正しい処理を並列化できます。記事のグローバルな順序を保証する必要があるため、マイクロブログの例のトピックには 1 つのパーティションしかありません。複数のパーティションでは、パーティション内の順序のみが保証されます。この例の pub/sub サービスは、ログを時系列で読み取り、サブスクライバーにその順序で公開することで記事を消費します。

ログを使用して記事を保存することには、従来のデータベースシステムと比較して利点があります。第一に、ログはスキーマレスです。単純なキーと値のペアと追加のメタデータを格納します。値は任意の種類のバイナリエンコードデータにすることができます。つまり、プロデューサーとコンシューマーには、格納された値をシリアル化および逆シリアル化する責任があります。したがって、ダウンタイムを発生させることなく、長期にわたって保存されたデータを簡単に変更できます。

ログのバックアップと復元も簡単です。Amazon Simple Storage Service (Amazon S3) のオブジェクトとしてログに単一のメッセージを消費および保存し、新しいログに再発行することでメッセージを復元できます。メッセージの再生は、コスト効率が高く安全なオブジェクトストアから行われ、オブジェクトのバージョニングライフサイクル管理などの Amazon S3 の機能を使用します。

また、レプリケーションは、従来のデータベースシステムと比較してはるかに簡単です。ログは時系列になっているため、レプリカも常に正しい順序になっており、同期しているかどうかを簡単に判別できます。

さらに、派生ストア (たとえば、最新の記事) の構築は、この方法の方がはるかに簡単です。ログはこのようなストアを構築するために必要なすべてのものを表し、データベースは最新の状態を表します。ログベースのアーキテクチャは本質的に一貫しています。

ログは、システムで発生したすべてのイベントの順序付けられた表現です。イベント自体がシステムへの変更です。これは、新しい記事または更新された記事の場合があります。ログは、マテリアライズドビューを作成するために使用します。これは、AWS AppSync を介して GraphQL API を駆動する Amazon DynamoDB などの NoSQL データベース、またはデータの他の特定のビューにすることができます。ログ内のメッセージを再生することで必要な状態をいつでも再作成できるため、ログ内のデータはあらゆる種類のビューでマテリアライズできます。このようなビューは、最終的にデータを消費するために使用します。すべてのサービスは、独自のデータストアとデータのビューを持つことができます。データの表示に必要なだけのデータを公開できます。このようなサービスのデータベースは、より目的にかなったものになり、保守が容易です。あるインスタンスで DynamoDB を使用したり、別のインスタンスで Amazon Aurora を使用したりできます。ただし、マイクロブログサービスの例では、マテリアライズドビューを使用して記事を表示していません。ログとの間で直接パブリッシュおよび消費します。

ログベースのアーキテクチャは、現在のデータと将来のデータへのアクセスに区別がないため、コンテンツプロバイダーにとって魅力的です。コンシューマーは常にデータを再生しています。ログの読み取りを開始した場所から、現在および将来のすべてのデータを取得します。コンシューマーの現在の位置は offset です。これは、コンシューマーがログから読み取った最後のレコードを指す単純な整数です。ログ内のオフセットと最新のメッセージの間のメッセージ数は、lag です。コンシューマーがオフセットからメッセージの読み取りを開始すると、この時点からすべてがメッセージストリームに融合します。これを、ブラウザにデータをプッシュする機能を提供するプロトコルと組み合わせると、このようなメッセージをウェブにストリーミングできます。

gRPC を使用したコンテンツのストリーミング

gRPC は、あらゆる環境で実行できる高性能 RPC フレームワークです。gRPC は分散バックエンドシステムでサービスを接続するためによく使用しますが、ラストマイルの接続デバイス、モバイルアプリケーションやブラウザにも適用できます。RPC フレームワークにより、アプリケーションはリモートプロセスの関数を呼び出すことができます。 gRPC は、プロトコルバッファを使用して、サービスとそのリモート呼び出しを定義します。強力なバイナリシリアル化ツールと言語です。プロトコルバッファは、構造化されたデータをシリアル化してネットワーク経由で送信するための、Google の言語中立、プラットフォーム中立、拡張可能なメカニズムです。データの構造を定義し、ソースコード生成を使用して、データ構造のデータを簡単に読み書きするコードを作成します。サービスやクライアントでオブジェクトモデルラッパーを記述する必要はなく、多くの一般的なプログラミング言語をサポートしています。

この記事のマイクロブログの例では、バックエンドサービスに Go support と、サービスのクライアントである React アプリの grpc-web を使用しています。両方の言語のコードは、1 つの Protobuf 定義から作成されます。モバイルアプリケーションを開発している場合は、Swift および Android がサポートされます。JSON をオブジェクトにアンラップするためのオブジェクトモデルラッパーを排除することは、大きな利点です。

コンテンツをストリーミングするための最も重要な機能は、HTTP/2 ベースのトランスポートを使用した双方向ストリーミングです。この例では、サーバー側のストリーミング RPC を使用して、公開された記事を一覧表示します。クライアントはサーバーにリクエストを送信し、一連の記事を読み取るためのストリームを取得します。接続は維持されるため、サーバーは今後の記事をすべてプッシュし続けます。gRPC は、個別の RPC 呼び出し内のメッセージの順序を保証します。これは、記事がログから直接消費され、ログ内の記事が時系列順になるため、この例では重要です。

マイクロブログのサンプルサービス

ログベースのアーキテクチャの長所について学ぶことと、このパターンの上にサービスを構築することは別の話です。この記事では、パターンと gRPC を使用して、学習したすべてを例示する補完的なマイクロブログサービスを示します。詳細については、GitHub リポジトリをご覧ください。

このサービスには次の 2 つの主要コンポーネントがあります。

  • gRPC を介してバックエンドサービスに接続し、公開されているすべての記事を一覧表示して新しい記事を作成するシンプルな React アプリ
  • アプリが呼び出す gRPC サービスを実装するバックエンド

React アプリは、Amazon MSK のトピックに新しい記事を発行し、これらの記事がログに追加されるときにリストします。記事のリストは、ListArticles リモートプロシージャの呼び出しで、記事のトピックにサブスクライブし、最初からログを読み取ります。ログから読み取られると、ウェブアプリケーションにプッシュされます。

GitHub リポジトリには、必要なインフラストラクチャを作成してサービスをデプロイするための CloudFormation テンプレートも含まれています。次の図は、このサービスとインフラストラクチャのアーキテクチャを示しています。

このサービスは、Amazon Elastic Container Registry (Amazon ECR) を使用して Fargate の Docker イメージを格納します。pub/sub サービスは、Amazon MSK をコミットログに使用します。サービスは、AWS Cloud Map を介して検出されます。クライアントは、Application Load Balancer を介してサービスに接続します。このロードバランサーを使用するのは、接続のアイドルタイムアウトが長くなる可能性があるためです。これにより、クライアントで接続を管理しなくても、サービスからクライアントにデータをプッシュできます。

AWS Cloud9 で実行

開発マシンからサービスをデプロイして実行できます。試してみたい場合は、AWS Cloud9 を使用して実行することもできます。AWS Cloud9 はクラウドベースの統合開発環境(IDE)で、ブラウザを使用してコードを作成、実行、およびデバッグできます。

このチュートリアルを完了するための唯一の前提条件は、AWS アカウントを持っていることです。アカウントを作成する手順については、「新しい AWS アカウントを作成してアクティブ化する方法を教えてください」を参照してください。

環境を作成してサンプルを実行すると、AWS リソースが消費されます。AWS アカウントに継続的に請求を受けるのを回避するために、完了したらすべてのリソースを削除するようにしてください。

AWS Cloud9 環境の作成

AWS Cloud9 環境を作成するには、次の手順を実行します。

  1. AWS Cloud9 コンソールで、[Create environment] を選択します。
  2. 環境に「mskworkshop」という名前を付けます。
  3. [Next step] を選択します。
  4. [Instance type] では、[small] を選択します。
  5. すべてのデフォルト値を受け入れ、[Next Step] を選択します。
  6. 概要ページで入力内容を確認し、[Create environment] を選択します。

AWS Cloud9 は、それを介して作成された Amazon Elastic Compute Cloud (Amazon EC2) インスタンスに対して、30 分のデフォルトの自動ハイバネーション設定を提供します。この設定では、IDE を閉じてから 30 分後に EC2 インスタンスが自動的に停止します。それは、環境を再び開いたときにのみ再開します。

  1. 環境の準備ができたら、[Welcome] を閉じます
  2. 残りのタブから、[New Terminal] を選択します。

環境は次のスクリーンショットのようになります。

詳細については、AWS Cloud9 の環境での操作 を参照してください。

AWS Cloud9 環境の準備

チュートリアルを続行するには、最新バージョンの AWS コマンドラインインターフェイス (AWS CLI) をインストールし、必要なツールをインストールして、AWS Cloud9 環境のサイズを変更する必要があります。

  1. AWS CLI の現在のバージョンを表示するには、次のコードを入力します。

Bash $ > aws –version

  1. 最新バージョンに更新するには、次のコードを入力します。

Bash $ > pip install –user –upgrade awscli

古い pip バージョンに関する警告を無視して、インストールされている AWS CLI を確認できます。jq コマンドをインストールする必要があります。これは、サンプルスクリプトが使用する軽量で柔軟なコマンドライン JSON プロセッサです。

  1. 次のコードを使用してツールをインストールします。

Bash $ > sudo yum install -y jq

クライアントは Cloud9 環境で実行されます。現在のバージョンの Node.jsYarn package manager をインストールする必要があります。

  1. インストールされている Node.js を管理するには、Node Version Manager (nvm) を使用します。次のコードを参照してください。

Bash $ > curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

  1. 次のコードを使用して、環境内のバージョンマネージャーをアクティブにします。

Bash $ > . ~/.nvm/nvm.sh

  1. 現在のバージョンの Node.js をインストールするには、次のように nvm を使用します。

Bash $ > nvm install node

  1. 次のように、npm を使用して、最新バージョンの Yarn パッケージマネージャーをインストールします。

Bash $ > npm install yarn -g

AWS Cloud9 環境の準備が完了しました。次に、サンプルリポジトリのクローンを作成してサンプルを設定します。

サンプルリポジトリの複製と設定

最初に、リポジトリのクローンを作成するフォルダに変更する必要があります。次のコードを参照してください。

Bash $ > cd ~/environment

repository のクローンを作成し、次のソースコードを含むフォルダに移動します。

Bash $ > git clone https://github.com/aws-samples/aws-msk-content-streaming aws-msk-content-streaming && cd $_

Cloud9 環境でソースコードを正常に複製しました。

環境のサイズ変更

デフォルトでは、AWS Cloud9 には 8 GB のストレージがアタッチされています。サンプルサービスには、提供されたスクリプトが構築するさまざまな Docker コンテナが必要です。デフォルトのストレージよりも多くを消費します。したがって、アタッチされているストレージのサイズを変更する必要があります。次のコードを参照してください。

Bash $ > make resize

これにより、アタッチされているストレージのサイズが 20 GB に変更されます。これは、マイクロブログサービスには十分です。/dev/nvme0n1 デバイスが存在しないというエラーが発生した場合は、Nitro ベースのアーキテクチャで実行されていません。デバイスの交換手順については、「環境の移動または Amazon EBS ボリュームのサイズ変更」を参照してください。

micoblogging サービスには、bastion ホストのオプションが含まれています。bastion ホストは、このネットワークのリソースに安全にアクセスするために特別に設計された、ネットワーク上の専用コンピュータです。SSH 経由でこのホストにアクセスできます。

SSH キーの作成

SSH キーを生成するには、次のコードを入力します (必要に応じて、このキーを使用して Amazon MSK および Fargate コンテナにアクセスできます) 。

Bash $ > ssh-keygen

Enter を 3 回選択して、デフォルトの選択を採用します。次に、以下のコードを使用して、パブリックキーを Amazon EC2 リージョンにアップロードします。

Bash $ > aws ec2 import-key-pair --key-name ${C9_PROJECT} --public-key-material file://~/.ssh/id_rsa.pub

使用するスクリプトのキーを、KEY_PAIR 環境変数として設定します。これで、サンプルアプリケーションを AWS アカウントにデプロイする準備が整いました。

アプリケーションのデプロイ

ウェブアプリケーションを実行する前に、必要なサービスを AWS アカウントにデプロイする必要があります。デプロイスクリプトは 2 つの CloudFormation スタックを作成します。1 つのスタックは、コアサービス VPC、NAT ゲートウェイインターネットゲートウェイ、および Amazon MSK を備えたもの。もう 1 つのスタックは、Docker コンテナと Application Load Balancer を備えたアプリケーションスタックです。

これらのスタックをデプロイするには、次のコードを入力します。

Bash $ > make deploy

デプロイプロセスには時間がかかる場合があります。デプロイプロセスが完了すると、ウェブアプリケーションを起動できます。また、公開されているローカル開発サーバーを起動します。次のコードを参照してください。

Bash $ > make start

Compiled successfully!」というメッセージと開発サーバーへの URL が表示されたら、構築プロセスは完了です。ツールバーの [Preview]、[Preview Running Application] を選択して、ウェブアプリケーションのプレビューにアクセスします。これにより、ウィンドウとウェブアプリケーションのある分割ビューが開きます。

残念ながら、Application Load Balancer のカスタムドメインを設定していないため、このプレビューのコンテンツを投稿したり読み取ったりすることはできません。そのため、デプロイされたサービスには HTTP 経由でのみアクセスできます。ただし、AWS Cloud9 は安全な環境であり、コンテンツが HTTPS 経由で提供されることが期待されています。

サンプルを機能させるには、プレビューから完全な URL をコピーするか、または、ブラウザバーの横にある [Pop Out Into New Window] アイコンを選択します。

URL で、httpshttp に置き換えます。これで、HTTP 経由でサービスにアクセスできます。

新しいアイテムを作成して、サンプルをテストできます。タイトルを付け、コンテンツを追加します。完了したら、[Create Post] を選択します。接続の問題が原因でエラーが発生した場合は、ブラウザを更新してください。

クリーンアップ

サンプルの探求とデプロイされたリソースの発見が完了したら、最後のステップはアカウントをクリーンアップすることです。次のコードは、作成したすべてのリソースを削除します。

Bash $ > make delete

マシンで実行

ローカルマシンでサンプルを実行する場合は、以下の必要なツールをインストールする必要があります。

  • Docker
  • AWS CLI
  • js および Yarn パッケージマネージャー
  • Linux ユーザーランドと bash
  • GNU Make

それらをインストールすると、pub/sub サービスに必要な Docker コンテナを構築し、必要なインフラストラクチャを作成してコンテナをデプロイし、React アプリを実行して記事を作成して読むことができます。詳細については、GitHub リポジトリをご覧ください。

インフラストラクチャを実行してサンプルにアクセスするには、リポジトリをマシンに複製してフォルダを移します。次のコードを参照してください。

bash $ > git clone https://github.com/aws-samples/aws-msk-content-streaming aws-msk-content-streaming && cd $_

次のステップでは、バックエンドサービス、gRPC 呼び出し用の envoy プロキシ、サービスに必要なインフラストラクチャを構築します。Envoy プロキシは、ウェブアプリの gRPC-Web クライアント をバックエンドサービスの gRPC サーバーにブリッジするために必要です。ウェブアプリからの呼び出しはテキストエンコードされますが、バックエンドサービスはバイナリ protobuf 形式を使用します。デプロイを機能させるには、次の環境変数を設定する必要があります。

bash $ > export PROJECT_NAME=<YOUR_PROJECT_NAME>

bash $ > export AWS_ACCOUNT_ID=<YOUR_ACCOUNT_ID>

bash $ > export AWS_DEFAULT_REGION=<YOUR_AWS_REGION>

bash $ > export KEY_PAIR=<YOUR_AWS_EC2_KEY_PAIR>

<> を詳細、プロジェクトの名前、インフラストラクチャをデプロイするアカウント、スタックをデプロイするリージョンに置き換えます。

AWS Cloud9 環境と同じ CloudFormation スタックをデプロイするには、次のコードを入力します。

bash $ > make deploy

localhost:3030 で開発用ウェブサーバーを開始し、デフォルトのブラウザでこの URL を使用してブラウザウィンドウを開くには、次のコードを入力します。

bash $ > make start

クライアントは、環境変数 REACT_APP_ENDPOINT を使用して Application Load Balancer の URL に設定されます。

記事を作成する

これで、タイトルとコンテンツを含む新しい記事を作成し、ログに公開できます。新しい記事がプッシュされると、記事のリストが自動的に更新されます。タブを複製し、新しいタブで新しい記事を作成することで、これをテストすることもできます。

次の図は、クライアントの観点から見たソリューションの動作を示しています。

pub/sub サービスへのリモート呼び出しは、記事ログ内の記事のリストにクライアントをサブスクライブします。単方向 gRPC ストリームが作成されます。pub/sub サービスは、利用可能なすべての記事とすべての新しい記事をクライアントにプッシュします。envoy プロキシは、テキスト形式の grpc-web 呼び出しをフィルタリングし、それらを pub/sub サービスのバイナリ gRPC 呼び出しに変換します。記事の挿入は、pub/sub サービスへの追加の 単項 gRPC 呼び出しです。

まとめ

この記事では、ログベースのアーキテクチャと、それを使用してウェブ上のコンテンツをストリーミングする方法について説明しました。コンテンツをブラウザまたはモバイルデバイスにストリーミングする gRPC の長所についても説明しました。さらに、Amazon MSK で構築されたマイクロブログサービスを使用したログベースのアーキテクチャを実験しました。また、必要なインフラストラクチャをデプロイし、サンプルコードで実行する方法についても説明しました。

基本的なアイデアと提供されたサンプルを使用して、独自のソリューションを構築できます。構築したものをぜひ共有してください。または、AWS クラウドでのログベースのアーキテクチャの実行に関してご質問がある場合は、いつでもお寄せください。

 


著者について

Sebastian Doell は AWS のスタートアップソリューションアーキテクトです。彼は、スタートアップがアイデアを迅速かつ大規模に実行するのをサポートしています。Sebastian は、多数のオープンソースプロジェクトも維持しており、Dart と Flutter を推奨しています。