🕳️

Cloudflare Workers KV をローカルで使おうとしたらハマった

2023/09/11に公開
1

起こった問題と解決策

環境

MacOS: Ventura 13.4.1(c)
Wrangler: wrangler 3.5.0
言語: TypeScript

問題

  • KV namespace 追加後にエラー
  • 公式ドキュメント通りに動かそうとするとエラー
  • ローカルで get/put がエラー無く動いているはずなのにデータの参照/登録が行えない

原因と解決方法

  • ローカルで KV namespace 追加時は --preiew オプションが必要
  • 直接記載はないが、Env に KV の定義が必要
  • ローカルで実行した場合は --preview の環境ではなくローカルの Miniflare に対して処理が実行される

以下は実際のコードを交えた解説です

KV の namespace 追加後にエラー

公式の手順に沿って wrangler を使って namespace を作成

https://developers.cloudflare.com/workers/wrangler/workers-kv/

$ wrangler kv:namespace create kv_test
🌀 Creating namespace with title "worker-test_kv_test"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "kv_test", id = "abcde12345" }

wrangler.toml に追加

wrangler.toml
name = "worker-test"``
main = "src/worker.ts"
compatibility_date = "2023-08-07"

kv_namespaces = [
    { binding = "kv_test", id = "abcde12345" }
]

wrangler dev でエラー

$ wrangler dev

✘ [ERROR] In development, you should use a separate kv namespace than the one you'd use in production. 
Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and 
add its id as preview_id to the kv_namespace "kv_test" in your wrangler.toml

※意訳

開発環境と本番環境で同じ kv namespace を使わないでね!
--preview オプションを付けて実行して preview_id を発行して wrangler.toml に設定してね!

--preview を使って開発環境用の namespace を作る

$ wrangler kv:namespace create kv_test --preview

🌀 Creating namespace with title "worker-test_kv_test"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "res_dev", preview_id = "fghij67890" }

wrangler.toml に追加

wrangler.toml
-{ binding = "kv_test", id = "abcde12345" }
+{ binding = "kv_test", id = "abcde12345", preview_id = "fghij67890" }

この状態で再度 wrangler dev を実行して、エラーが発生せず開発サーバが立ち上がりました

$ wrangler dev
[mf:inf] Ready on http://127.0.0.1:50119/

公式ドキュメント通りに動かそうとするとエラー

公式の手順に沿って workers 経由で KV から取得する処理を実装

https://developers.cloudflare.com/workers/runtime-apis/kv/#reading-key-value-pairs
wrangler init で生成された worker.ts を修正

worker.ts
export interface Env {}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext,
  ): Promise<Response> {
+    const id = env.kv_test.get("id");
+    return new Response(id);
-    return new Response("Hello World!");
  },
};

Property 'res_dev' does not exist on type 'Env'.ts(2339) のエラーが発生

Env に定義を追加してエラー解消

Envkv_test の定義を追加

worker.ts
- export interface Env {}
+ export interface Env {
+   kv_test: KVNamespace;
+ }

エラーを解消することが出来た

ローカルで get/put がエラー無く動いているはずなのにデータの参照/登録が行えない

wrangler 経由で開発環境の kv namespace にデータを投入

$ wrangler kv:key put --binding=kv_test --preview "id" "100"

Writing the value "100" to key "id" on namespace fghij67890.

wrangler 経由でデータを確認

$ wrangler kv:key get --binding=kv_test --preview "id"

100

問題なく get/put が出来ている事を確認

workers 経由で KV からデータ取得

worker.ts
export interface Env {
    kv_test: KVNamespace;
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext,
  ): Promise<Response> {
    const id = env.kv_test.get("id");
    return new Response(id);
  },
};

エンドポイントを叩いてみると返ってくる来るのは null でした
wrangler 経由で --preview で生成した namespace にデータがある事は確認できていたので、なぜ null になるのでしょうか?
実行時にエラーが発生している訳でもなさそうです

答え

前項で以下のように思い込んでいたのが原因でした

「ローカルでの実行時は wrangler.toml にセットした preview_idnamespace に接続できるようになるんだな」と思い込みます

KV namespace 作成時に --preview をつけるよう誘導されたので勘違いしていましたが、wrangler dev 実行時は preview_id の環境に接続しているわけではなさそうです
https://blog.cloudflare.com/wrangler3/#a-new-default-for-wrangler

Starting with Wrangler v3, users running wrangler dev will be leveraging Miniflare v3 to run your Worker locally.

※意訳

Wrangler v3 からは wrangler dev は Miniflare v3 を通してローカルで worker を実行するよ!

ローカルで実行した場合の接続先は Cloudflare 上の Workers KV ではなく、ローカルで実行されている Miniflare に接続する仕様になっています
ローカルには project_dir/.wrangler/state/v3/kv というディレクトリが作成されていて、配下には KV namespace ごとにディレクトリがあります
ディレクトリの中を確認すると .sqlite ファイルがあるので、Miniflare を通して SQLite が動いているようです

ローカルでの実行確認

wrangler 経由で put してから get するのではなく、ローカル側で put してみます

worker.ts
export interface Env {
    kv_test: KVNamespace;
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext,
  ): Promise<Response> {
+   await env.kv_test.put("id", 111);
    const id = env.kv_test.get("id");
    return new Response(id);
  },
};

この状態で API を叩くと 111 が返ってくるため、Miniflare を通して処理が行われていることが確認できました

EGSTOCK,Inc.

Discussion