我对Deno KV很感兴趣,它将自己描述为“全球应用程序的全局数据库”。它是Deno应用程序的K/V存储,它捆绑了某种全球分布式/复制数据库服务。

代码示例如下所示:

const kv = await Deno.openKv();

等等,这看起来像是核心语言功能?他们是否将自己的专有托管云数据库作为其核心语言的一部分提供客户端?

它们不是 - 至少不是在Deno的开源实现中。我挖了进去,我想我明白他们在做什么。

Deno 本地K/V存储

这是一个仍然隐藏在Deno的“--unstable”标志后面的新功能。它于 2023 年 3 月 22 日登陆 Deno 1.32

入门文档包含以下令人困惑的文本:

可以使用“Deno.openKv()”函数打开数据库。此函数可以选择将磁盘上的数据库路径作为第一个参数。如果未指定路径,则数据库将保留在全局目录中,绑定到调用“Deno.openKv()”的脚本。将来对同一脚本的调用将使用相同的数据库。

“全局目录”是什么意思 - 这是他们所说的云服务吗?

不是。我运行了以下脚本并使用活动监视器来查看它在做什么:

import { sleep } from "https://deno.land/x/sleep/mod.ts";

const kv = await Deno.openKv();

// Persist an object at the users/alice key.
await kv.set(["users", "alice"], { name: "Alice", age: 44 });

// Read back this key.
const res = await kv.get(["users", "alice"]);
console.log(res.key); // [ "users", "alice" ]
console.log(res.value); // { name: "Alice" }

console.log('About to sleep for a minute')
await sleep(60);

我是这样运行的:

% deno run --unstable hello.js
[ "users", "alice" ]
{ name: "Alice", age: 44 }
About to sleep for a minute

这给了我一个 60 的时间来打开 Mac 上的活动监视器,找到“deno”进程,单击信息图标并单击打开文件和端口:

Deno 的打开文件和端口列表

关键文件是这个:

/Users/simon/Library/Caches/deno/location_data/82469e8b266758412fd6bbd0058abaee6712cadb9c64024473af6afcff9eba6f/kv.sqlite3-shm

这就是“全局目录”的含义 - 它指的是“~/Library/Caches/deno/location_data”文件夹,它似乎包含带有名称哈希的目录(可能是 Deno 脚本路径的哈希),可以包含额外的数据。

'kv.sqlite3' 是一个 SQLite 数据库!

以下是该数据库的架构:

sqlite-utils dump kv.sqlite3 
BEGIN TRANSACTION;
CREATE TABLE data_version (
  k integer primary key,
  version integer not null
);
INSERT INTO "data_version" VALUES(0,5);
CREATE TABLE kv (
  k blob primary key,
  v blob not null,
  v_encoding integer not null,
  version integer not null
) without rowid;
INSERT INTO "kv" VALUES(X'0275736572730002616C69636500',X'FF0F6F22046E616D652205416C696365220361676549587B02',1,5);
CREATE TABLE migration_state(
  k integer not null primary key,
  version integer not null
);
INSERT INTO "migration_state" VALUES(0,2);
CREATE TABLE queue (
  ts integer not null,
  id text not null,
  data blob not null,
  backoff_schedule text not null,
  keys_if_undelivered blob not null,

  primary key (ts, id)
);
CREATE TABLE queue_running(
  deadline integer not null,
  id text not null,
  data blob not null,
  backoff_schedule text not null,
  keys_if_undelivered blob not null,

  primary key (deadline, id)
);
COMMIT;

这很有趣。显然,他们已经提出了自己的原子K/V原语,然后设计了一个SQLite架构,用于将这些对象的序列化版本存储在磁盘上。

Deno 部署云托管版本

在我看来,他们已经设计了一种数据结构,可以很好地与新的托管FoundationDB,他们为Deno Deploy云服务构建的全球基础设施配合使用,然后找到了一种方法在SQLite之上本地支持同一组操作。

看起来神奇的部分是,如果您编写使用“Deno.openKv()”的代码而没有任何额外的参数,那么您的脚本在本地运行时将在该“location_data”目录中使用SQLite数据库...但是一旦部署到Deno Deploy,它将切换到使用FoundationDB支持的云数据库,并在全球范围内复制。

我发现这在开源商业模式方面特别有趣:他们正在将核心功能融入他们的框架中,他们的SaaS平台具有独特的优势,可以作为全球规模的升级提供。

一个计数器

我试图从KV营销登录页面复制它,最终不得不对其进行一些调整才能使其正常工作。

另存为“count.js”:

import { serve } from "https://deno.land/std/http/server.ts";

const kv = await Deno.openKv('count.db');

serve (async () => {
    await kv.atomic().sum(["visits"], 1n).commit();
    const res = await kv.get(["visits"]);
    console.log(res);
    return new Response(`Visits: ${res.value.value}`);
});

像这样运行:

deno --unstable run counter.js
✅ Granted read access to "count.db".
✅ Granted write access to "count.db".
✅ Granted net access to "0.0.0.0:8000".
Listening on http://localhost:8000/

控制台会记录以下内容:

{
  key: [ "visits" ],
  value: KvU64 { value: 19n },
  versionstamp: "00000000000000130000"
}
{
  key: [ "visits" ],
  value: KvU64 { value: 20n },
  versionstamp: "00000000000000140000"
}
{
  key: [ "visits" ],
  value: KvU64 { value: 21n },
  versionstamp: "00000000000000150000"
}

SQLite 数据库转储的相关部分如下所示:

CREATE TABLE data_version (
  k integer primary key,
  version integer not null
);
INSERT INTO "data_version" VALUES(0,22);
CREATE TABLE kv (
  k blob primary key,
  v blob not null,
  v_encoding integer not null,
  version integer not null
) without rowid;
INSERT INTO "kv" VALUES(X'0276697369747300',X'1600000000000000',2,22);
CREATE TABLE migration_state(
  k integer not null primary key,
  version integer not null
);
INSERT INTO "migration_state" VALUES(0,2);

必须升级到 Deno 1.32.5 才能让它工作('brew upgrade deno'), kv.atomic().sum(...)是全新的功能。

延伸阅读