8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Open Beta版になったDeno KVの世界にダイブする

Posted at

DenoにはDeno KVと呼ばれるデータをディスク上に永続的に保持する組み込みのキーバリューストアがあります。これによってDenoではサービスとシステムの再起動をまたいだデータの保存とアクセスが可能となります。

この機能はこれまでClosed Beta版として公開されていましたが、2023年9月6日にOpen Beta版として公開されました。

この記事ではそんなOpenされたばかりのDeno KVについて紹介します。

Open Beta版が始まったばかりで実験中の機能です。バージョンアップによって内容が変更される可能性があるので気をつけてください。

特徴

簡単なセットアップ

ローカル環境であってもDeno Deploy上であってもDeno KVは以下のように手間なく利用可能です。

await Deno.openKv();

Deno Deploy以外の環境からDeno Deploy上のリソースを使いたい場合はurlを入力する必要があります。

await Deno.openKv("https://api.deno.com/databases/XXX/connect");

JavaScript用のデータベース

Deno KVはJavaScriptもしくはTypeScriptで利用すること前提で作られているので、文字列や数値のような単調な値の他にもMapundefinedDateのようなJavaScriptで扱えるオブジェクトをそのまま値として使うことができます。
さらに、APIもPromiseベースで直感的に実装されているのでデータの操作が容易です。

undefined;
null;
true;
false;
42;
-42.5;
42n;
"hello";
new Uint8Array([1, 2, 3]);
[1, 2, 3];
{ a: 1, b: 2, c: 3 };
new Map([["a", 1], ["b", 2], ["c", 3]]);
new Set([1, 2, 3]);
new Date("2023-04-23");
/abc/;
const a = {};
const b = { a };
a.b = b;

// Denoがサポートするbigintとして表される64ビットの符号なし整数
new Deno.KvU64(42n);

簡単に拡張可能

Deno KVはFoundationDB上に構築されており、毎秒数百万のオペレーションを処理することができます。エンタープライズへの移行もゼロコンフィグ、ゼロプロビジョニング、ゼロオーケストレーションで行えます。

ACIDトランザクション

Deno KVではデータの一貫性、整合性、耐久性を確保した一連の操作にトランザクションを貼れます。
一連の操作が成功した場合はデータを永続化し、失敗した場合はロールバックされるのでデータが常に正確に保ちます。

await kv.atomic()
  .check(senderRes)
  .check(receiverRes)
  .set(senderKey, newSenderBalance)
  .set(receiverKey, newReceiverBalance)
  .commit();

読み取りの精度

データの読み取りは通常プライマリーリージョンから最新のデータを読み取ったものを返します。データの読み取りに強い整合性を求めない場合に最も近いリージョンで読み取った最後の結果を返すように指定できます。

await kv.get(key, { consistency: "eventual" });

使ってみる

現在Deno KVの利用には、実行時に--unstableフラグを設ける必要があります。実機で試す時は気をつけてください。

deno run --unstable --allow-net main.ts

構造

キーはKvKeyという型として実装されています。KvKeyreadonly KvKeyPart[]という型になっています。さらにKvKeyPartUint8Arraystringnumberbigintbooleanの5つの型のユニオンです。
つまり、['hello', 'world', 123]のように一連のシーケンスでキーを表します。

バリューは特徴でも紹介しましたが、JavaScriptのオブジェクトも含んださまざまな値を扱えます。
undefined
null
boolean
number
string
bigint
Uint8Array
Array
Object
Map
Set
Date
RegExp
Deno.KvU64
値の循環参照もサポートされていて、かなり自由度が高いです。

Deno KVを開く

全ての操作はDeno.KvAPIを介して行います。このAPIはDeno.openKv関数をを呼び出すことで利用できます。

const kv = await Deno.openKv();

openKv関数の第一引数には接続先を与えられます。Denoをデプロイする先がDeno Deployではない、もしくは異なるプロジェクトのDeno KVを利用する場合はパスを与えて接続先をカスタマイズします。

操作

主要な操作について紹介します。

get

getは指定したキーに対応するバリューとバージョンを返します。
kvからgetを呼び出して型引数でバリューの型を決めます。

await kv.get<string>(['hello', 'deno']);

getの第2引数にはoptionsを渡せます。optionsは現在consistencyをキーとするオブジェクトを渡すことができ、strongeventualで取得する値の精度を決めます。デフォルトではstrongとなっており、eventualにすると一番近いリージョンから取得した値を返します。

await kv.get<string>(
  ['hello', 'deno'],
  {
    consistency: 'eventual',
  },
);

得られる型はPromise<KvEntryMaybe>となっています。これは以下のように定義されています。

type KvEntry<T> = { key: KvKey; value: T; versionstamp: string };
type KvEntryMaybe<T> = KvEntry<T> | {
  key: KvKey;
  value: null;
  versionstamp: null;
};

keyには指定したキーを、valueには取得した値のバリューを指定した型Tを、versionstampにはバージョン情報をstringで返しています。値がなかった場合はvalueversionstampnullを詰めて返します。

詳細は紹介しませんが、getManyによって複数のキーに対応する複数の値を取得することも可能です。

await kv.getMany<[string, string, string]>([
  ['hello', 'node'],
  ['hello', 'deno'],
  ['hello', 'ts'],
]);

list

listは指定したセレクターに一致した値を返します。KvListIteratorというイテレーターとして実装されており、イテレータからは先ほどgetで見たKvEntryが渡されます。
イテレーターから取り出す値はキーを元に定められた順番で規則正しく並んでいます。
まずは大雑把に型を元にUint8Arraystringnumberbigintbooleanの順番で並べられます。そして型の中でもそれぞれに定義された順番で並べられます。stringだとUTF-8エンコーディングのバイト順に並べられます。

kv.list<string>({ prefix: ["hello"] });

第一引数にはKvListSelectorで表現されるセレクターを渡します。

type KvListSelector =
  | { prefix: KvKey }
  | { prefix: KvKey; start: KvKey }
  | { prefix: KvKey; end: KvKey }
  | { start: KvKey; end: KvKey };

prefixはそれと完全に一致したキーで始まる値を取り出します。startendはキーの範囲を指定します。
例えばKvListSelector{ start: ['hello', 'a'], end: ['hello', 'e'] }のようにした場合を考えます。この時はキーが['hello', 'deno']の値が取り出されます。['hello', 'node']['hello', 'ts']nodetsの箇所で範囲外となるので取り出されないです。
endに指定したキーは含まれないことに注意してください。end['hello', 'd']の場合は['hello', 'deno']は引っかかりません。

そして任意で第2引数をKvListOptionsとして渡せます。

type KvListOptions = {
  limit?: number;
  cursor?: string;
  reverse?: boolean;
  consistency?: KvConsistencyLevel;
  batchSize?: number;
}

limitは名前の通り最大の取得件数を指定します。cursorは取得位置を指定します。reverseは逆順で返すように指定します。consistencygetと同じです。
batchSizeは裏側でデータを取得する時のサイズを指定します。デフォルトでは500になっています。直接は見えない設定ですがこの取得単位でデータの整合性が担保され、データ間の整合性は担保されないことに気をつけてください。

返ってきたイテレーターは(当然ですが)以下のようにループさせて取得します。

const iter = kv.list<string>({ prefix: ["hello"] });
for await (const res of iter) console.log(res.value);

set

setは値の作成または更新を行います。

await kv.set(["users", "alex"], "alex");

キーを第1引数に、バリューを第2引数に渡します。
第3引数には任意で期限を渡せます。オブジェクトでexpireInをキーとしてms単位で数値を渡すことで生存期間設定します。

await kv.set(['hello', "deno"], 'good!', { expireIn: 5 });

返り値はPromise<KvCommitResult>です。以下のようになっていて、結果とversionstampを渡してくれます。

type KvCommitResult = {
  ok: true;
  versionstamp: string;
}

delete

deleteは値の削除です。

await kv.delete(['hello', "deno"]);

第1引数にキーを渡して対象の値が存在すれば削除、なければ何もしません。返り値はPromise<void>なので何も返しません。

トランザクション

Deno KVでトランザクションを張るにはatomic関数を使います。
kv.atomic()でトランザクションを開始してAtomicOperationクラスを提供します。
AtomicOperationクラスはchecksetcommitなどのメソッドを持ちます(他にも色々な操作メソッドを持っています)。

checkは取得した値が現在も最新であることを確認します。取得した値は引数にAtomicCheckのような形で渡します。

type AtomicCheck = {
  key: KvKey;
  versionstamp: string | null;
}

以下のようにして事前に取得したキーが['hello', 'deno']の値が最新であることを確かめます。

const hd = await kv.get<string>(['hello', 'deno']);
kv.atomic().check(hd)

結果として自身(AtommicCheck)を返して次に繋げます。

引数を調整して複数チェックできます。

const hd = await kv.get<string>(['hello', 'deno']);
// 意味ないですが、例として
kv.atomic().check(hd, hd, hd)

setは先ほど見たsetと同じような引数を渡して値の作成または更新を行います。check同様に返り値は自身(AtommicCheck)を返します。

commitatomicを呼び出してからcommitが呼ばれるまでの一連の操作を実行します。
以下の例では事前に取得した値が最新であることを確かめてから作成・更新します。

const key = ['hello', 'deno']
const hd = await kv.get<string>(key);
await kv.atomic()
  .check(hd)
  .set(key, 'good')
  .commit();

checkで矛盾した場合や作成・更新に失敗した場合はこれまでの操作を棄却します。
成功の有無はcommitの返り値から判断可能です。型はPromise<KvCommitResult | KvCommitError>のようになっていて、成功した時はKvCommitResultを返し、失敗した時はKvCommitErrorを返します。

type KvCommitResult = {
  ok: true;
  versionstamp: string;
}

type KvCommitError = {
  ok: false;
}

さいごに

Deno KVの基本的な操作を紹介しました。Denoのエコシステムがどんどん発達していることに喜びを感じます。
かなり重要な機能だと思うので本番のリリースが楽しみです。

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?