今日は打ち上げにふさわしい日です。皆さんが待ち望んでいた打ち上げです。どうして私が知っているかって?あなたたち自身が言っていたじゃないですか!
新しいキャッシュ可能なデータタイプの発表ってどうですか?3つの新しいキャッシュ可能なデータ型はどうだろうか?
「オブジェクトや順序付きリスト、ユニークな要素のセットをキャッシュできたらいいのに」と思っている方に朗報です!今日からできるようになりました!
以下のコレクション・データ・タイプのサポートをリリースします:
・List– 要素の重複を許容し、挿入された順番を記憶する配列。
・Set – 個々の要素を格納する、順不同の配列。
・Dictionary -フィールド/値オブジェクトのストア。
コレクション・データ・タイプの使用方法
超クールな響きですよね。でも、実生活でどうやって使ったらいいんでしょうか?
Lists
リストは、要素を追加した順番に永続化されます。また、要素の重複も許されます。架空のゲーム「どんぐり狩り」のチャットルームを見てみましょう。プレイヤーは、できるだけ多くのどんぐりを集めるために、時間を計って協力します。ゲーム中、プレイヤーはチャットでお互いの連携をとることができます。ゲームが終わると、チャットの履歴は消えます。
プレイヤーチャットは時系列にリスト化する必要があります。MomentoのリストはlistPushBack
関数呼び出しでこのユースケースを完璧に解決します。チャットにメッセージを追加するこのエンドポイントを考えてみましょう。
//
// POST /games/{gameId}/messages
//
exports.handler = async (event) => {
const { gameId } = event.pathParameters;
const { userName } = event.authorizer.context;
const input = JSON.parse(event.body);
const message = { userName, message: input.message };
const momento = await getMomentoClient();
await momento.listPushBack(CACHE_NAME, gameId, JSON.stringify(message));
return { statusCode: 201 };
}
リストから要素をフェッチするには、listFetch
コマンドを1回呼び出せばいいのです。
//
// GET /games/{gameId}/messages
exports.handler = async (event) => {
const { gameId } = event.pathParameters;
const momento = await getMomentoClient();
const chat = await momento.listFetch(CACHE_NAME, gameId);
const messages = chat.valueListString().map(message => JSON.parse(message));
return {
statusCode: 200,
body: JSON.stringify(messages)
};
}
こうして、機能的なチャット・ルームが完成しました!チャットメッセージを見るためにGETエンドポイントを呼び出すと、次のようなレスポンスが返ってきます:
[
{
userName: 'mo-the-squirrel',
message: 'Can someone get that acorn by the big oak tree?'
},
{
userName: 'honey-badger',
message: 'on it'
},
{
userName: 'mo-the-squirrel',
message: 'thx!!'
}
]
単純さに騙されてはいけません。リストは強力な構造体であり、リストの前や後ろから要素を取り出したり、一度に複数の要素を追加したり、特定の値を持つ要素をすべて削除したりといった便利な追加機能があります。
Sets
セットは要素の明確なコレクションを提供します。順序は重要ではないが重複排除が重要な場合は、集合を使うことになります。
ゲームの例に戻ると、セットを使ってゲームのプレーヤーを追跡することができます。プレーヤーは同じゲームに2度参加することはできず、プレーヤーの順番は関係ありません。プレーヤーの1人が断続的に接続を切断し、ゲームに再接続し続ける場合、セットはそのプレーヤーを2度含まないことを保証するため、非常に価値があります。
ゲームにプレーヤーを追加する場合は、setAddElement
コマンドを使って実装できます。
//
// POST /games/{gameId}/players
//
exports.handler = async (event) => {
const { gameId } = event.pathParameters;
const { userName } = event.authorizer.context
const momento = await getMomentoClient();
await momento.setAddElement(CACHE_NAME, gameId, userName);
return { statusCode: 201 }
}
このエンドポイントが何度再試行され、あるいは呼び出されたとしても、その1つのユーザー名がこのゲームのセットに含まれるのは1回だけです。
すべてのプレーヤーを取得するには、fetchSet コマンドを使用してエンドポイントを実装します。
//
// GET /games/{gameId}/players
//
exports.handler = async (event) => {
const { gameId } = event.pathParameters;
const { userName } = event.authorizer.context
const momento = await getMomentoClient();
const playerSet = await momento.setFetch(CACHE_NAME, gameId);
const players = Array.from(playerSet.valueSetString());
return {
statusCode: 200,
body: JSON.stringify(players)
};
}
選手リストのGETを呼び出すと、全選手のユーザー名の配列が得られます。
[
"mo-the-squirrel",
"honey-badger",
"nuts-about-nuts"
]
重複もナンセンスもありません。ただ、必要な情報を正確に備えた要素の正確なコレクションです。
Dictionaries
コレクションデータ型をリリースする前は、ユーザは文字列化されたアイテム全体をMomento Cacheに保存することができました。そのアイテムから特定のデータをフェッチしたいときは、キャッシュから全体を取り出して、必要な情報に絞り込んで、詳細を送り返す必要がありました。これがいわゆる電子の無駄遣いです。必要なのは1つか2つの詳細だけなのに、すべてを取り出す必要はありません。その方が環境的にも持続可能だし、お財布にも優しいのは勿論のことです!
Acorn Huntでは、現在のゲームに参加しているすべてのユーザーのメタデータを保存しているとします。ゲームでは、プロフィールをロードするときのように、プレイヤーのメタデータをすべてロードしたい場合があります。また、レベルや所属するクランなど、一部のデータのみが必要な場合もあります。ディクショナリを使用して、プレーヤーに関するデータのすべてまたはサブセットをロードするエンドポイントを構築できます。
//
// GET /users/{userName}
//
const handler = async (event) => {
const { userName } = event.pathParameters;
const attributes = event.queryStringParameters?.attributes;
let details;
if (attributes) {
const dictionaryFields = attributes.split(',').map(att => att.trim());
const fields = await momento.dictionaryGetFields(CACHE_NAME, userName, dictionaryFields);
details = fields.valueRecord();
} else {
const player = await momento.dictionaryFetch(CACHE_NAME, userName);
details = player.valueRecord();
}
return {
statusCode: 200,
body: JSON.stringify(details)
};
}
直接呼び出された場合、このエンドポイントはすべてのユーザー・メタデータを返します。しかし、クエリ文字列パラメータが提供されると、メタデータから特定のフィールドのみを読み込みます。dictionaryFetch
とdictionaryGetFields
を使用して異なる情報を読み込むと、レスポンスがどのように変わるか見てみましょう。
// GET /users/mo-the-squirrel
{
"userName": "honey-badger",
"level": "49",
"clan": "Flying Squirrelz",
"winRatio": ".8",
"name": "Mo"
}
// GET /users/mo-the-squirrel?attributes=level,clan
{
"level": "49",
"clan": "Flying Squirrelz"
}
データを必要なフィールドだけに絞り込むことで、データ転送コストだけでなく、全体的なレイテンシーも下げることができます。ウィンウィンです!
辞書の要素を更新するのは簡単です。dictionarySetField
コマンドを呼び出すだけで、指定されたキーの値が追加または更新されます。
await momento.dictionarySetField(CACHE_NAME, userName, 'level', '50');
親しみを感じる…。
もしあなたがRedisユーザーなら、この中のいくつかは聞き覚えがあるかもしれません。Redisはリスト、セット、ハッシュで素晴らしい仕事をしてくれますが、私たちはそれらに少し違った方法で取り組み、素晴らしい結果をもたらす方法を見つけました。
自動切り捨て
固定サイズのリストにデータをプッシュする場合を考えてみましょう。データをポップしてからプッシュするか、データを自由にプッシュし、定期的にバックグラウンド・ジョブを実行してリストのサイズを縮小するかです。固定長のリストにデータをプッシュするのは、不必要な計算とネットワーク・トラフィックが多そうですよね?リストでは、truncateFrontToSize
とtruncateBackToSize
パラメータを使って、これを一挙に行うことができます。
Acorn Huntで表示されるメッセージの最大数を30に制限したいとします。listPushBack
コールを更新することで、1つのコマンドでそれを行うことができます!
const MAX_CHAT_SIZE = 30;
const options = { truncateFrontToSize: MAX_CHAT_SIZE };
await momento.listPushBack(CACHE_NAME, gameId, JSON.stringify(message), options);
より速く、よりシンプルに💥。
バイトサポート
Momento SDKは、文字列とバイトの両方をネイティブにサポートしています。これは、コンテンツをbase64エンコードするために追加の呼び出しを行うことなく、データを直接バイトとして格納および取得できることを意味します。Base64エンコードするとデータサイズが大きくなり、不必要な非効率と電子廃棄物の増加につながります。
別の例を見てみましょう。プレイヤーのアバターのサムネイルをメタデータに保存したいとします。ファイルストアとしてAmazon S3を使用すると、以下のコードでアバターをロードし、ユーザープロファイルにキャッシュすることができます。
const avatarBytes = await getAvatarBytes(avatarKey);
await momento.dictionarySetField(CACHE_NAME, userName, 'avatar', avatarBytes);
const getAvatarBytes = async (objectKey) => {
const response = await s3.send(new GetObjectCommand({
Bucket: 'acorn-hunt-avatars',
Key: objectKey
}));
const stream = response.Body;
return new Promise((resolve, reject) => {
const chunks = [];
stream.on('data', (chunk) => chunks.push(chunk));
stream.on('end', () => resolve(Buffer.concat(chunks)));
})
};
バイトを直接キャッシュに保存することで、操作が効率化され、画像ができるだけ早くレンダリングされます。
生存期間(TTL)の合理化
キャッシュされたデータはすべて期限切れになります。Momento Cacheでは、標準的なキャッシュアイテムでデータの自動期限切れに慣れているかもしれません。すべてのキャッシュアイテムが同じではなく、他のアイテムよりも長い有効期限が必要なものもあります。これはアイテムだけでなく、CDT にも当てはまります。
新しく導入されたコレクションデータ型では、任意のコレクション型を作成または更新するときに、インラインで個々のTTL設定を行うことができます。Acorn Huntのユーザーにゲームの継続時間を設定できるようにすれば、プレイヤーリストのTTLを設定するコードを更新できます。
const options = { ttl: CollectionTtl(gameSettings.duration) };
await momento.setAddElement(CACHE_NAME, gameId, userName, options);
const options = { ttl: CollectionTtl(gameSettings.duration) };
await momento.setAddElement(CACHE_NAME, gameId, userName, options);
他のキャッシュ・ソリューションでは、データを設定し、TTLを設定するために複数の呼び出しが必要です。しかし、私たちの新しいコレクション・データ型では、1回の呼び出しで完了します!
コレクション・データ型のTTLでできることの詳細については、こちらをご覧ください。
スパイキーワークロード?私たちがカバーします
バーストは誰にでも起こります。私たちのアプリケーションは、標準的なリクエストの流入で動作しているときに、突然のトラフィックの急増によって、1秒あたり2トランザクション(TPS)から2,000TPSになる可能性があります。
従来のキャッシュソリューションを使ったサーバーレスアプリケーションでは、これが問題になります。Lambdaが需要に応じてスケールアップするとき、新しい実行環境ごとに新しい接続を初期化します。キャッシュがアプリケーションの他の部分と同じ弾力性でスケールしない場合、スケーリングのボトルネックにぶつかり、下流に深刻な問題を引き起こします。
スケーリングのボトルネックはToo Many Requestエラーにつながり、パフォーマンスに悪影響を及ぼし、最終的にはデータロスにつながる可能性があります。誰もそんなことは望んでいません。
Momento Cacheを使用すると、Lambdaとともにキャッシュワークロードをシームレスに拡張できます。接続が切断されることも、速度低下を指示するエラーが発生することも、データが失われることもありません。
コレクション・データ型では、トラフィックの急増をプロ並みに処理します。単一のアイテムをキャッシュする場合でも、一握りの要素をリストにプッシュする場合でも、辞書のフィールド値を更新する場合でも、キャッシュからセット全体を削除する場合でも、お任せください。
試乗の準備はいいですか?
もしあなたが私たちのようなものであれば、これらを自分で試してみたくてうずうずしていることでしょう!今日現在、以下のSDKで試すことができます:
もしあなたの好きな言語が現在サポートされていない場合は、ご連絡ください!Go、Rust、Javaのフルサポートに積極的に取り組んでいます。
クールだと思いますか?ちょっと待ってください。Momentoコレクション・データ型には、もっとおいしいご馳走が用意されています。まもなくAcorn Huntは、素晴らしい新機能を実装できるようになるでしょう!
私たちは、お客様からのフィードバックに基づいて、Momentoコレクションのデータタイプを構築しています!試してみて、好きなもの、嫌いなものを教えてください。CDTは、超クールな何かのアイデアを閃きますか?教えてください!
お問い合わせフォーム、Discord、またはTwitterでお気軽にご連絡ください!