🧊

Cloudflare WorkersのKVで、キーのリストは即時反映されない

というバグなのか仕様なのかわからない挙動があってちょっと困ってるという話。

もしもし中の人、もし見てたら教えてください!

How to reproduce

こういうコードを書いてデプロイする。

addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request))
})

let id = 0;
async function handleRequest(request) {
  const params = new URL(request.url).searchParams;

  const mode = params.get("mode") || "0";

  if (mode === "list") {
    const list = await KV.list();
    console.log("LIST: %o", list.keys);
    return new Response(JSON.stringify(list.keys));
  }

  if (mode === "put") {
    await KV.put(id++, "OK");
    console.log("PUT: %s", id);
    return new Response();
  }

  const value = await KV.get(mode);
  console.log("GET: %s => %s", mode, value);
  return new Response(value);
}

もちろん`kv:namespace`は事前に作っておく。

で、期待される挙動としてはこう。

await list(); // []

await put();
await list(); // [{ name: "0" }]

await put();
await list(); // [{ name: "0" }, { name: "1" }]

という感じで、書き込んだものはリストに即時反映されてほしい。

しかし、実際はこうなる・・・。

await list(); // []

await put();
await list(); // []

await get("0"); // "OK"
await list(); // []

// だいたい30秒くらい待ったら
await list(); // [{ name: "0" }]

だいたい30秒のところは、もっと速いこともあれば遅かったりと不定ではあるけど、とにかく即時ではない挙動を示す。

`get()`ではちゃんと取れるのに、`list()`だと取れない、これいかに。

仕様なのかバグなのか

ドキュメントを見てもそれらしい記述は見つけられなかった。

KV achieves this performance by being eventually-consistent. Changes are immediately visible in the edge location at which they're made, but may take up to 60 seconds to propagate to all other edge locations.
https://developers.cloudflare.com/workers/learning/how-kv-works

というわけで、"WRITEしたエッジであれば、即READできる"とは書いてあって、さっきの検証コードでもそこはそれらしく動いてると思う。(もし違うエッジならば、`id`が違って`get()`すらうまく動かないはず)

Listing items returns inconsistent state - Workers - Cloudflare Community

困ってる先人もいたので、そういう仕様と割り切ったほうがいいんかな〜とか。

何に困るのか

いちおう書いておくと、たとえばTODOアプリを作ろうという場合。
それぞれのアイテムを、個別のキーで保存しようとしたら困る。

  • リスト取得して表示
  • TODO追加
  • リスト取得して表示

ってすると、追加されたはずのアイテムが存在しない古いリストが返ってくる。

ワークアラウンドとしては、アトミックにデータ取得して表示するのではなく、TODOを追加したときにリストの末尾に自分でアイテムを追加して、それを表示するしかない。

公式のTODOのサンプルコードがあったのでどうやってるんやろ?と思って見てみたら、1ユーザーあたり1キーでJSONまるごと保存するスタイルで書かれていて、まあそういう使い方しろってこと?ってなった。

そもそも結果整合なストアの用途じゃないかもしれんとはいえ、でもいわゆるKVSってこういうもんやっけ・・?

と思ってたけど

最近ドキュメントを読み直したら、リストのセクションにこういう記述を発見。

Changes may take up to 60 seconds to be visible when listing keys.

というわけで、やはり書き込み後の挙動としては、

  • `id`指定での直READは、当該エッジであれば即時で可能
  • ただしLISTは、他エッジからのREADと同じく、60秒かかる

ということでFAっぽい。わかりにくいけど。