✏️

Fastly Compute 揮発データの読み書き (1) SimpleCache API

2023/12/18に公開

この記事は Fastly Compute (旧 Compute@Edge) 一人アドベントカレンダー 16 日目の記事です。

Fastly Compute の前身である Compute@Edge が発表されてから 4 年が経ち、当初は備わっていなかった永続/揮発両方のデータ読み書きについても機能の拡充が進んでいます。そこで本日から数記事に渡り、まずは揮発性データの読み書きについて代表的な方法である Cache API の利用方法について紹介していきたいと思います。本稿はその一回目として、SimpleCache API と呼ばれる揮発性ストレージへ SDK が提供するインターフェースについて紹介します。

実装例

実際にコードを書いてみるのが一番早くイメージを掴めるので、実装例から紹介します。

SimpleCache API は主に getOrSet(), get(), purge() の 3 種類の API を提供します。以下が getOrSet() を利用した Cache に値を書き込む実装例です。トランザクションなどを意識せずにシンプルに使えることを意識したインターフェースの設計になっていますが、Memcached のように set だけの API 提供ではなく getOrSet() という get 操作も兼ねた API になっている点に注意してください。

package main

import (
  "context"
  "io"
  "strings"
  "time"
  "github.com/fastly/compute-sdk-go/cache/simple"
  "github.com/fastly/compute-sdk-go/fsthttp"
)

func main() {
  fsthttp.ServeFunc(func(ctx context.Context, w fsthttp.ResponseWriter, r *fsthttp.Request) {
    rc, _ := simple.GetOrSet([]byte(r.URL.Path), func() (simple.CacheEntry, error) {
      return simple.CacheEntry{
        Body: "hello world",
	TTL:  time.Minute,
      }, nil
    })
    defer rc.Close()
    w.Header().Set("Content-Type", "text/plain")
    io.Copy(w, rc)
  })
}

もう少し工夫して、Memcached のような set っぽい挙動のイメージで、毎回上書きオペレーションをしたい場合は対象のキーに purge() した後で getOrSet() する処理になりそうです。ただその際、Cache API の制約条件の部分に記載されているように purge が非同期で実行されるためその直後の write 処理の前に実行される保証はされないことには注意が必要になります。[1]

Purges are asynchronous while writes are synchronous, so performing a purge immediately before a write may result in a race condition in which the purge may clear the primary instance of the cached data after the write has completed.

利用可能なメソッド一覧

メソッド 機能 Rust Go JavaScript
get() Key を元に値を取得 x x x
getOrSet() Key に紐づく値があれば返す、なければ書き込む(TTL指定可) x x x
purge() Key に紐づく値を削除 x x x
SurrogateKeyForCacheKey() Key を元に対応する SurrogateKey を取得 - x -

SurrogateKeyForCacheKey() は Go SDK の場合だけ提供されている API で、このメソッドにより SimpleCache API の Cache Key -> Surrogate Key への変換を行うことができます。(言い換えると、SimpleCache API の Cache Key と Surrogate Key は異なる名前空間に存在します。)

Surrogate Key が得られると、Surrogate Key 用に提供されている各種 API/CLI/UI 経由でのパージ手段が使えるようになるので、覚えておくと便利かもしれません。なお、この API 自体も Hostcall は必要とせず以下のように sha256 を使った符号化ができれば良さそうなので、必要であれば Go 以外での SDK でも実装ができそうですね。
https://github.com/fastly/compute-sdk-go/blob/main/cache/simple/simple.go#L164-L175

制限事項 / 注意事項

  • SimpleCache では Purge の際に POP または GLOBAL いずれかの Scope を選択することができます。Rust の Doc に少しコメントが書いてあるのですが、ここは特別な理由がない限りは POP を選択することが推奨されています。(これは体感ですが、GLOBAL の場合 Purge のオペレーションに POP よりも時間がかかり、期待する実行タイミングのズレが発生しやすい気がしており、その観点からも強い理由がない限りは POP を選択することがお勧めです)
  • 設計の際、Cache API の読み書きに関する制限値(例:1 つのエントリに格納できるデータサイズの最大値 等)や Purge の回数制限など各種制限値があることに注意してください(制限値では足りない場合、サポートチームへ事前連絡しましょう)
  • 本稿を執筆している 2023 年 12 月現在、SimpleCache API はローカルのデバッグ環境($fastly compute serve 、Viceroy)や fiddle ではサポートされておらず、機能しないことに注意してください。(公式doc

利用可能なパージ方法

ここまで見てきたように、SimpleCache は SDK の API でキー単位での purge ができます。あるいは、キー単位ではなくサービス単位でまるっとキャッシュ全体をパージしたい場合には、CLI/API/UI の以下のインターフェースを利用することが可能です。

  • CLI
    • $fastly purge --all
  • API
    • Purge all する API call (doc)
  • UI
    • Web コントロールパネル上の以下のボタンからキャッシュ全体クリアを実行可能

まとめ

本稿では SimpleCache API の概要を紹介しました。明日は SimpleCache よりも豊富な機能が利用可能な CoreCache API について紹介したいと思います。

脚注
  1. 個人的な体験談として、試験的に開発中に利用する中では Purge の Scope を POP 単位で指定して低頻度でアクセスする分にはこの方式で上書きに失敗したと感じたことはなかった一方で、Scope を GLOBAL 単位にすると write の後に遅れて purge がやってきたことがありました。必ずそういった挙動になるとも限りませんが、そういった順番が前後することが仕組み上は起き得るし排他的な制御を目的とした使い方には向かない、というイメージになるかと思います。 ↩︎

Discussion