メインコンテンツにスキップ
AWS Lambda
Lambdaのパフォーマンス最適化

Lambda環境変数の毎リクエスト取得を防ぐ:パフォーマンス最適化の実践

はじめに

パフォーマンス最適化の事例です。

テーマは、見落としがちですが効果の大きい「環境変数の取得」です。

この記事でわかること

  • なぜリクエストのたびに環境変数を取得するのが非効率なのか
  • Goのsync.Onceを使って、安全かつ効率的に処理を改善する方法
  • 「ちりつも」な改善が、どれだけ大きなパフォーマンス向上に繋がるか

🚨 問題発見:「毎回聞く」ことの非効率さ

pkg/lambda/common_handler.goという、多くのリクエストが通る共通処理のファイルで、少し気になるコードが見つかりました。

問題のコード

// ❌ 問題のコード(リクエストのたびにos.Getenvが実行される)
func (h *CommonHandlerWrapper) WrapHandler(...) {
    return func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
        // ...

        // CORS設定の初期化
        allowedOrigins := h.config.AllowedOrigins
        if allowedOrigins == "" {
            // 🚨 リクエストが来るたびに、毎回環境変数を取得している!
            if envOrigins := os.Getenv("ALLOWED_ORIGINS"); envOrigins != "" {
                allowedOrigins = envOrigins
            } else {
                allowedOrigins = "http://localhost:5173,https://poc-cognito.netlify.app"
            }
        }
        // ...
    }
}

何が問題だったのか?

このコードは、APIリクエストを受け取るたびに os.Getenv("ALLOWED_ORIGINS") を実行しています。

Lambdaの環境変数は、一度設定したら関数の実行中に変わることはありません。それなのに、リクエストのたびに「許可するオリジンは何ですか?」と毎回お伺いを立てているのです。これは非効率です。

この「毎回聞く」という無駄が、パフォーマンスにどう影響するのでしょうか?

パフォーマンスへの影響

Lambdaには「コールドスタート」と「ホットリクエスト」という状態があります。

os.Getenvの呼び出しは非常に高速なため、1回だけなら大した影響はありません。しかし、ホットリクエストで1日に何十万回、何百万回と呼び出されると、この小さな無駄が「ちりも積もれば山となる」で、無視できないCPU時間の浪費(=コスト増)と遅延に繋がるのです。

✅ 最適化ソリューション:「一度だけ聞く」仕組みを作る

この問題を解決する鍵は、先ほどの店員さんの例えと同じです。**「一度だけ聞いて、結果をメモしておく(キャッシュする)」**ことです。

Go言語には、この「一度だけ」を安全かつ確実に実現するための便利な仕組み、sync.Onceがあります。

【用語解説】

  • sync.Once: syncパッケージにある、まさに「一度だけ実行する係」です。たくさんの処理(Goroutine)が同時に「実行して!」と要求しても、sync.Onceが「はい、最初の人だけね!」と交通整理をしてくれ、中の処理が本当に一度しか実行されないことを保証してくれます。
  • スレッドセーフ: 複数の処理が同時に同じデータにアクセスしても、データが壊れたりせず、安全に動作することです。sync.Onceはスレッドセーフです。

sync.Onceを使った改善コード

// ✅ 改善案:sync.Onceで環境変数をキャッシュする

// 関連する環境変数をまとめて管理する構造体
type CachedEnvironment struct {
    AllowedOrigins string
    AllowedMethods string
}

var (
    envCache     *CachedEnvironment // キャッシュした設定を保存する場所
    envCacheOnce sync.Once          // 「一度だけ実行する係」を準備
)

// getCachedEnvironment は、環境変数をキャッシュから取得する関数
func getCachedEnvironment() *CachedEnvironment {
    // Doメソッドの中に書いた処理は、本当に一度しか実行されない
    envCacheOnce.Do(func() {
        // ----------------------------------
        // この中は、最初の1回しか通らない!
        // ----------------------------------
        allowedOrigins := os.Getenv("ALLOWED_ORIGINS")
        if allowedOrigins == "" {
            allowedOrigins = "http://localhost:5173,https://poc-cognito.netlify.app"
        }

        allowedMethods := os.Getenv("ALLOWED_METHODS")
        if allowedMethods == "" {
            allowedMethods = "GET,POST,PUT,DELETE,OPTIONS"
        }

        // 取得した設定をキャッシュに保存
        envCache = &CachedEnvironment{
            AllowedOrigins: allowedOrigins,
            AllowedMethods: allowedMethods,
        }
    })
    // 2回目以降の呼び出しでは、キャッシュ済みのenvCacheが即座に返される
    return envCache
}

このgetCachedEnvironment関数を使えば、os.Getenvが呼ばれるのはLambdaコンテナの生存期間中にたったの1回だけになります。

最終的なハンドラーの実装

// ✅ 最適化されたハンドラー実装
func (h *CommonHandlerWrapper) WrapHandler(...) {
    return func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
        // ...

        // ✅ キャッシュされた環境変数を取得(超高速!)
        env := getCachedEnvironment()

        // CORS設定の初期化
        allowedOrigins := h.config.AllowedOrigins
        if allowedOrigins == "" {
            allowedOrigins = env.AllowedOrigins // キャッシュから取得
        }
        // ...
    }
}

これで、リクエストのたびに発生していた無駄な処理がなくなり、コードもスッキリしました。

📊 改善効果はどれくらい?

「ちりつも」な改善が、どれほどの効果を生んだか見てみましょう。

パフォーマンス向上(実測値)

項目改善前(毎回取得)改善後(キャッシュ)向上率
環境変数アクセス22.19 ns0.96 ns約23倍 高速化!

ナノ秒(ns)という非常に小さな単位ですが、アクセス速度が23倍も向上していることが分かります。

高負荷時の影響

この小さな差が、リクエストが増えるとどうなるでしょうか?

負荷レベル改善前の累積オーバーヘッド改善後削減効果
100万 req/日22.2 ms0.96 ms21.24 ms 削減

100万リクエストのサービスなら、1日で21ミリ秒以上のCPU時間を節約できます。これは、ユーザーの体感レスポンス向上と、AWS利用料金のわずかな節約に繋がります。まさに「ちりつも」ですね!

💡 この実装から学べること

  1. 不変なデータはキャッシュする: Lambdaの実行中に変わらないデータ(環境変数、設定ファイルなど)は、最初に一度だけ読み込んで使い回すのが基本です。
  2. sync.Onceは便利屋: 「一度だけ実行したい初期化処理」があれば、sync.Onceが安全・確実に解決してくれます。
  3. パフォーマンスは計測と思考から: 「なんとなく遅い」ではなく、どこにボトルネックがあるかを考え、ベンチマークで計測することが、効果的な改善への第一歩です。

まとめ

今回は、Lambdaの環境変数取得という、見落としがちな処理をキャッシュすることで、パフォーマンスを改善する実践的な方法を紹介しました。

  • 効果: 環境変数へのアクセスが23倍高速化し、高負荷時のCPU使用率とコストを削減。
  • 手法: Goのsync.Onceを使い、スレッドセーフなキャッシュ処理を簡単に実装。
  • 教訓: 小さな改善でも、積み重なれば大きな価値を生む。

この「一度だけ読む」という考え方は、環境変数以外にも様々な場面で応用できます。