re:Invent 2024: AWSが語るElastiCache Serverlessの高性能化戦略
はじめに
海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!
📖 AWS re:Invent 2024 - Optimizing for high performance with Amazon ElastiCache Serverless (DAT327)
この動画では、Amazon ElastiCache Serverlessのキャッシング戦略と実装方法について詳しく解説しています。Cache-aside、Write-through、Write-behindなどの基本的なキャッシング戦略から、Time To LiveやVersion-basedなどのキャッシュ無効化手法まで、実践的な知見を共有しています。特に注目すべきは、2分ごとにスループットを2倍にできる高速なスケーリング能力と、Valkeyという新しいオープンソースエンジンの導入です。Valkeyは新しいスレッディングアーキテクチャにより、単一インスタンスで100万リクエスト/秒を実現し、わずか5秒で処理能力を8倍に拡張できる革新的な性能を持っています。
※ 画像をクリックすると、動画中の該当シーンに遷移します。
re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!
本編
ElastiCache Serverlessの概要とキャッシングの基本
みなさん、おはようございます。ElastiCache サイレントディスコへようこそ。朝早くからご参加いただき、また re:Invent へお越しいただき、ありがとうございます。本日は Amazon ElastiCache Serverless についてお話しさせていただきます。Serverless アーキテクチャでのキャッシングの実装方法について説明し、さらに ElastiCache Serverless の構築方法や、この1年間で実施した改善点について詳しく見ていきます。これにより、システムの仕組みをより深く理解し、皆様のアプリケーションでより効果的に活用していただけるようになるはずです。
まず、キャッシングとは何かについてお話ししましょう。最も基本的な形では、緊急に必要になったときに取りに行く時間や遅延が許されない場合に、すぐに使えるところに何かを置いておくことです。これが本質的なキャッシングの考え方です。つまり、高頻度の使用に耐えられ、かつデータの取得に非常に低いレイテンシーを提供できる、すぐにアクセス可能な場所にデータを配置するのです。
キャッシングを行う最も基本的な理由は、レイテンシーを削減することです。基本的な Serverless アーキテクチャを見てみましょう。クライアントが Amazon API Gateway と通信し、それが何らかのコンピュート(この場合は AWS Lambda)と通信し、さらに Amazon DynamoDB のようなデータストレージと通信する、という構成です。アプリケーションが遅くなる理由は複数考えられます。例えば、非常に重いクエリを実行している場合や、大量のトラフィックでデータベースが過負荷になっている場合があります。また、ネットワークの輻輳によってレイテンシーが積み重なることもあります。アプリケーションが遅くなる理由は様々ですが、キャッシュを使用することでこのレイテンシーを削減できます。
キャッシングを行うもう一つの一般的な理由は、読み取りの多いワークロードへの対応です。通常、読み取りが多い場合、アプリケーションは同じデータを何度も読み取ることになります。例えば、セッションデータを考えてみましょう。ユーザーがeコマースアプリケーションを使用していて、ページを何度もリロードする場合、カートデータやログイン情報といったセッションデータをデータベースから何度も読み込む必要はありません。この場合、データベースに複数のリードレプリカを追加することも可能ですが、それはすぐにコストが高くなってしまいます。代わりに、キャッシュを追加してそのデータをキャッシュに保存することで、データベースのリードレプリカにコストをかけることなく、より高速に読み取りをスケールすることができます。
では、具体的なキャッシングの方法について見ていきましょう。Serverless アプリケーションにキャッシュを追加する際には、考慮すべき点が複数あります。画面に表示されているこれらのトピックについて、これから詳しく説明していきます。まずはキャッシング戦略から始めましょう。
キャッシング戦略とデータの一貫性
キャッシュの戦略には様々なものがあります。最も基本的な、あるいは多くの人が最初に採用する基本的な戦略は、Cache-asideまたはLook-aside Cachingと呼ばれるものです。この方式では、基本的にコンピュートシステムがキャッシュからデータを読み取ります。キャッシュからデータが取得できれば問題ありません。アプリケーションにデータを提供できます。もしキャッシュからデータが取得できない場合(これをキャッシュミスと呼びます)、データベースからデータを読み取り、そのデータをキャッシュに書き込みます。このアプローチの利点はシンプルさにあります。データベースに特別なロジックを実装する必要がなく、キャッシュを別の場所や別のシステムに配置できるため、アプリケーションへの実装が非常に簡単です。アプリケーションロジックの変更も比較的単純です。また、コンピュートレイヤーがキャッシュに何を入れるかを制御するため、キャッシュサイズもコントロールできます。ただし、このアプローチにはいくつかの欠点もあります。特別なキャッシュ事前準備のロジックを実装しない限り、最初の読み取りは必ずデータベースからの読み取りが必要となるため、ほぼ常に遅くなります。また、データベースとキャッシュの2か所にデータが保存されるため、データの一貫性を確保するのが難しくなります。
もう一つのアプローチは、Write-through Cacheと呼ばれるものです。この方式では、データがキャッシュとデータベースの両方に同時に書き込まれます。利点は、キャッシュとデータベースに同時に書き込むため、キャッシュ内のデータがほとんど古くなることがないことです。同時に書き込みを行うため、データの一貫性も比較的良好です。つまり、データベースが更新されると同時にキャッシュも更新されるということです。
また、このシナリオではキャッシュミスが発生する可能性も低くなります。欠点としては、2つのシステム(データベースとキャッシュ)に書き込むため、書き込み速度が遅くなることが挙げられます。通常はデータベースの方が遅いため、2つのシステムのうち遅い方が書き込み速度を決定することになります。 また、あまり注目されていない欠点として、データベースに書き込まれるすべてのデータがキャッシュにも書き込まれるため、キャッシュサイズが非常に大きくなる可能性があります。
もう一つのアプローチは、Write-behind Cachingと呼ばれるものです。これはWrite-through Cachingの改良版あるいは最適化版とも言えるもので、まずキャッシュにデータを書き込んでアプリケーションに応答を返し、その後、非同期でデータベースにデータを書き込みます。この方法では、2つのデータコミットを待つ必要がなくなるため、書き込みが高速になります。通常メモリへの書き込みとなるキャッシュに書き込んだ後、アプリケーションに応答を返します。これにより書き込みのレイテンシーが改善され、書き込みスループットの高いアプリケーションに特に有効です。
Write-behind Cachingの欠点は明らかです。コンピュートに非同期の書き込み戦略を実装する必要があるため、実装が複雑になります。書き込みが失われないようにする必要があり、将来的にはデータベースに確実に書き込まれるようにしなければなりません。非同期で書き込みを行うため、何らかの理由でコンピュートが失敗してデータベースに書き込めない場合、データが失われるリスクがあります。また、非同期での書き込みによってデータの不整合が発生する可能性もあります。
データの一貫性維持とキャッシュの実装方法
データの一貫性について話しましょう。これまで、様々な基本的なキャッシング戦略を使ってキャッシングを実装する方法について説明してきました。キャッシュの無効化、つまりキャッシュの一貫性やデータの一貫性は難しい問題です。簡単な問題ではありませんし、完全な解決策があるわけでもありません。考慮しなければならないトレードオフが存在します。
最も基本的なキャッシュ無効化と一貫性の確保の方法は、時間ベースのアプローチです。基本的に、キャッシュから読み取りを行いキャッシュヒットした場合、一定時間後にキャッシュデータのTime To Live(TTL)が期限切れになります。Amazon ElastiCache Serverlessを含むほとんどのキャッシュでは、すべてのデータにTTLを設定できます。その時間が経過すると、キャッシュは自動的にそのデータを削除し、キャッシュミスが発生します。その時点でデータベースから読み取りを行い、それをキャッシュに書き込みます。これが時間ベースの基本的なキャッシュ無効化で、ほとんどのキャッシュではすぐに利用できる機能として実装されています。
適切なTTLを選択する面では実装が複雑になる可能性がありますが、多くの場合、ロジックを組み込むことはできるものの、判断に基づいて決定することが多いでしょう。例えば、ログインしているユーザーのセッションデータがあるとします。この場合、合理的なTTLを設定できます。通常、セッションは数分間続くので、ユーザーにその時間後に再ログインしてもらいたくない場合は、1時間のTTLを設定できます。しかし、アプリケーションに応じてTTLを決定するのは難しい場合があり、TTLが期限切れになるまでデータは削除されないため、データベースとキャッシュ間の一貫性を管理するのは課題となります。
もう一つのアプローチは、イベントベースの無効化です。この場合、データを無効化するための独立したシステムを構築します。この例では、DynamoDB Streamsを読み取ってデータの変更を検知する新しいAWS Lambdaレイヤーを設けています。データが変更されると、このLambdaがキャッシュデータを無効化または更新します。これにより、より速い一貫性を実現できます。ただし、データベースでデータが更新され、それがコンピュートシステムにイベントとして伝わり、その後キャッシュが更新されるため、依然として非同期的です。通常はミリ秒か数秒程度で処理され、TTLアプローチよりもはるかに速いデータの一貫性を実現できます。また、この方法では基盤となるデータベースに変更がある場合にのみデータが削除されます。
ただし、これは実装の複雑さを増加させます。キャッシュ無効化を行う独立したシステムが追加され、それをテストしてデプロイする必要があります。アーキテクチャにシステムを追加すると、コードの面だけでなく、テストやエンドツーエンドの機能確認の面でも複雑さが増します。
非同期システムにおいて、特にストリームやデータベースからの非同期更新に依存している場合、これは特に困難な課題となります。
今回説明する中で最も複雑ですが、精度の高い方法が、Version-basedインバリデーションと呼ばれるものです。この方式では、キャッシュ内の各キーのバージョンを管理します。データを読み取る際には、そのキーの最新バージョンを読み取っていることを確認します。この手法は、複数のコンピュートシステムが同時にキャッシュと相互作用する高負荷なシステムで特に有効で、より高いレベルの一貫性を提供します。
このプロセスは、読み取りを開始し、キーのバージョンを読み取り、そしてキャッシュからそのバージョンのキーを読み取ることから始まります。書き込み操作時には、まずキーのバージョンを更新することで、次の読み取りで最新バージョンのデータにアクセスできるようにします。この方法により、データの一貫性をより迅速に確保できます。ご想像の通り、この手法の実装はやや複雑です。
このロジックはコンピュートシステム側で実装する必要があります。Amazon ElastiCacheなどのほとんどのキャッシュサービスは、バージョニングの機能を標準では提供していませんが、自身で実装することは可能です。キャッシュ内にデータとキーのバージョンの両方を設定し、管理することができます。実装の複雑さは増しますが、アプリケーションが古いデータを許容できず、データベースとキャッシュ間でより強い一貫性が必要な場合には、優れたソリューションとなります。
ElastiCache Serverlessの具体的なユースケース
ここまでキャッシング戦略とキャッシュデータのインバリデーションについて説明してきましたが、次はキャッシュにデータを保存する方法について説明しましょう。いくつかのユースケースを見ていき、ElastiCache Serverlessでそのデータをどのように保存できるかを探っていきます。キャッシングを始める多くのアプリケーションの基本的なバージョンは、セッション管理でしょう。ユーザーセッションデータをキャッシュに保存する場合、通常はHashデータ構造を使用します。ElastiCache ServerlessはValkeyとRedis OSSをサポートしており、どちらもHashのような高度なデータ構造をサポートしているため、そのキー内に複数レベルのデータや複数のサブ値を保存することができます。
Session管理では、例えば「session_user_123」のようなキーがあり、その中にauthToken、lastLogin、preferencesなどの複数のデータタイプを格納できます。ElastiCache Serverlessでサポートされているように、JSONフォーマットで保存することも可能です。JSONフォーマットを使用すると、より構造化されたアプローチが可能になり、データの検索やJSONドキュメントの一部を更新することが容易になります。
Rate limitingは、Serverlessアーキテクチャでよく使用されるパターンの1つですが、キャッシュをこの目的で使用できることは一見分かりにくいかもしれません。このコンセプトは、データベースや高コストのサービスコール、アクセス制御が必要なリソースなど、重要なリソースへのアクセスを制限することです。キャッシュ内の文字列としてデータを保持し、リソースにアクセスするたびにカウンターをインクリメントします。指定された時間枠内で、リクエスト数が設定値を下回っている限り、アクセスは許可されます。これはTTL設定によって制御されます。例えば、5秒間に10回のリクエストに制限したい場合、TTLを5秒に設定し、アクセスごとにカウンターをインクリメントして、カウントが10を超えた時点でアクセスを拒否します。
もう1つの重要なユースケースは、高コストな計算結果のキャッシングです。例えば、リレーショナルデータベースを使用する場合、eコマースアプリケーションで割引商品や高評価商品の詳細を在庫から取得するクエリを考えてみましょう。このクエリは複数のテーブルを跨いで複数のJOINを実行するため、非常に高コストになりやすく、データベースリソースを大量に消費します。ユーザーがウェブサイトで商品詳細を閲覧する際、ページを更新したり何度も同じページに戻ったりしても、データは頻繁には変更されないため、キャッシングに適した候補となります。
このデータはキャッシュ内にJSONドキュメントとして保存できます。基本的に、すべての商品詳細を取得してTTL(Time To Live)付きのJSONドキュメントとして保存しています。アプリケーションの要件に応じて、商品在庫が5秒、10秒、あるいは1分以内に変更されないと想定してTTLを設定できます。この方法により、高コストなクエリ結果をキャッシュに保存することで、データベースへの負荷を大幅に軽減できます。
もう1つのユースケースは、リーダーボードやランキングです。例えば、ゲームアプリケーションを開発していて、トップユーザーに関するデータを保存したい場合、Sorted Setsに保存することができます。ValKeyやRedis OSSに詳しい方なら、これらのデータ構造をご存知かと思いますが、Amazon ElastiCacheでサポートされている様々なデータ構造についてお話ししたいと思います。これまで、Look-asideパターン、Write-throughなどのキャッシング戦略、データの無効化方法、一貫性の維持方法、データの保存方法について説明してきました。これらは一般的なキャッシングの基本となる部分です。
ElastiCache Serverlessのインフラストラクチャと性能
それでは、キャッシングのインフラストラクチャの側面について説明しましょう。キャッシュをセットアップする際には、インフラストラクチャをどのように構築するかを検討する必要があります。ここで ElastiCache Serverless のようなマネージドサービスが重要になってきます。私たちは、多くの部分をバックグラウンドで自動的に処理できるようにサポートします。 ElastiCache Serverless で高いパフォーマンスを実現するために、私たちは多大な投資を行ってきました。マルチAZデータレプリケーションをサポートし、レプリカからの読み取り時には低レイテンシーを実現しています。アプリケーションが ElastiCache Serverless から読み取りを行う際、リクエストがどのアベイラビリティーゾーンから来ているかを検出し、ローカルAZのプロキシノードにルーティングして、ローカルAZの読み取りパフォーマンスを確保します。
私たちは数分以内(具体的には13分以内)にゼロから毎秒500万リクエストまでスケールアップすることができます。この速度がどのように実現されているかについては、Yaronが後ほど説明します。2分ごとにリクエストのスループットを2倍にすることが可能です。昨年のre:Inventで発表した時点では、10〜12分ごとにスループットを2倍にできましたが、その後の改善により、現在は2分ごとに実現できるようになりました。この図は、システムがどのように動作しているかを示しています。これについてもYaronが詳しく説明します。ElastiCache Serverlessアプリケーションのリクエストを処理するプロキシレイヤーがあり、クライアントアプリケーションは単一のエンドポイントと通信し、リクエストはローカルAZのプロキシノードにルーティングされます。
バックグラウンドでは、瞬間的なバースト容量のための垂直スケーリングと、時間の経過とともに容量を増やすための水平スケーリングの両方を自動的に制御しています。プロキシアーキテクチャには、画面に表示されているような追加のメリットがあります。プロキシレイヤーと通信しているため、基盤となるキャッシュノードのトポロジーとその変更をアプリケーションから抽象化することができます。つまり、トポロジーが変更されてもアプリケーションはキャッシュとの接続が切れることがありません。なぜなら、接続を維持する知能を持つプロキシノードに接続されているからです。
このアーキテクチャにより、ソフトウェアパッチの適用を自動的かつ透過的に行うことができるというメリットもあります。キャッシュのマイナーバージョンやパッチバージョンのすべてを、アプリケーションに影響を与えることなく、自動的かつ透過的に適用・更新することができます。
キャッシュをデプロイする際、容量管理は重要な考慮事項です。どれだけのキャッシュ容量が必要になるかを考える必要があります。通常、キャッシュ容量をデプロイする際には、過去6ヶ月や1年などの期間のピークを調べ、そのピークに対応できる容量を確保します。しかし、新しい使用パターンによってそれらのピークを超えた場合、十分な容量がなくなるか、その時点でキャッシュを手動でスケールする必要が出てきます。さらに、ピークがない時期にはキャッシュが十分に活用されていないにもかかわらず、そのコストを支払い続けることになります。
キャパシティ管理は、データセットのサイズを考慮する必要があるため、当初考えていた以上に困難です。先ほど説明したように、Cachingの戦略によって、Cacheのサイズはデータベースと同じくらい大きくなることもあれば、その一部になることもあります。アプリケーションのテストを実行したり、大規模な計算を行ったりしなければ、Cacheをどの程度の大きさにすべきかを判断するのは困難です。メモリ要件に加えて、1秒あたりのリクエスト数やスループットも考慮する必要があり、これらは異なるコンピューティング要件につながります。また、コマンドの複雑さもコンピューティング容量のニーズに影響を与えます。例えば、先ほど説明したLeaderboardの例では、何十万人ものプレイヤーの中からトップ3を常に見つけようとするアプリケーションの場合、メモリ上で処理が行われているとはいえ、その計算は非常にコストがかかります。
ペイロードのサイズによって必要なネットワーク要件が変わってきますし、接続要件もネットワーク要件に影響を与えます。Cacheに必要な容量を決めることは、決して単純な作業ではありません。ElastiCache Serverlessは、これらすべての問題を解決します。キャパシティ管理が不要という原則に基づいて設計されており、アプリケーションの成長に合わせて裏側で自動的にスケールアップし、適切なタイミングでスケールダウンします。使用量に応じた課金なので、スケーリングのタイミングを気にする必要はありません。Serverlessの原則に基づいて構築されたElastiCache Serverlessは、真の従量課金制を提供します。メモリ(メモリに保存されているデータ)とCacheで実行するリクエストに対して料金を支払います。リクエストを実行していない時は、コンピューティングの料金は発生しません。
ここまで、Serverlessがどのようにしてキャッシュのデプロイの基本的な部分を実現できるのか、そしてアプリケーションでのCachingをどのように実現できるのかについて、高いレベルで説明してきました。では次に、Serverlessの仕組みについてより深く掘り下げていきましょう。これにより、内部の仕組みについて理解を深め、アプリケーションでServerless ElastiCacheをより効果的に活用する方法を理解していただけると思います。ここからはYaronに引き継ぎたいと思います。
Valkeyエンジンとマルチスレッドアーキテクチャの革新
ありがとうございます、Abhay。皆さん、こんにちは。本日はご参加いただき、ありがとうございます。ここにいられることを大変嬉しく思います。私のチームがこの素晴らしい製品の背後にある技術を構築したので、Serverlessについて話すのはいつもワクワクします。今日は、私たちがすべての課題をどのように解決したのかについて、詳しくお話ししたいと思います。 まず、高レベルのアーキテクチャから始めて、その後各コンポーネントについて詳しく説明していきます。まず、アプリケーションはお客様のVPC上で実行され、私たちが提供するEndpointを通じてサービスVPCに接続されます。このEndpointはNode Balancerを経由して、Proxy Fleetに接続を分散させます。Proxyは、リクエストを適切なShardsにルーティングする役割を担っています。お客様から見ると単純な接続に見えますが、実際にはもっと洗練されています。ProxyはMultiplexing接続を使用しており、実際には1つのTCPチャネルで複数のクライアント接続を送信できます。
そのため、ネットワークバッチを操作するたびに、より少ないシステムコールで済みます。これによってパフォーマンスが向上し、Storage Nodeへの接続数を少なく抑えることができます。私たちのアーキテクチャは複数のAvailability Zone(AZ)にまたがっているため、高可用性をサポートしています。1つのAZが故障しても、データストアへのアクセスは維持されます。Route53を使用して、アプリケーションが実行されているのと同じAZのProxyにルーティングできるため、マイクロ秒単位のレスポンスタイムを保証できます。
Serverlessアーキテクチャの裏側には実は多くのサーバーが稼働していることに、驚かれるかもしれません。私たちの仕事は、これらのサーバーを適切に管理し、Serverlessのワークロードが増加するたびにスケールさせることです。キャッシュノードは、 CPU、メモリ、ネットワークなどの共有リソースを持つマルチテナント環境で実行されています。私たちは、お客様のワークロードを実行するための十分な容量とリソースが確保されているかを継続的にモニタリングしています。
アーキテクチャをもう少し詳しく見てみましょう。 クライアントアプリケーション、Proxy、そしてこの例では、マルチテナント環境のキャッシュノードを実行・共有する2台の物理ホストがあります。ご覧のように、各キャッシュノードは異なる量の使用量を処理できるため、物理ホストごとに異なる数のキャッシュノードが存在します。先ほど述べたように、私たちは十分な容量を確保するため、各物理ノードのハードウェア使用率を綿密にモニタリングする責任があります。
この例では、ネットワーク、CPU、メモリの使用率が一定のしきい値に達し、十分な容量を確保するために調整が必要になっています。私たちには複数の選択肢があり、その最初の一つがHeat Managementと呼ばれるものです。 Heat Managementの目的は、物理ホスト全体を調べ、それらをホットまたはコールドとして分類することです。これにより、どのキャッシュノードを他の物理ホストに移動する必要があるかを判断できます。ある物理ホストがしきい値に達すると、Heat Managementがそれを特定し、Power of Two Random Choicesというアルゴリズムを実装します。
私たちは常に2番目に負荷の高いノードを移動させます。1番目のノードは移動させません。なぜなら、それは高負荷を経験しており、おそらくスケールアウトの過程にあるためです。しかし、別の物理ホストに移動した際に十分なリソースを解放できるキャッシュノードを見つける必要があります。移動が完了すると、元の物理ホストの負荷が軽減され、フリート全体のバランスが保たれ、お客様のワークロードに十分なリソースが確保されます。
ライブイベントなどの際には、 バーストワークロードが発生することがあり、これらのスパイク的なワークロードを効果的に処理することが期待されます。Abhayが言及したように、私たちは今年、Serverlessを立ち上げ時よりもさらに高速にするために多大な投資を行いました。固定のメモリやCPUフットプリントを持たないプラットフォームテクノロジーを使用することで、迅速なスケールアップとダウンが可能になっています。 このテクノロジーは、オーバーサブスクリプション技術を活用することでコストを抑えながら優れたパフォーマンスを実現します。必要に応じて、メモリ、CPU、ネットワーク帯域幅を即座に拡張・縮小できるため、わずか5秒で30,000から230,000リクエスト/秒まで、8倍のサポート能力に瞬時にスケールアップすることが可能です。
Vertical Scalingについて説明したところで、次はHorizontal Scalingについて説明しましょう。 Horizontal Scalingには3つの主要なステージがあり、最初のステージは検知です。先ほど説明したように、私たちは常にCacheノードと物理ノードを監視し、ワークロードを実行するのに十分なリソースがあることを確認しています。
また、使用パターンを監視することで将来の需要を予測し、数分後に発生する使用量を予測することができます。これにより、ワークロードが発生する前にクラスターをスケールアップすることができ、需要が発生した時点で既に対応できる容量を確保できています。
2番目のフェーズはプロビジョニングと呼ばれます。Warm Poolingの技術を使用して、1分以内に新しいノードをプロビジョニングしてクラスターに接続することができます。Warm Poolingとは、既存のクラスターに接続できるよう、スタンバイモードでCacheノードが事前にインストールされたリストを用意しておくことです。例えば、特定のCPUしきい値に達した場合、既存のクラスターにCacheノードを動的かつ迅速に追加でき、すぐにクラスターの一部として機能し始めることができます。
スケーリングの最後のフェーズはデータの再バランシングと呼ばれます。この段階では、クラスターに接続された新しいシャードにデータを移動する必要があります。ElastiCacheでのデータの保存方法はスロットによって分割されており、各スロットがデータの範囲を表しています。元のシャードから新しいシャードへ、元のスロットからデータの移動を開始します。スロットレベルで使用状況を監視しているため、どのスロットがより多く使用されているかを判断し、それに応じてクラスターのバランスを取ることができます。今年、複数の異なるスロットとデータ範囲を並行して複数の異なるターゲットに移動する機能を導入し、2〜3分ごとに対応可能な1秒あたりのリクエスト数を2倍にすることができるようになりました。
接続性に関しては、ElastiCache Serverless製品への単一の論理的なエントリーポイントを構築することを決定しました。クラスタートポロジーの変更を抽象化できるProxyを構築しました。スケーリング、障害、パッチ適用、切断に関連するすべての処理はProxyが担当するため、クライアント側からは隠蔽されています。Proxyの主な役割は、クラスタートポロジーを常に監視し、リクエストを適切なシャードにルーティングすることです。分散トポロジーテーブルを維持し、常に最新の状態を保つことで、この責任をクライアント側から取り除いています。Proxyは、クライアントが単一の接続のみを管理すればよい一方で、バックグラウンドで数百の接続をサポートすることができ、30分以内にゼロから1秒あたり500万リクエストまでの大規模なスループットスケーリングを処理することができます。
レイテンシーに敏感で、強い整合性を必要としないアプリケーションの場合、ElastiCache Serverlessを使用してマイクロ秒単位のレスポンスタイムを実現できます。このシステムには、設定不要な2つのレプリカが組み込まれており、主に高可用性のためですが、読み取りスループットも向上させます。Proxyは、レプリカ自体への単一のエントリーポイントを提供し、常にローカルのAvailability Zoneからの読み取りを保証します。 Read from replicaで動作するようにチャネルを定義すると、Proxyは常に同じAvailability Zone内で最も近いノードからデータを読み取ります。これは、レプリカやプライマリーがビジー状態かどうかに関係なく、マイクロ秒単位のレスポンスタイムを実現します。サービス側で特別な設定は必要ありません。ElastiCache Serverlessへの TCP接続をRead from replicaオプションで動作するように設定するだけです。
コード例を見てみましょう。裏側では複数の異なる接続が存在する可能性がありますが、単一の接続だけでServerlessに接続していることがわかります。TLSを使用しているのは、これがデフォルトでありServerlessで動作する唯一のオプションだからです。また、Read from replicaも有効にしています。このチャネルをクラスターに確立すると、Proxyは可能な限り常にローカルのAvailability Zoneからデータを読み取ることを保証します。
この例では、3つのシンプルなコードブロックがあります。最初のブロックは、逐次的にデータをキャッシュにポピュレートします。2番目と3番目のブロックはキャッシュからデータを読み取りますが、1つ目は逐次的に、2つ目はパイプラインを使用したバッチ処理です。最初のアプローチは逐次的で、2番目はパイプラインを使用したバッチ処理です。裏側では、これらのリクエストはすべてProxyを通過します。Proxyはシャード間でリクエストを分散し、並行して発生する障害、接続、スケーリングの際にもシームレスな体験を確保します。さらに、ローカルのAvailability Zoneからデータが読み取られることを保証します。コードはシンプルなままです。なぜなら、その複雑さに対処する必要がないからです。すべてがProxy自体に委ねられ、ビジネスロジックをクライアント側だけに保持することができます。
ここで、キャッシュノード自体について説明し、その領域に投資してきた技術について詳しく見ていきたいと思います。まず、Valkeyについて紹介します。今年、Amazon ElastiCacheとMemoryDBは、Valkeyと呼ばれる3番目のオープンソースエンジンのサポートを発表しました。Valkeyは高性能なKey-Valueデータストアで、Redisのコミュニティ代替品です。既存のRedisオープンソースのコントリビューターによって構築され、Redis OSS 7.2のドロップイン代替として機能します。Linux Foundationによってホストされており、これによりValkeyが永久にオープンソースであり続けることが保証されています。
まず歴史を振り返ってみましょう。ValkeyとRedis open source は、シングルスレッドアーキテクチャとしてスタートしました。これは、コマンドを待ち、受信したら、ネットワークからデータを読み取ります。その後、コマンド自体を処理し、そのコマンドが要求する処理を実行します。最後にレスポンスを送り返します - これがイベントループで、非常にシンプルです。このデザインが選ばれた主な理由は、競合状態や同期が不要なシンプルさにあります。 スケールアウト時にはShare-nothingアーキテクチャの恩恵を受けることができ、キャッシュの一貫性も向上します。ただし、この技術では1秒あたり約20万リクエストを処理できますが、ほとんどの時間は読み書きのIO操作に費やされます。
実際に何が起こっているのか、視覚化してみましょう。メインスレッドがデータを処理している中で、データを処理するクライアントを追加していくと、最も遅いコマンドの処理が完了するのを待つ間、それらはキューで待機することになります。これがシングルスレッドのアーキテクチャであり、最終的に、最も遅いクライアントプロセスの完了を待つという、Head of line blockingの問題に直面することになります。
その後、IO処理を別のスレッドにオフロードするためにIO Threadsが導入されました。これによりワークロードを分散させ、IO処理時間が改善されました。しかし、この技術ではまだワークロードに応じてIO Threadsを動的に追加・削除することはできませんでした。このアプローチでは、IOがすでにオフロードされているため、ほとんどの時間がコマンド処理に費やされ、約40万リクエスト/秒程度まで処理できました。この設計における一つの問題点は、図に示されている待ち時間です。IOをオフロードするたびに、メインスレッドは完了を待って待機状態になり、パフォーマンス向上に使える時間が無駄になってしまいます。
今年、AWSはValkeyに新しいスレッディングアーキテクチャを提供し、単一インスタンスで100万リクエスト/秒を実現しました。この素晴らしい貢献は、3つの主要な側面によって達成されました:コマンドとIO操作の並列処理を可能にする非同期IOスレッディング、リアルタイムの使用状況に基づいてIO処理を複数のコアに分散させるインテリジェントなコア活用、そしてCPUキャッシュミスを最小限に抑えるためにアクセスデータをプリフェッチしてメモリアクセスパターンを最適化するコマンドバッチング処理です。
システムは現在、ワークロードに応じてIO Threadsを生成する複数のスレッドで動作しており、必要に応じてスレッドの追加・削除が可能です。クライアント接続とIO Threadsの間のアフィニティを維持することで、同期の問題が発生しないようにしています。また、各アウトレットが十分なワークロードを処理できるようにすることで、ハードウェアを完璧に活用し、100万リクエスト/秒を達成することができます。メインスレッドとIO Threadsで視覚化してみましょう。メインスレッドとIO Thread があり、どんどんデータを処理していきます。
最終的に、すべてを並列で処理することができ、ワークロードが落ち着いてきたらIO Threadsの一部を削除することもできます。この技術、サーバー技術、そしてValkeyとIO Thread技術がどのように連携するのか見てみましょう 。ここにProxyとクライアント、待機中のWarm pool、そして物理ホストがあります。この例では、CPUのみが接続された単一のキャッシュノードのみを示しており、定常状態のワークロードを処理するためのIO Threadsが2つだけある状態を想定しています。
30,000のワークロードを実行していると仮定して、あるタイミングでバイラルなライブイベントや予期せぬワークロードが発生し、 1秒あたり230,000まで急増したとします。すぐさまキャッシュノードのスケールアップを行い、 マルチスレッド環境でこのキャッシュノードが消費しているメモリを含め、より多くのCPUを割り当てます。ワークロードを処理するためにIOスレッドを追加し、わずか5秒で8倍までの増加に対応できます。 その直後、Warm Poolにクラスターへの物理ホストの追加を要求し、迅速に接続してスケールアウトプロセスを開始できるようにします。
この時点で、Proxyが新しいノードを識別し、クラスターに接続します。データの再バランシングが行われ、シャード間で同時にデータが移動し、ワークロードを処理するためのリソースが増強されます。この技術により、2〜3分で対応可能なRPSを2倍にし、30分で0から500万リクエスト/秒まで拡張し、5秒で処理能力を8倍にすることができます。ここで、簡単な実演をお見せしたいと思います。 最初は250リクエスト/秒の安定した状態から始めて、徐々にクライアントと接続数を増やしていきます。
トランザクション/秒のグラフが上昇していく様子を見ていただくと、より多くのワークロードを簡単かつ迅速に処理できることがわかります。 ご覧の通り、P50レイテンシーは多少の変動がありますが、1ミリ秒未満を維持しています。 マイクロ秒レベルを保っているのです。P99では、ワークロードを追加するたびに システムが飽和状態に達するため、3ミリ秒程度のジャンプが見られます。素晴らしいのは、読み取りレイテンシーをマイクロ秒レベルに保ちながら、 300万トランザクション/秒まで到達できることです。
ElastiCache Serverlessのベストプラクティスと結び
最後に、ElastiCache Serverlessをより効果的に活用するためのベストプラクティスをご紹介します。 最高のパフォーマンスを得るために、長期的な接続の使用を常に推奨しています。TCP接続の確立は、一般的なValkeyコマンドと比較して潜在的にコストがかかることを理解しておくことが重要です。例えば、単純なGetコマンドやSetコマンドの処理は、既存の接続を使用する場合、処理速度が桁違いに速くなります。また、Read from Replicaについても説明しましたが、読み取りスループットを拡張しマイクロ秒レベルの応答時間を達成するには、Read from Replicaを使用する必要があります。エンジン全体をスキャンする高負荷なコマンドは避け、もちろんネットワークやCPUサイクルに影響を与える大きなオブジェクトも制限すべきです。
Serverless ElastiCacheサーバーを キャッシングに使用する場合、キャッシュサイズを管理するためにTime to Liveを使用するのが一般的です。Time to Liveを定義する際は、グローバルではなくアイテムごとに設定します。削除には相対オプションまたは固定オプションの使用を推奨しますが、最も重要なのは、ランダムなジッターを使用して削除を複数の異なるスライディングウィンドウに分散し、一度にすべての削除を行ってElastiCacheサーバーに負荷をかけないようにすることです。以上です。セッションをお楽しみいただけたと思います。Abhayに戻したいと思います。
Yaron、ありがとうございました。マルチスレッドアーキテクチャを使用して高速にスケールする新しい方法について、興味深く学んでいただけたのではないかと思います。ご来場いただき、お時間を取っていただき、ありがとうございました。re:Inventアプリでのアンケートへのご協力をお願いいたします。スピーカーや内容に関するフィードバックをいただくことで、今後より良いプレゼンテーションを提供することができます。少し早めに終わりましたので、外でお待ちしております。ご質問がございましたら、の外でお待ちしておりますので、お気軽にお声がけください。お時間を取っていただき、誠にありがとうございました。
※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。
Discussion