🛤️

Fastly Compute HTTP リクエストの作成 (4) Readthrough Cache 応用編

2024/12/05に公開

この記事は 2024 年の Fastly Compute 一人アドベントカレンダー 5 日目の記事です。

Fastly Compute で最も多く利用することになるであろう HTTP リクエストの作成について紹介するシリーズ(Fastly Compute HTTP リクエストの作成)の第四回目です。本稿では第二回目で紹介した Readthrough Cache をより便利かつ高度に使うために、今年新たに導入された HTTP Cache 系の API について簡単に紹介していきます。

おさらい

前回(第二回目)の記事では、Readthrough Cache の概要について以下のように紹介しました。

  • fetch()send() するとデフォルトで結果がキャッシュされる
    • 不要であればキャッシュ無効化も可能だが、特に明示的な指定がない場合 GET についてはデフォルトでキャッシュされる
  • キャッシュの挙動はオリジンサーバから届く Cache-Control ヘッダなどのヘッダ等でコントロールされる
    • TTL 等の一部のパラメータを除くと、Compute 側ではキャッシュを細かくコントロールする良い方法がない

このように、Readthrough Cache は Web の世界で標準的に利用されている HTTP Caching のセマンティクスに従ったキャッシュを簡単に利用でき、かつ Fastly の高速なパージの恩恵も受けることができるなど上手に使うと非常に便利な一方で、キャッシュコントロールの主体が Compute ではなくオリジンサーバとなってしまう部分で融通の聞きづらい部分があると紹介してきました。

この "細かくコントロールする" という部分についてもう少し掘り下げて説明すると、例えばこれまでの API では以下のようなケースでキャッシュの動作の制御が仕切れないという状況がありました。

  • オリジンサーバから返されるヘッダの内容を見てキャッシングの挙動を変えたい場合
    • 例1: レスポンスで得られたコンテンツの拡張子に応じてキャッシュの TTL を変えたい
    • 例2: ユーザの属性を見て Vary ヘッダを書き換えたい
    • 例3: オリジンからエラーが返った場合はコンテンツのキャッシュがされないようにしたい
  • オリジンサーバに対して fetch()send() を実行した際にキャッシュにヒットしたかどうかでバックエンドへの接続処理に変更を加えたい場合
    • 例4: キャッシュにヒットしなかった(=バックエンドへの接続処理が走る直前)にのみヘッダ追加などの特定の処理を差し込みたい

従来の Readthrough Cache の機能の範囲では上記のような細かい粒度でのキャッシング挙動を扱う API が提供されていなかったため、(自前で Core Cache API などにより HTTP Caching 相当の処理を実装しない限りは)キャッシングの挙動についてオリジン側の処理に依存せざるを得ない状況がありましたが、今年追加された API によって Compute 側で従来は実現が難しかったより細かなキャッシュ制御が簡単に可能になります。

"Customizing cache interaction with the backend" 章の攻略

今年新たに導入された API について最も詳しく解説されているページが Developer Docs の "Caching content with Fastly" という題名のページに存在する Customizing cache interaction with the backend 章です。
https://www.fastly.com/documentation/guides/concepts/edge-state/cache/#customizing-cache-interaction-with-the-backend

この章を読み解くことが動作を理解する最短距離だと思うので、本稿ではその概要について紹介していきます。結論から先にお話すると、Request::set_before_send()Request::set_after_send() という 2 つの API が Rust SDK 0.11.0 から 新規に追加されており、これらを活用してキャッシングの挙動を制御していくことが可能になっています。

set_before_send()

名前の通り、 Request::send() が実行される前に呼び出したい処理をコールバック関数(クロージャ)として引数に渡すことができる API です。VCL で言うと実行タイミングとしては vcl_missvcl_pass 相当のタイミングで実行される処理です。

例えば以下のような実装により、send() 実行時にキャッシュヒットしなかった場合のみ Authorization ヘッダとして関数の実行結果をセットする、といった処理が簡単に実装可能になっています。

let mut client_req = Request::from_client();
client_req.set_before_send(|req| {
    req.set_header(http::header::AUTHORIZATION, prepare_auth_header());
    Ok(())
});
let backend_resp = client_req.send("origin_0")?;

set_after_send()

こちらも名前の通り、Request::send() が実行された後に呼び出したい処理をコールバック関数(クロージャ)として引数に渡すことができる API です。VCL で言うと実行タイミングとしては vcl_fetch 相当のタイミングで実行される処理です。

例えば以下のような実装により、send() 実行後にオリジンから返却された Content-Type レスポンスヘッダの値に応じてキャッシュの TTL を Compute 側で制御することが可能になっています。

let mut client_req = Request::from_client();
client_req.set_after_send(|resp| {
    match resp.get_header_str("Content-Type").as_deref() {
        Some("image") => resp.set_ttl(Duration::from_secs(67)),
        Some("text/html") => resp.set_ttl(Duration::from_secs(321)),
        Some("application/json") => resp.set_uncacheable(false),
        _ => resp.set_ttl(Duration::from_secs(2)),
    }
    Ok(())
});
let backend_resp = client_req.send("origin_0")?;

注意事項

以下、注意事項の例です。

  • Readthrough Cache の利用によって request と response の一部の内容は場合により transform される(Range リクエスト, 条件付きリクエスト, stale-while-revalidate 期間中のリクエストの場合、等)
  • Readthrough Cache を req.set_pass(true) により無効化した場合、本稿で紹介した 2 つの新しい API でセットしたコールバック関数は発火しない(本稿執筆時点の動作想定)

ドキュメントの更新頻度を考えると本稿でその詳細を記載するのは悪手であるように思われるので、本稿で紹介した新しい API を利用する際には必ず原文の注意事項セクションを含めて関連ドキュメントを一読することをお勧めします。不明点があればサポートチームに問い合わせすることも有効です。

まとめ

本稿では Readthrough Cache をより便利にかつ高度に使いこなすために追加された 2 つの API の概要について紹介しました。

次回は Fastly Compute を使って実現できる WebSocket 接続のハンドリングのうち、パススルー機能について紹介します。

Discussion