💾

AWS Lambdaのインメモリキャッシュについて

2024/06/30に公開

はじめに

AWS Lambdaは、イベント駆動型のサーバーレスコンピューティングサービスであり、API GatewayやEventBridgeなど様々なサービスから起動されます。
この記事ではAWS Lambdaでのインメモリキャッシュのポイントと、その実装方法について解説します。

キャッシュとは

コンピューティングにおいて、キャッシュは、データのサブセットが保存される高速のデータストレージレイヤーで、通常は一時的な性質のものです。これにより、それ以降に同じデータのリクエストが発生した場合、データのプライマリストレージロケーションにアクセスするよりも高速にデータが供給されます。キャッシュにより、以前に取得または計算されたデータを効率的に再利用できるようになります。

https://aws.amazon.com/jp/caching/

AWSでキャッシュを活用することのメリット

AWSでキャッシュを活用することで以下のようなメリットがあります。

  1. パフォーマンスの向上:
    キャッシュによってデータ取得性能が向上し、大規模なアプリケーションでのコスト削減が可能になります。
  2. コストの削減:
    従来のデータベースやディスク等の保存エリアを使用する場合と比較して、リソースの追加が不要となり低レイテンシーのパフォーマンスを実現できます。

Amazon DynamoDB Accelerator (DAX)

Lambdaでよくある構成としては下記のようにAPI gateway→Lambda→DynamoDBというパターンになると思います。

このとき、キャッシュをどうするかと考えたときに思いつくものがAmazon DynamoDB Accelerator (DAX) だと思います。

https://aws.amazon.com/jp/dynamodbaccelerator/

Amazon DynamoDB Accelerator (DAX) は、Amazon DynamoDB 用に構築されたフルマネージド型で可用性の高いキャッシュサービスです。
DAX は、1 秒あたり数百万のリクエストにおいても、ミリ秒からマイクロ秒へと最大 10 倍のパフォーマンス向上を実現します。

DAXのメリットを簡単にまとめると以下のようなものがあげられます。

  1. 低レイテンシ: DAXは、DynamoDBに書き込んだデータを読み込む際にキャッシュされ、数ミリ秒からマイクロ秒での応答時間を実現します。
  2. Read heavyワークロード向け:読み込みが多いワークロードに適しており、2回目以降のアクセスが高速化されます。
  3. スケーラビリティ: DAXは、マルチAZ DAXクラスターを使用して、1秒間に数百万件のリクエストを処理する能力があります。
  4. 暗号化サポート: DAXは、保存時および転送時の暗号化をサポートしており、セキュリティを強化します。

メリットづくしでできれば利用したいところですね。
ただし、DynamoDBと併せてサービスを立ち上げなければならないためDAXの構築・設定のコストや運用のコストがかかってしまいます。

AWS Lambdaのインメモリキャッシュ

前述したようにDAXには大きなメリットがあるものの追加でコストがかかってしまいます。
そこまで厳密なキャッシュが必要でない場合やコストをできるだけ抑えたい場合の選択肢としてインメモリキャッシュがあります。

一般的に使用方法が定められているわけではありませんが、グローバル変数を使用することが多いです。
グローバル変数と聞いて、Lambdaはイベント駆動型なので値を保持できないのでは?と思う方もいると思います。

この時重要になってくるのがLambdaのスタート状態の違いです。


コールドスタート:
Lambda関数が初めて呼び出されるか、一定時間アイドル状態が続いた後に呼び出されると、コールドスタートが発生します。
コールドスタートでは、新しいLambdaインスタンスが起動され、コードがロードされ、ランタイムが初期化されます。
このプロセスには時間がかかり、関数の応答時間が長くなる可能性があります。

ウォームスタート:
Lambda関数がアクティブで、すでに起動しているインスタンスがある場合、ウォームスタートが発生します。
ウォームスタートでは、関数のコードやランタイムが既にメモリにロードされているため、関数はすぐに実行を開始できます。


つまりウォームスタート状態の場合、メモリ上に諸々ロードされた状態となるのでグローバル変数としてキャッシュの保持が可能となります。

AWS Lambdaのインメモリキャッシュの実装方法

実装方法はとてもシンプルです。
グローバル変数に対象となるデータをキャッシュしておくだけです。

下記サンプルコードとなります。

index.mjs
import {DynamoDBClient} from '@aws-sdk/client-dynamodb';
import {DynamoDBDocumentClient, GetCommand} from '@aws-sdk/lib-dynamodb';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

let cache = {};

export const handler = async(event) => {

	try {

        console.log(event.key);

        // cacheからデータを取得
		const val = cache[event.key];

		if (val) {
			console.log("Cacheがあったよ");
            console.log(val);

			return {
				statusCode: 200,
				body: JSON.stringify({
					message: 'Cache hit',
					data: val
				})
			};
		}

		console.log("Cacheがなかったよ");

		// DynamoDBから値を読み込み
		const result = await docClient.send(
			new GetCommand({
				TableName: "testApp",
				Key: {
					"testId": event.key,
				},
			})
		);


        // Cacheとして保存
		const item = result.Item;
		if (item) {
			cache[item.testId] = item.data;
		}

        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'No Cache hit',
                data: null
            })
        };

	} catch (error) {
		console.error('失敗', error);
		return {
			statusCode: 500,
			body: JSON.stringify({
				message: '失敗',
				error: error.message
			})
		};
	}
}

インメモリキャッシュの動作確認

以下のようなデータを準備しました。

下記のようにインメモリキャッシュが動作することを確認できます。

コールドスタート(キャッシュがない状態での実行)

キャッシュが取得できなかったのでキャッシュを作成する処理が実行

ウォームスタート(キャッシュがある状態での実行)

キャッシュが取得できたのでキャッシュデータを返却

しばらく経過後に再実行

Lambdaのインスタンスが停止するのでコールドスタート(キャッシュがない状態での実行)となる

注意事項

前述したように比較的簡単に実装できるAWS Lambdaのインメモリキャッシュですが、使用するにあたっていくつか注意事項があります。

  1. キャッシュの一貫性が担保できない:DAXと異なりインスタンスごとにキャッシュを保持することになるのでインスタンスごとに保持する結果が異なる場合があります。
  2. キャッシュのヒット率が低くなる可能性がある:インスタンスごとにキャッシュを保持するということはインスタンスAではデータAを保持し、インスタンスBではデータBを保持するといったことが発生します。つまり目的とするデータのキャッシュを保持していない場合はDynamoDBにアクセスしに行くこととなるのでキャッシュの効果が出ない可能性があります。
    →プロビジョニング済み同時実行数を1にすると問題なし
  3. 保持期間を制御できない:キャッシュの保持期間 = Lambdaの存続期間となるのでいつまで保持するかを制御することができません。
    意図していなかったような動きになるかもしれないので要注意!
  4. 大きなデータをキャッシュするとメモリを大量に使用してしまう:当たり前ですがキャッシュをメモリ上に展開することとなるのであまり大きなデータを使用すると処理に影響が出てしまいます。

上記のような注意事項があるということを理解したうえで使用を検討することとなります。
使いどころとしては毎回読み込みが必要となり、値が頻繁に変更されず、キャッシュするデータが大きくないものが対象の場合となります。

例えば小規模なマスタであったり、共通の設定データを使用する場合には効果的に働くと思います。

まとめ

今回はLambdaのインメモリキャッシュについて紹介しました。

AWS Lambdaのインメモリキャッシュは、コストを削減したり関数のパフォーマンスを向上させるために非常に有効です。
ただし、記事内にも記載した通り注意事項もありますので、適用する際にはしっかりと検討したうえで採用が必要となります。

基本的にはDAXの採用をベースに進めて、もしコスト面でどうしてもというときにはインメモリキャッシュを考えてみてもいいかもしれません。

また、コールドスタートやLambdaのパフォーマンス向上についてはLLRTの仕組みも有効となります。
下記の記事にまとめているので興味のある方は読んでみてはいかがでしょうか。
https://zenn.dev/penginpenguin/articles/fe42ba481ab5a1

Discussion