
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 ns | 0.96 ns | 約23倍 高速化! |
ナノ秒(ns)という非常に小さな単位ですが、アクセス速度が23倍も向上していることが分かります。
高負荷時の影響
この小さな差が、リクエストが増えるとどうなるでしょうか?
| 負荷レベル | 改善前の累積オーバーヘッド | 改善後 | 削減効果 |
|---|---|---|---|
| 100万 req/日 | 22.2 ms | 0.96 ms | 21.24 ms 削減 |
100万リクエストのサービスなら、1日で21ミリ秒以上のCPU時間を節約できます。これは、ユーザーの体感レスポンス向上と、AWS利用料金のわずかな節約に繋がります。まさに「ちりつも」ですね!
💡 この実装から学べること
- 不変なデータはキャッシュする: Lambdaの実行中に変わらないデータ(環境変数、設定ファイルなど)は、最初に一度だけ読み込んで使い回すのが基本です。
sync.Onceは便利屋: 「一度だけ実行したい初期化処理」があれば、sync.Onceが安全・確実に解決してくれます。- パフォーマンスは計測と思考から: 「なんとなく遅い」ではなく、どこにボトルネックがあるかを考え、ベンチマークで計測することが、効果的な改善への第一歩です。
まとめ
今回は、Lambdaの環境変数取得という、見落としがちな処理をキャッシュすることで、パフォーマンスを改善する実践的な方法を紹介しました。
- 効果: 環境変数へのアクセスが23倍高速化し、高負荷時のCPU使用率とコストを削減。
- 手法: Goの
sync.Onceを使い、スレッドセーフなキャッシュ処理を簡単に実装。 - 教訓: 小さな改善でも、積み重なれば大きな価値を生む。
この「一度だけ読む」という考え方は、環境変数以外にも様々な場面で応用できます。


