Closed34

CloudFrontのstale-while-revalidateを試してみる。

りょたりょた

GPT大先生の叩き台。

exports.handler = async (event, context) => {
    try {
        // 日本時間(JST)の日付と時刻を取得
        const now = new Date().toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });

        // HTMLコンテンツを作成
        const htmlContent = `
        <html>
            <head><title>Lambda Test Page</title></head>
            <body>
                <h1>This page was generated by AWS Lambda!</h1>
                <p>Current Date and Time in JST: ${now}</p>
            </body>
        </html>
        `;

        // CloudFrontに返却するレスポンスを生成
        const response = {
            status: '200',
            statusDescription: 'OK',
            headers: {
                'content-type': [{
                    key: 'Content-Type',
                    value: 'text/html'
                }]
            },
            body: htmlContent
        };

        return response;
    } catch (error) {
        console.error('Error:', error);
        // エラーレスポンスを生成
        return response.FAILED;
    }
};

りょたりょた

少し修正してHTMLが返却できるものに調整。

export const handler = async (event) => {
  try {
        // 日本時間(JST)の日付と時刻を取得
        const now = new Date().toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });

        // HTMLコンテンツを作成
        const htmlContent = `
        <html>
            <head><title>Lambda Test Page</title></head>
            <body>
                <h1>This page was generated by AWS Lambda!</h1>
                <p>Current Date and Time in JST: ${now}</p>
            </body>
        </html>
        `;

        // CloudFrontに返却するレスポンスを生成
         return {
            statusCode: 200,
            headers: {
                'Content-Type': 'text/html',
            },
            body: htmlContent,
        };
    } catch (error) {
        console.error('Error:', error);
    }
};

りょたりょた

最終盤

export const handler = async (event) => {
  try {
        // 日本時間(JST)の日付と時刻を取得
        const now = new Date().toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });

        // HTMLコンテンツを作成
        const htmlContent = `
        <html>
            <head><title>Lambda Test Page</title></head>
            <body style="text-align: center;" >
                <h1>CreatedAt</h1>
                <h1>${now}</h1>
            </body>
        </html>
        `;

        // CloudFrontに返却するレスポンスを生成
         return {
            statusCode: 200,
            headers: {
                'Content-Type': 'text/html',
                "Cache-Control": "max-age=60, stale-while-revalidate=30"
            },
            body: htmlContent,
        };
    } catch (error) {
        console.error('Error:', error);
    }
};

りょたりょた

1.stale-while-revalidate の検証をしてみる

"Cache-Control": "max-age=60, stale-while-revalidate=30"

期待される挙動
60~90(60+30)秒だとキャッシュされたコンテンツを返却(古いコンテンツを返却しつつ、裏側で新規をキャッシュ)
次にアクセスすると新規のコンテンツが返却される。
90秒後にアクセスするとオリジンにアクセス。(レスポンスが遅くなるはず)

りょたりょた

GPT大先生メモ


Cache-Control: max-age=60, stale-while-revalidate=30 というヘッダー設定は、ウェブキャッシュの動作を制御するために使用されます。この設定の意味と期待される挙動は以下の通りです:

max-age=60:

この部分は、リソース(例えば、ウェブページや画像など)が「新鮮」と見なされる期間を秒単位で指定します。ここでは 60 秒と設定されています。
つまり、リソースがキャッシュに保存されてから60秒間は、キャッシュサーバー(例:ブラウザのキャッシュ、プロキシサーバー、CDNなど)は元のサーバーに再度リクエストすることなく、キャッシュされたバージョンを提供します。
stale-while-revalidate=30:

この部分は、リソースが「古くなった」(max-ageで指定された期間を過ぎた)後、どのくらいの期間キャッシュサーバーが古いバージョンのリソースを提供しながら新しいバージョンをバックグラウンドで取得するかを指定します。ここでは 30 秒と設定されています。
つまり、リソースが60秒間新鮮であった後、追加で30秒間はキャッシュサーバーは古いバージョンのリソースを提供し続けながら、バックグラウンドで新しいバージョンの取得を試みます。
期待される挙動
最初の60秒間(max-age期間): キャッシュサーバーはキャッシュされたリソースを提供します。この間、元のサーバーへのリクエストは行われません。
60秒を超えて90秒まで(stale-while-revalidate期間): キャッシュされたリソースが古くなりますが、キャッシュサーバーはこの期間中にユーザーに対して古いリソースを提供し続け、同時に新しいバージョンのリソースをバックグラウンドで取得します。
90秒を超えた後: 新しいバージョンのリソースがキャッシュされ、それが提供されます。もし新しいバージョンがまだ取得できていない場合は、古いリソースが提供され続ける可能性があります。
このヘッダー設定は、ウェブページやAPIのパフォーマンスを向上させるためによく使われます。ユーザーには古いコンテンツが即座に提供されるため、レスポンス時間が短縮され、一方で最新のコンテンツも適時に更新されます。
りょたりょた

結果

60秒未満
何度ブラウザ更新しても、同じ時刻になっている。キャッシュされている。

60~90秒
古いものを返却しつつ、裏側でキャッシュしている。

90秒後
時刻が更新されている。また正常通りキャッシュされている。何度更新しても同じ時刻。

りょたりょた

2. stale-if-errorを検証してみる。

エラーレスポンスにする
status: 500に変更する

りょたりょた
export const handler = async (event) => {
  try {
        // 日本時間(JST)の日付と時刻を取得
        const now = new Date().toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });

        // HTMLコンテンツを作成
        const htmlContent = `
        <html>
            <head><title>Lambda Test Page</title></head>
            <body style="text-align: center;" >
                <h1>CreatedAt</h1>
                <h1>${now}</h1>
            </body>
        </html>
        `;

        // CloudFrontに返却するレスポンスを生成
         return {
            statusCode: 500,   //  追加したもの
            headers: {
                'Content-Type': 'text/html',
                // "Cache-Control": "max-age=60, stale-while-revalidate=30"
                "Cache-Control": "max-age=60, stale-if-error=90" //  追加したもの
            },
            body: htmlContent,
        };
    } catch (error) {
        console.error('Error:', error);
    }
};

りょたりょた

結果

最初の60s
画面はstale-while-revalidateと同じ。
待ちの60s内に
エラーのコードを埋め込む。

export const handler = async (event) => {
//   try {
        // 日本時間(JST)の日付と時刻を取得
        const now = new Date().toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });

        // HTMLコンテンツを作成
        const htmlContent = `
        <html>
            <head><title>Lambda Test Page</title></head>
            <body style="text-align: center;" >
                <h1>CreatedAt</h1>
                <h1>${now}</h1>
            </body>
        </html>
        `;
        throw new Error()    // こちらを追加
        // CloudFrontに返却するレスポンスを生成
        //  return {
        //     statusCode: 200,
        //     headers: {
        //         'Content-Type': 'text/html',
        //         // "Cache-Control": "max-age=60, stale-while-revalidate=30"
        //         "Cache-Control": "max-age=60, stale-if-error=90"
        //     },
        //     body: htmlContent,
        // };
    // } catch (error) {
    //     console.error('Error:', error);
    // }
};

60s~90s
エラーにならず、 キャッシュされたコンテンツが正しく返却されている。

90s後、正しくはエラーになったのは結構後だった。

りょたりょた

3.両方を設定してみる。

りょたりょた
export const handler = async (event) => {
//   try {
        // 日本時間(JST)の日付と時刻を取得
        const now = new Date().toLocaleString("ja-JP", { timeZone: "Asia/Tokyo" });

        // HTMLコンテンツを作成
        const htmlContent = `
        <html>
            <head><title>Lambda Test Page</title></head>
            <body style="text-align: center;" >
                <h1>CreatedAt</h1>
                <h1>${now}</h1>
            </body>
        </html>
        `;
        // throw new Error()
        // CloudFrontに返却するレスポンスを生成
         return {
            statusCode: 200,
            headers: {
                'Content-Type': 'text/html',
                // "Cache-Control": "max-age=60, stale-while-revalidate=30"
                // "Cache-Control": "max-age=60, stale-if-error=90"
                "Cache-Control": "max-age=60, stale-while-revalidate=30, stale-if-error=120"
            },
            body: htmlContent,
        };
    // } catch (error) {
    //     console.error('Error:', error);
    // }
};

りょたりょた

結果

最初の60s
画面はstale-while-revalidateと同じ。
待ちの60s内に
エラーのコードを埋め込む。

エラー埋め込み後60s未満。キャッシュされたものが表示されている。

60~90s 1回目
キャッシュされたものが表示されている。

2回目 オリジンでエラーが出ているはずだが、stale-if-errorのおかげでそのままの表示になっている。(stale-if-errorがない時は時刻の更新はされなかった。)

90s後
キャッシュされたものが表示され続けている。

120s後 でもまだキャッシュされている。

エラーになったのは結構後だった。

りょたりょた

stale-while-revalidate について

りょたりょた

“SWR” という名前は、 HTTP RFC 5861(opens in a new tab) で提唱された HTTP キャッシュ無効化戦略である stale-while-revalidate に由来しています。 SWR は、まずキャッシュからデータを返し(stale)、次にフェッチリクエストを送り(revalidate)、最後に最新のデータを持ってくるという戦略です。

https://swr.vercel.app/ja

りょたりょた

標準的な Cache-Control ディレクティブの一つ
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cache-Control#ブラウザーの互換性

stale-while-revalidate
レスポンスディレクティブの stale-while-revalidate は、キャッシュを再検証している間、古いレスポンスの再利用が可能なことを示します。
Cache-Control: max-age=604800, stale-while-revalidate=86400
上記の例では、レスポンスは 7 日間(604800 秒間)は新鮮です。7 日後、レスポンスは古くなりますが、キャッシュは翌日(86400 秒後)のリクエストに再利用できます。ただし、バックグラウンドでレスポンスを再検証することが条件です。
再検証により、キャッシュは再び新鮮になるため、クライアントはその期間中は常に新鮮であったかのように見えます。これにより再検証の遅延ペナルティを効果的にクライアントから隠蔽できます。
その間にリクエストがなければ、キャッシュは古くなり、次のリクエストは正常に再検証されます。

りょたりょた

mermaid 気泡も使える。

sequenceDiagram
    participant Browser
    participant CloudFront
    participant Origin

    Browser->>CloudFront: リクエスト
    alt キャッシュが有効 (max-age)
        CloudFront-->>Browser: キャッシュされたコンテンツ
    else 古いものを返しつつ裏側でリクエストを行う
        CloudFront-->>Browser: stale コンテンツ
        CloudFront->>+Origin: 新しいコンテンツをリクエスト
        Origin-->>-CloudFront: 新しいコンテンツ
        note right of CloudFront: CloudFrontは新しいコンテンツでキャッシュを更新👍
    else キャッシュが無効
        CloudFront->>Origin: コンテンツをリクエスト
        Origin-->>CloudFront: コンテンツ
        CloudFront-->>Browser: コンテンツ
    end
りょたりょた

stale-if-errorについて

りょたりょた

gpt大先生

stale-if-errorは、HTTPのCache-Controlヘッダーの一部であるディレクティブです。このディレクティブは、キャッシュされたリソースが新鮮でなくなった後、あるいは通常の有効期限が過ぎた後でも、元のサーバーからのエラーレスポンス(例えば、サーバーがダウンしている場合など)が返される場合に、クライアントがその古いキャッシュされたコピーを使用することを許可します。

stale-if-errorディレクティブは通常、秒単位で時間を指定します。例えば、Cache-Control: max-age=3600, stale-if-error=86400は、リソースが最大1時間(3600秒)新鮮であり、その後も24時間(86400秒)は、元のサーバーからエラーが返された場合にキャッシュされたコピーを使用することを許可することを意味します。

この機能は、サーバー障害や一時的な接続問題が発生した場合に、ユーザーエクスペリエンスを維持するのに役立ちます。ただし、このディレクティブを使用する場合は、古い情報がクライアントに提供される可能性があることに注意する必要があります。

りょたりょた

stale-if-error の有効期間が一般に長いかどうかは、その使用方法や設定に依存します。stale-if-error と stale-while-revalidate の有効期間は、それぞれ独立して設定されます。これらのディレクティブの期間は、具体的な使用ケースや目的に応じて異なります。

stale-while-revalidate の期間は、キャッシュされたコンテンツが古くなった(max-age の期限が切れた)後に、新しいコンテンツの取得が完了するまでの期間を指します。この期間は通常、新しいコンテンツの取得にかかる時間を考慮して設定されます。

stale-if-error の期間は、キャッシュされたコンテンツが古くなり、かつサーバーからの新しいコンテンツの取得がエラーにより不可能になった場合に、古いコンテンツを使用できる期間を指します。この期間は、サーバーのエラーが解消されるまでの予想時間や、古いコンテンツが許容可能である最長の期間を考慮して設定されます。

このため、stale-if-error の方が有効期間が長い場合もあれば、そうでない場合もあります。それは、サイトの運営方針や特定のリソースに対する要件に基づいて決定されます。たとえば、サーバー障害が比較的長期間にわたる可能性がある場合、stale-if-error の期間を長く設定することが望ましいかもしれません。逆に、コンテンツが頻繁に更新されるサイトでは、stale-while-revalidate の期間を短く設定することが適切かもしれません。

りょたりょた
sequenceDiagram
    participant Browser
    participant CloudFront
    participant Origin

    Browser->>CloudFront: リクエスト
    alt キャッシュが有効 (max-age)
        CloudFront-->>Browser: キャッシュされたコンテンツ
    else キャッシュが期限切れor無効
        CloudFront->>+Origin: 新しいコンテンツをリクエスト
        alt Originから正常なリクエスト
            Origin-->>-CloudFront: 新しいコンテンツ
            CloudFront-->>Browser: 新しいコンテンツ
        else Originからエラーリクエスト
            Origin--xCloudFront: エラーリクエスト
            CloudFront-->>Browser: エラー時の代替コンテンツ
            note left of CloudFront: オリジンサーバーからのエラーにより代替コンテンツを配信👍
        end
    end

このスクラップは2023/12/10にクローズされました