サーバーレスWebSocketサービス「Momento Topics」を構築するまで

サーバーレスのWebSocketサービス、Momento Topicsを構成するアーキテクチャを見てみましょう。

数ヶ月前、Momentoはフルマネージド・サーバーレスWebSocketサービス「Topics」をリリースしたました。このサービスは、あらゆるものを接続することを目的としています。バックエンド・サービスとバックエンド・サービス、バックエンド・サービスとユーザー・インターフェース、さらにはユーザー・インターフェースとユーザー・インターフェースを、文字通り2回のAPIコールで接続することができます👇。


// sends a message containing “hello world!”
await topicClient.publish("tutorials", "greetings", "hello world!");

そしてメッセージを受け取ること:

await topicClient.subscribe("tutorials", "greetings", {
	onItem: (message) => console.log(message.valueString())
});

リソース管理もコンフィギュレーションもなく、ただコードを書いて実行するだけです!

Amazon SQS、SNS、Kinesisのようなサービスもサーバーレス・イベント・メッセージング・サービスだが、バックエンド・コンポーネント間でイベントを送受信することに重点を置いています。

反面、Pusher、Amazon AppSync、AWS IoT Coreのようなサービスは、WebSocketのようにバックエンド・サービスとユーザー・インターフェイスを接続して使用することを意図しています。これらはWebSocketの複雑さの多くを抽象化した素晴らしいサービスですが、私たちMomentoは、リアルタイム通信を構築するための開発者体験を改善し続けることができると考えました。

そこで私たちは、あらゆるものがあらゆるものと通信できるようにする、極めてシンプルなサービス、Topicsを構築しました。

この高度にスケーラブルなサービスは、毎秒数百万トランザクション(TPS)という驚異的な低レイテンシーを誇り、プライムタイムの準備が整っています。

信じられないですか?このサービスがどのように構築され、何がこのサービスを輝かせているのかを見てみましょう。

イベントとサブスクリプション

まず、トピックを介してMomento Topicsに情報を公開したり購読したりするときに何が起こっているのかについて説明しましょう。トピックとは、パブリッシャーと購読者間の集中的なコミュニケーション形態であり、専用のチャットルームのようなものだと考えてください。

ユーザーインターフェイスやバックエンドサービスがトピックを購読するとき、Momento に何かが起こったときに通知するように指示します。具体的には、誰かがトピックにイベントをパブリッシュ (送信) したときに通知されます。サブスクライブのユースケースとしては、チームメイトがサインオンしたときや、インスタントメッセージを受け取ったときにエンドユーザーに通知することが考えられます。

Momento Topicsでは、サブスクリプションは、サブスクライバとMomentoサーバ間の長期間のgRPC接続です。これは電話を設定するようなものです。あなたのユーザーインターフェイスは、Topics サービスを呼び出し、そのサービスとのオープンな接続を持つようになり、両者間で即座に直接データを転送できるようになります。

SNSはアクティブな接続を維持しないので、これはAmazon SNSなどとは異なります。その代わりに、SNSの購読者は電話帳に追加され、イベントが発生したときに誰が呼び出されるべきかをサービスが知ることになります。その結果、レイテンシーは高くなるが、ステートレス通信が可能になります。ステートレス通信は、電子メールを送信したり、キューに何かを追加したりする必要があるときなど、リアルタイム性が要求されないときには非常に有効です。このような状況では、ワークフローの非同期性によりレイテンシーが高くなることは全く問題ありあせん。

Topicsが他のWebSocketサービスと異なるのは、転送プロトコルです。標準的なWebSocket接続は、wsまたはwssでデータを転送します。IoT デバイスは通常、MQTT 経由でデータを転送します。しかし、Momento TopicsはgRPCを使用しているため、データはHTTPで転送されることになります。これらのプロトコルの主な違いを見てみましょう:

WSS
Pros:全二重(同時双方向)通信を提供。リクエストにヘッダーやメタデータを必要としないため、帯域幅のオーバーヘッドが少ない。ブラウザやサーバー環境で広くサポートされています。
Cons:接続の設定と管理が複雑な場合があります。接続管理のためにサーバーリソースを大量に消費します。

MQTT
Pros:低帯域幅、高遅延、または信頼性の低いネットワークに最適化されています。様々なレベルのメッセージ配信保証を提供。遺言(LWT)を提供します。
Cons:ウェブブラウザではネイティブにサポートされていません。オーバーヘッド(キープアライブメカニズム)が追加されるため、wssに比べてレイテンシーが高くなります。

HTTP (gRPC)
Pros:HTTP/2を使用し、より高速なパフォーマンスを実現するための多重化とヘッダー圧縮をもたらします。双方向ストリーミングが可能です。
Cons:JSONやXMLの代わりにプロトコル・バッファを使ってシリアライズする。インテグレーターにとっては、RESTほどなじみがないかもしれない。

一方、イベントはほとんど説明の必要がありませn。イベント・ドリブン・アーキテクチャーについて私が聞いた中で最も良い説明は、Momentoのカンファレンス「MoCon」でエリック・ジョンソンが語ったものです。彼はこう言った。そして我々は反応する。イベントとはその “何か “のことなのです。

イベントは何でも表現できます。ブール値でも、エンティティ識別子でも、JSONオブジェクト全体でも、さらには写真でもかまいません。Momento Topicsでは、イベントのペイロードとしてバイト配列を提供します。すべての購読者は、あなたが提供する同じペイロードを受け取るので、イベントに適切に反応することができます。

さて、サブスクリプションとは何か、何を受け取るのかを理解したところで、アーキテクチャについて話しましょう。

サービス・アーキテクチャ

私にとってサーバーレスWebSocketを使う最大の理由は、ベンダーが提供するインフラ管理です。私はシステム管理者ではなく、アプリビルダーです。接続の負荷分散や自動スケーリング・グループの設定、利用率の計算や急増するワークロードの管理方法などは知りません(知りたくもありません)。

私が知っているのは、アプリケーションにビジネス価値を提供する方法です。私が時間とエネルギーを費やしたいのはそのことであって、インフラの心配をすることではありません。幸運なことに、Momentoのエンジニアたちはインフラについて熟知しており、深く気にかけています。ここでは、オンデマンド・スケーリング、使用した分だけの支払い、スケジュールされたダウンタイムなし、リソースのプロビジョニングなしといったサーバーレス機能を提供するために、彼らがどのようにTopicsを構築したかを覗いてみましょう。

Topicsは2層のアーキテクチャで構築されており、自分で接続を管理するよりも1000倍速く、優れたファンアウトを可能にします。2つの層とは、ルーティング層とストレージ層です。

ルーティング層

ティア1はルーティング・レイヤーです。このレイヤーは、メモリ内にMomentoエコシステムの完全な地形図を持っています。SDKからのリクエストに対応し、サブスクライバーへの接続を管理し、どのティア2ノードがトピックメッセージを保持しているかを計算します(詳細は後述します)。

このレイヤーは、高スループットのEC2インスタンスの自動スケールフリートです。Topicsサービスは、ラウンドロビンのロードバランシングパターンを使用して、受信サブスクリプションを管理します。キャパシティが特定の閾値に近づくと、Control Plane(Momentoの頭脳)が別のインスタンスをウォームアップし、エコシステムのトポグラフィを渡し、それをミックスに追加します。

ストレージ層

第2階層はストレージ層です。イベントがトピックにパブリッシュされると、ここに着地し、接続されたルーティングレイヤーのノードに配信されます。

このレイヤーには、アーキテクチャーの優れた部分があります。ストレージノードはルーティングレイヤーノードとだけ通信し、ルーティン グレイヤーノードはサブスクライバーと通信します。新しいサブスクリプションが追加されると、指定されたルーティングノードはどのストレージノードがそのトピックを所有しているかを計算し、そのストレージノードとの長期間のgRPC接続を確立します。

これは、ルーターがエンドユーザーのサブスクリプションで行っているのとまったく同じパターンです。つまり、Momentoサーバーは他のMomentoサーバーとサブスクライブし、可能な限り接続を再利用しているのです。例を見てみましょう。

上記の例では、3つのルーティング・ノードと2つのストレージ・ノードがある。5人のエンドユーザーが「weather-alerts」キャッシュの「Dallas – TX – Hail storm」トピックを購読しています。決定論的ハッシュによって、トピックメッセージはストレージAに存在します。

接続が来ると、リクエストはルータ-A、ルータ-B、ルータ-Cに均等に分散されます。4番目のサブスクライバで、Router-AはStorage-Aに接続する必要があると判断するが、Storage-Aとの接続がすでに開かれていることを知っているので、接続は再利用されます!

同じことがサブスクライバ#5にも起こります。サブスクライバ#5はストレージノードとの 既存のコネクションを再利用します。メッセージがトピックにパブリッシュされると、ストレージノードはそれをオープンなコネクションにブロードキャストし、次にルーティングノードも同様に、サブスクライバーへのオープンなコネクションにブロードキャストします。

これにより、少ない接続数と拡張性の高いファンアウトパターンが可能になり、その結果、非常に高速なメッセージ配信と、開発者にとって弾力的に拡張可能なエクスペリエンスがもたらされます。

トピックの位置の決定

Topicsサービスを構築する前の最初の会話では、開発者がトピックリソースを使用する前に作成する必要はないということで合意していました。チームは、それが開発者のエクスペリエンスを奪い、イベントメッセージングに不必要な部分だと感じていました。このサービスのゴールは、アプリケーション開発者がビジネス上の問題を解決するという1つの焦点に集中できるように、未分化の重労働をできる限り引き受けることでした。

そこでチームは、トピックリソースを作成してサーバー上のストレージロケーションを割り当てる代わりに、サブスクライバーのアカウントID、キャッシュ名、トピック名を使用してランデブーハッシュを使用し、ストレージノードを動的かつ明確に指すことを選択しました。

つまり、ルーティングノードがロードバランサーの内外にシャッフルされるとき、どのトピックがどこに存在するかという状態を維持する必要がありません。‍

結論

Topicsは、当社のキャッシュ・サービスと同じハードウェア上で動作します。意図的に低遅延、高スケーラブル、瞬時に準備できるように設計されています。

2層アーキテクチャを使用することで、サブスクリプションをファンアウトし、サブスクライバーの数に関係なく、低遅延のメッセージ配信を提供することができます。このようなアーキテクチャは、Momento Topicsが他のイベントメッセージングサービスと異なる点です。他の多くのサービスは、このような方法で実行する機能を持っていますが、そのほとんどは、あなたのためにそれを管理できません。

Momentoはできます。つまり、夜中の3時にメッセージが届いていないという電話がかかってくることはないので、安心して眠れるのです。それは私たちの責任です。

サブスクリプションはgRPC接続であり、電話のようなものであることを覚えておいてください。電話はステートフルであり、あなたと通話相手の両方が通話中でなければ機能しません。つまり、LambdaやCloudflareワーカーのようなサーバーレス関数でサブスクライブしようとしてもうまくいかないのです。関数の実行が終わると、コードは一時停止されます。これは、電話が保留になり、最終的に切断されることを意味します。ブラウザ、コンテナ、サーバーのようなステートフルな環境で使ってください。

とはいえ、どこからでもパブリッシュできます。データを公開しても接続は設定されないので、すべての機能で自由に使用できます。

私たちは、あなたがトピックスで成功することを望んでいます。ご質問、ご意見、ご不明な点がありましたら、お知らせください。私たちはオープン・アーキテクチャを信条としています!どのように機能するかを喜んで共有させていただきます。

チームへのご連絡は、ウェブサイトから直接お願いします。あなたからのご連絡と、あなたが作るものを見るのを楽しみにしています!

Happy coding!