🐪

Lambda,Node.js,Typescriptのキャッシュについて

2025/02/11に公開

お久しぶりです。mikenekoです。
2025年が始まってもう1ヶ月が経ってしまいましたね😅
私は、現職で新しい機能開発をしつつ、お休みの日は本を読んだりしています!
最近では「Web API The Good Parts」という本を読みました。
API開発をまだしたことない人やOWASPなどでセキュリティ診断をしたことないって人は読んだほうが良いかなと思いました!
HTTPヘッダーの話はセキュリティ面においてかなり重要になると思っています。

さて、今回は現職で新規機能開発をしている時に使用しているAWS Lambdaについてお話しをしようと思います。
こちらについて記載していくとかなり長くなってしまうので「インメモリキャッシュ」についてのみお話をしていきます。

AWS Lambdaについて(軽く触れておきます。)

まず、Lambdaといえば「サーバーレス」を思い浮かべる方も多いでしょう。AWS Lambdaは、サーバー管理不要でコードを実行できるサービスです。イベント駆動型で、特定のイベントが発生すると自動的にコードが実行されます。これにより、インフラ管理から解放され、開発者はアプリケーションロジックに集中できます。

Lambdaの特徴は以下の通りです:

  • 自動スケーリング: トラフィックに応じて自動的にスケールします。
  • 高可用性: 複数のアベイラビリティゾーンで実行され、高い可用性を持ちます。
  • コスト効率: 実行時間に応じて課金され、使用した分だけ支払います。

Lambda関数は、Node.js、Python、Java、Goなど様々なランタイムで実行できます。特に、Node.jsとTypescriptの組み合わせは非同期処理やイベント駆動型アプリケーションに適しており、多くの開発者に利用されています。

AWS Lambdaのライフサイクルについて

AWS Lambdaのライフサイクルは、関数の作成から実行、終了までの一連のプロセスを指します。以下にその主要なステージを説明します。

  1. 関数の作成:
    開発者はLambda関数を作成し、コードをアップロードします。関数にはトリガーとなるイベントソースを設定します。

  2. 初期化 (Init):
    関数が初めて呼び出されるとき、またはスケールアップのために新しいインスタンスが作成されるときに初期化が行われます。このステージでは、関数の初期化コードが実行され、外部リソースへの接続やグローバル変数の設定が行われます。

  3. 実行 (Invoke):
    イベントがトリガーされると、Lambda関数が実行されます。このステージでは、関数のハンドラーコードが実行され、イベントデータが処理されます。関数の実行が完了すると、結果が返されます。

  4. 終了 (Shutdown):
    関数の実行が終了すると、Lambdaは関数のインスタンスを一定期間保持します。この期間中に再度イベントがトリガーされると、同じインスタンスが再利用されます。一定期間が過ぎると、インスタンスは終了され、リソースが解放されます。

コールドスタンバイとホットスタンバイ

  • コールドスタンバイ:
    初期化ステージが発生する場合、これをコールドスタートと呼びます。コールドスタートでは、関数の初期化に時間がかかるため、初回の呼び出しに遅延が発生します。

  • ホットスタンバイ:
    一度初期化された関数インスタンスが再利用される場合、これをホットスタートと呼びます。ホットスタートでは、初期化が不要なため、関数の実行が迅速に行われます。

インメモリキャッシュの活用

Lambda関数のライフサイクルを理解することで、効率的なリソース管理とパフォーマンスの最適化が可能になります。
特に、ホットスタートを活用してインメモリキャッシュを利用することで、関数のパフォーマンスを向上させることができます。

例えば、データベースから頻繁にアクセスされるデータをインメモリキャッシュに保存することで、データベースへのアクセス回数を減らし、レスポンスタイムを短縮することができます。以下に、簡単な例を示します。

  • S3を使用するので以下パッケージのインストールを行います。
npm install @aws-sdk/client-s3
  • 例えば、S3に配置してあるファイルからユーザIDとユーザ名を取得する処理があります。
import {
  GetObjectCommand,
  S3Client,
  S3ServiceException,
} from "@aws-sdk/client-s3";

interface Cache {
  userId: number;
  userName: string;
}

let cache: Cache | undefined = undefined;

export const handler = async (event: any) => {
  if (!cache) {
    cache = await fetchS3CsvFile();
  }

  if (cache) {
    console.log(cache.userId, cache.userName);
  } else {
    console.error("Failed to fetch cache");
  }
};

async function fetchS3CsvFile(): Promise<Cache | undefined> {
  const client = new S3Client({});
  try {
    const response = await client.send(
      new GetObjectCommand({
        Bucket: "bucketName",
        Key: "key",
      }),
    );
    const bodyString = await response.Body.transformToString();
    const [userId, userName] = bodyString.split(",");
    return { userId: Number(userId), userName };
  } catch (error) {
    if (error instanceof S3ServiceException) {
      console.error(
        `Error from S3 while getting object from bucketName. ${error.name}: ${error.message}`,
      );
    } else {
      console.error("Unexpected error:", error);
    }
    return undefined;
  }
}

Lambda関数が実行されるたびにS3へアクセスを行なっていると、その分オーバーヘッドタイムが加算され、パフォーマンスが低下する可能性があります。これを防ぐために、インメモリキャッシュを活用することで、頻繁にアクセスされるデータをメモリ内に保持し、再利用することができます。これにより、S3へのアクセス回数を減らし、レスポンスタイムを短縮することができます。

ただし、キャッシュの有効期限やメモリ使用量には注意が必要です。

まとめ

今回は、AWS Lambdaの基本的な特徴とインメモリキャッシュの活用方法についてお話ししました。Lambdaのライフサイクルを理解し、適切にキャッシュを利用することで、パフォーマンスの向上が期待できます。ぜひ、皆さんのプロジェクトでも試してみてください。

最後までお読みいただき、ありがとうございました。次回もお楽しみに!

参考文献

Discussion