🧊

Cloudflare KV vs R2 参照系パフォーマンス比較

なんとなく気になって・・。

調べたかったユースケースとしては、

  • バイナリを保存したい
  • それを一意なキーで取得し、そのままレスポンスしたい
  • 整合性はどっちでもよい

KVでもR2でもそれができるけど、どっちでやるか?っていう。 頻度としてはKVが良さそうと直感的に思うけど、R2のほうが安いからな〜っていう下心を添えて。

対戦者の紹介

まずはKV。

https://developers.cloudflare.com/workers/runtime-apis/kv/

  • KVS
  • 結果整合性(最大60s待ち)
  • 最大25MiBまで
  • await KV.get(key, "stream")でバイナリを返せる
  • R2よりコストが高い

つぎにR2。

https://developers.cloudflare.com/r2/api/workers/workers-api-reference/

  • S3互換BLOBストレージ
  • 強整合性
  • 最大5TiBまで
  • (await R2.get(key)).bodyでバイナリを返せる
  • KVよりコストが安い

どちらも.get(key)からバイナリが取得できる。 ファイルサイズが25MiBを超えるならR2しか選択肢はないけど、そうではなく、コストも問題にならないなら、KVでもよい。

コスト優先でR2を選ぶ場合、そのパフォーマンスがどれほどKVと差が出るのかが気になるところ。その特性からして、KVより遅いとは想定できるけど、それが数値としてどれほどか?っていうのを調べたい。

コード

事前の条件として、KV、R2ともに1件だけバイナリファイルをアップロード済とする。

let now;
let resp;

now = performance.now();
const kvList = await env.TEST_KV.list();
console.log("kvList: %s ms", performance.now() - now);

now = performance.now();
const r2List = await env.TEST_R2.list();
console.log("r2List: %s ms", performance.now() - now);

now = performance.now();
const kvFile = await env.TEST_KV.get("foo", "stream");
console.log("kvGet: %s ms", performance.now() - now);
resp = new Response(kvFile);

now = performance.now();
const r2File = await env.TEST_R2.get("foo");
console.log("r2Get: %s ms", performance.now() - now);
resp = new Response(r2File.body);

このコードをwrangler dev --remoteして、それぞれログに出た値を眺めてみる。

結果

雑に10回ほどcurl http://localhost:8787してみた結果。 連打してみたり、ちょっと放置してから叩いてみたり。

kvList: 191 ms
r2List: 113 ms
kvGet: 879 ms
r2Get: 151 ms

kvList: 12 ms
r2List: 103 ms
kvGet: 14 ms
r2Get: 148 ms

kvList: 12 ms
r2List: 117 ms
kvGet: 13 ms
r2Get: 127 ms

kvList: 16 ms
r2List: 105 ms
kvGet: 133 ms
r2Get: 129 ms

kvList: 15 ms
r2List: 105 ms
kvGet: 21 ms
r2Get: 153 ms

kvList: 14 ms
r2List: 106 ms
kvGet: 17 ms
r2Get: 123 ms

kvList: 12 ms
r2List: 113 ms
kvGet: 12 ms
r2Get: 289 ms

kvList: 14 ms
r2List: 105 ms
kvGet: 16 ms
r2Get: 121 ms


kvList: 186 ms
r2List: 110 ms
kvGet: 888 ms
r2Get: 137 ms

kvList: 15 ms
r2List: 99 ms
kvGet: 17 ms
r2Get: 123 ms

という感じだった。

  • 初回、しばらく放置した後は、コールドスタートみたいな挙動がある
  • 以降は安定する(そうしてもらわんと困る)
  • 基本的には、KVのほうが圧倒的に速い

というわけで、R2はその中身を最速で取得する方法ですらこのパフォーマンスなので、await (await R2.get(key)).json()みたくR2ObjectBodyのメソッドを使うとなると、もっと遅くなるはず。

この数値をどう捉えるかはその人次第ではあるが、コスト優先でR2をKVSとして使うのは、パフォーマンスの劣化を覚悟の上でという感想。