惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
S
Security @ Cisco Blogs
月光博客
月光博客
V
Vulnerabilities – Threatpost
The Hacker News
The Hacker News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Cisco Talos Blog
Cisco Talos Blog
J
Java Code Geeks
Scott Helme
Scott Helme
S
Schneier on Security
腾讯CDC
博客园 - 司徒正美
L
Lohrmann on Cybersecurity
Latest news
Latest news
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
V
Visual Studio Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
小众软件
小众软件
博客园 - Franky
Attack and Defense Labs
Attack and Defense Labs
美团技术团队
IT之家
IT之家
TaoSecurity Blog
TaoSecurity Blog
SecWiki News
SecWiki News
P
Proofpoint News Feed
阮一峰的网络日志
阮一峰的网络日志
博客园_首页
PCI Perspectives
PCI Perspectives
量子位
T
Threat Research - Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
Cyberwarzone
Cyberwarzone
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
L
LINUX DO - 最新话题
Forbes - Security
Forbes - Security
罗磊的独立博客
宝玉的分享
宝玉的分享
Simon Willison's Weblog
Simon Willison's Weblog
雷峰网
雷峰网
www.infosecurity-magazine.com
www.infosecurity-magazine.com
人人都是产品经理
人人都是产品经理
N
News and Events Feed by Topic

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno Zero-config Debugging with Deno and OpenTelemetry | Deno Exploring Art with TypeScript, Jupyter, Polars, and Observable Plot | Deno Deno v Oracle Update 3: Fighting the JavaScript Trademark | Deno Build a custom RAG AI agent in TypeScript and Jupyter | Deno How to get deep traces in your Node.js backend with OTel and Deno | Deno toranoana.deno #20 登録受付中(2025年3月14日) | Deno Node just added TypeScript support. What does that mean for Deno? | Deno The Dino 🦕, the Llama 🦙, and the Whale 🐋 | Deno Publish a lint rule, get a prize | Deno Deno 2.2: OpenTelemetry, Lint Plugins, node:sqlite | Deno If you're not using npm specifiers, you're doing it wrong | Deno How Deno's documentation is evolving | Deno Oracle justified its JavaScript trademark with Node.js—now it wants that ignored | Deno Introducing the JSR open governance board | Deno Intro to Wasm in Deno | Deno Announcing OpenAI on JSR | Deno Deno in 2024 | Deno Goodbye WinterCG, welcome WinterTC | Deno Build a SolidJS app with Deno | Deno Run your Next.js SSR app on Deno Deploy | Deno Solve Advent of Code 2024 with Deno and Win Prizes! | Deno Deno v. Oracle: Canceling the JavaScript Trademark | Deno Deno 2.1: Wasm Imports and other enhancements | Deno Build a Typesafe API with tRPC and Deno | Deno Self-contained Executable Programs with Deno Compile | Deno Build a Database App with Drizzle ORM and Deno | Deno Introducing your new JavaScript package manager: Deno | Deno Announcing Growthbook on JSR | Deno Build an Astro site with Deno | Deno How to convert CommonJS to ESM | Deno Announcing Deno 2 | Deno The Final Touches: What’s New In v2.0.0-rc.10 | Deno Announcing Stable V8 Bindings for Rust | Deno Deno 2.0 Release Candidate | Deno Secure, efficient private npm registries with Cloudsmith and Deno | Deno Painting the Plane as We Fly It: Designing JSR | Deno Introducing Web Cache API support on Deno Deploy | Deno Deno 1.46: The Last 1.x Release | Deno Protect your cloud spend with new Deno Deploy spend limits | Deno What we got wrong about HTTP imports | Deno Benchmarking AWS Lambda Cold Starts Across JavaScript Runtimes | Deno Announcing Supabase on JSR | Deno Deno 1.45: Workspace and Monorepo Support | Deno Introducing KV Backup for Deno Subhosting | Deno A Gentle Intro to TypeScript | Deno Announcing Hono on JSR | Deno How We Made the Deno Language Server Ten Times Faster | Deno How the Guardian uses Deno to audit accessibility and performance across their 2.7 million articles | Deno Introducing More Flexible Domain Association for Deno Subhosting | Deno The stabilization process of the Standard Library has begun | Deno Deno 1.44: Private npm registries, improved Node.js compat, and performance boosts | Deno How we built a secure, performant, multi-tenant cloud platform to run untrusted code | Deno The Deno Standard Library is now available on JSR | Deno How to document your JavaScript package | Deno Your Low Code Solution Needs an Escape Hatch | Deno Deno 1.43: Improved Language Server performance | Deno How Slack used Deno to save months of engineering effort in launching their new platform | Deno JSR Is Not Another Package Manager | Deno Announcing the Hookdeck SDK on JSR | Deno Announcing the Neon Serverless Driver on JSR | Deno An intro to TSConfig for JavaScript Developers | Deno How we built JSR | Deno How Netlify used Deno Subhosting to build a successful edge functions product | Deno Introducing Simpler Project Creation in Deno Deploy | Deno Deno 1.42: Better dependency management with JSR | Deno Introducing deployctl, the command line interface for Deno Deploy | Deno Introducing JSR - the JavaScript Registry | Deno How to add Monaco to a Next.js app and securely run untrusted user code | Deno Survey Results and Roadmap | Deno Deno 1.41: smaller deno compile binaries | Deno
Deno KV internals: building a database for the modern web | Deno
2023-09-14 · via Deno

Deno is designed to streamline web and cloud development with built-in modern tools, direct access to web platform APIs, and the capability to import modules via npm. Web apps very often require some persistent application state. Setting up a database involves numerous configuration steps and the subsequent integration of an ORM or other systems.

What if you could access such a database without any preliminary setup? This is what Deno KV allows you to do:

const kv = await Deno.openKv();
const userId = crypto.randomUUID();
await kv.set(["users", userId], { userId, name: "Alice" });

Why build Deno KV?

The Deno runtime doesn’t just run on your local machine in the form of the deno executable, but also on our Deno Deploy cloud platform which allows you to deploy your Deno apps to many regions around the world. This global deployment minimizes latency between your users and your app’s compute, which can be a huge performance boost for your app.

We knew that we wanted to build a database for Deno, because of a very simple problem we heard from many customers: unless your app’s data is also globally distributed, you won’t be able to make use the performance benefits you get by distributing your compute.

Additionally, because the Deno runtime is designed to run either locally in a single instance scenario, or globally in a multi-instance scenario, any API we introduce for one of the two scenarios should also work great in the other. This makes it easy to develop and test your app locally, and then deploy it globally without having to change your code or add any configuration.

This led us to a few design goals for Deno KV:

  • Scalable: a distributed database that can handle large volumes of data with high throughput
  • Highly performant: minimal high latency round-trips between compute and database to minimize impact of network latency
  • JavaScript-native: a database that’s designed to be used from JavaScript and TypeScript, with an API that natively uses JavaScript types
  • Atomic transactions: a database that provides atomic operations to ensure data integrity and enable complex application logic
  • Works seamlessly locally and globally: a database that works great in both single-instance and multi-instance scenarios

So, we set out to build two versions of the Deno KV system, with the same user-facing API. A version of Deno KV for local development and testing, built on SQLite, and a distributed systems version for use in production (especially on Deno Deploy). This post is about the implementation of the production, distributed systems version of Deno KV. If you’re interested in the SQLite implementation of Deno KV, you can read the source code yourself as it is open source.

FoundationDB: scalable, distributed, flexible, and production-ready

We chose to build Deno KV atop of FoundationDB, Apple’s open source distributed database used in iCloud and by Snowflake, since it’s perfect for building scalable database solutions: it’s thoroughly verified with deterministic simulation, scalable and efficient, and provides the transactional key-value storage API.

While FoundationDB provides the necessary mechanisms of a robust distributed database, there’s still several challenges when turning that into a seamless JavaScript-native experience that works within our Deno Deploy platform:

  • Deno KV is multi-tenant not only in data but also in configuration. Different users have different replication setups, backup policies, and throughput quotas. FoundationDB does not have native mechanisms to handle this.
  • We want Deno KV to be fully JavaScript-native and use JavaScript types. For example, Deno KV can store signed varints (bigint), and we want to support an atomic sum operation on these, but FoundationDB itself does not support atomic sums on varints.
  • To minimize potential for unbounded latency between compute and data, the Deno KV API is designed around non-interactive transactions (i.e. atomic operations), while FoundationDB provides optimistic interactive transactions. Even though possible, there is unnecessary overhead when implementing the Deno KV API on top of FoundationDB.

These constraints led us to design a new system ontop of a FoundationDB that we call the “transaction layer”. It performs transaction processing and cross region data repliation in a distributed way, while still delegating the difficult aspects of a distributed database to FoundationDB: sharding, synchronous replication within a cluster, ensuring serializable and linearizable processing of transactions, and storing data durably.

Deno Deploy Transaction Layer that interfaces between Deno Deploy isolates and FoundationDB

Each Deno KV command from Deno Deploy is processed and optimized in our Transaction Layer before its committed to FoundationDB

Let’s dive into how we designed our transaction layer for atomicity, minimal latency, and high concurrency.

Atomic operations with minimal network requests

Atomic operations, usually interactive transactions, as in they require multiple requests to the database to guarantee atomicity.

Interactive atomic operation

However, for a globally distributed database, interactive transactions are costly. If a write operation requires several trips between the compute server and a database region, then a web app performing multiple write operations could experience high network latency.

Non-interactive atomic operation in global Deno KV

To minimize latency, we designed global Deno KV to be non-interactive — all transactions should complete within one or two network trips. We’ve done so by enclosing all atomic writes into a “bag” of conditions, write commands, and conflict-free mutations:

  • Condition: to guarantee atomicity, we check the versionstamp of the key. If it’s false at the time the operation is executed, the entire operation is discarded
  • Write command: any .set() or .delete() operation on a key
  • Conflict-free mutation: any function that takes the old value of the key and returns a new value. For example, the mutation type .sum() adds the provided operand to the old value

To create this “bag”, Deno KV requires all atomic operations to be chained following .atomic():

const kv = await Deno.openKv();
const change = 10;

const bob = await kv.get(["balance", "bob"]);
const liz = await kv.get(["balance", "liz"]);
if (bob.value < change) {
  throw "not enough balance";
}

const success = await kv.atomic()
  .check(bob, liz) 
  .set(["balance", "bob"], bob.value - change)
  .set(["balance", "liz"], liz.value + change)
  .commit();

This API design for atomic operations ensures minimal trips between compute and database for optimum performance.

Building on top of FoundationDB’s lock-free system

“Locks” in databases are mechanisms to ensure data integrity. These mechanisms only let one transaction modify or access data. In contrast, a lock-free system allows concurrent processes to access and modify data, providing parallelism and scalability without sacrificing data integrity.

Despite FoundationDB using a lock-free design, simply delegating the conflict-check mechanism to FoundationDB would lead to latency issues, especially if the atomic operation performs a conflict-free mutation that cannot be pushed down to a primitive of the underlying database.

await kv.atomic().sum(["visitor_count"], 1n).commit();
This atomic operation cannot be pushed down because FoundationDB does not natively understand variable size interger types, like JS bigints, so does not implement any conflict-free mutation operation for that type.

To maximize performance and concurrency for atomic operations, we built the Deno KV Transaction Layer that manages the global order of atomic operations. For each transaction that is received by this Transaction Layer:

  1. The transaction is assigned a Transaction Sequence Number (“TSN”), a monotonic integer whose order is equivalent to the linearized order of atomic operations, by a sequencer
  2. The transaction is batched with other transactions, from which the evaluator constructs a graph that describes the relations between all conditions, write commands, and conflict-free mutations. The evaluator then processes the graph with maximum concurrency until the value of every graph node is known.
  3. The transaction is finally committed to FoundationDB by the writer

The sequencer, evaluator, and writer are distinct components that work together to process high volumes of operations. But how do we squeeze out even more performance from this pipeline?

Faster operations with speculative execution

Speculative execution is a technique for maximizing instruction processing throughput where some work is done that might not be needed.

Deno KV’s Transaction Layer uses this technique, where transactions are assigned a global order in a centralized way, but processed out-of-order elsewhere. Outputs of transactions are then used speculatively in future transactions before they are durable on disk. The effect of a transaction is only visible externally after it’s been committed to FoundationDB.

The core data structure of this mechanism is the “Transaction Reorder Buffer”, which manages the flow of transactions to process:

Transaction Reorder Buffer

The numbers above indicate Transaction Sequence Numbers and the column beneath each represents a single operation received by Transaction Layer.

The sequencer is responsible for issuing monotonic integers that are continuous within an epoch. While being a singleton for every KV database, sequencing is a very cheap operation since it’s just an increment on an in-memory atomic counter. The sequencer also only needs to wait for the previous sequencer to be committed (green).

The evaluator processes transactions in batches. In each batch, the evaluator constructs a “data flow subgraph” that describes the relations between all conditions, write commands, and conflict-free mutations. It’s a graph of operations that determine whether the transactions succeed or fail, and the operations final values. The evaluator then processes the subgraph with maximum concurrency until the value of every graph node is known.

To better illustrate this data flow subgraph, which enables high concurrency in a lock-free system, let’s look at an example:


await kv
  .atomic()
  .check({ key: ["users", UID1], versionstamp: V1 })
  .check({ key: ["user_by_login", "bob"], versionstamp: null })
  .set(["users", UID1], user1)
  .set(["user_by_login", "bob"], UID1)
  .delete(["user_by_login", "alice"])
  .commit();


await kv
  .atomic()
  .check({ key: ["user_by_login", "bob"], versionstamp: null })
  .set(["users", UID2], user2)
  .set(["user_by_login", "bob"], UID2)
  .commit();

These two operations above are mutually conflicting, so only one can succeed. Assuming the second operation is assigned a larger TSN than the first, this would be the data flow subgraph constructed from an evaluator batch containing these two operations:

Data flow subgraph example

An example of a data flow subgraph, a graph of operations that determine whether the transactions succeed or fail, and the operations final values.

This diagram looks bit complicated - let’s walk through it.

The top row (with the two GetMetadata boxes) represents the two .check() commands from Client 1 (.check({ key: ["users", UID1], versionstamp: V1 }) and .check({ key: ["user_by_login", "bob"], versionstamp: null })). They flow into the horizontal AND line because both checks need to succeed.

The second row from the top (beneath the horizontal AND line) shows three MUX (aka a multiplexer logic gate) operations, which represents Client 1’s .set() and .delete() commands. Above each MUX gate shows each commands inputs:

  • .set(["users", UID1], user1) -> user1 and [pass]
  • .set(["users_by_login", "bob"], UID1) -> UID1 and [pass]
  • .delete(["user_by_login", "alice"]) -> [delete] and [pass]

Note that [pass] simply means keep the old value, since those commands have a single input when MUX gates can accept two inputs.

While the first and third MUX gate output is green (final result), the middle one is blue (intermediary result). Keep in mind that final result is not a value; it’s a write function whose input (represented in the diagram by ?) will be evaluated after this graph is created.

So why is there an intermediary result? This is because Client 2’s .check() needs to assert versionstamp on the shared key ["user_by_login", "bob"]. In order to resolve this intermediary result and get to a final result, we use another MUX gate that takes the intermediary result and the GetMetadata of ["user_by_login", "bob"].

This takes us to the bottom row, where there are two MUX gates representing Client 2’s .set() commands (.set(["users", UID2], user2) and .set(["user_by_login", "bob"], UID2)). From these gates, we can reach final results.

After the evaluator creates this data flow subgraph from a batch of operations, it calculates the results, and buffers them in memory. These in-memory results are available to use in the next evaluator batches, for more speculative execution, since the results may not yet be visible in FoundationDB. These in-memory results will be discarded as the known committed version advances, or when we know for sure we can read the data from FoundationDB.

Finally, the writer is initiated to persist the mutations. The writer also processes transactions in batches. In each batch, the writer writes the outcome of the transactions to the underlying database, and performs various other tasks, like writing the replication log and queuing up keys for expiration.

Using speculative execution with the sequencer-evaluator-writer pipeline helps maximize concurrency and performance while maintaining data integrity for atomic operations.

Conclusion

The development of Deno KV was informed by the needs of modern web development and the possibilities presented by FoundationDB. Our emphasis has always been on functionality, scalability, and the seamless integration of JavaScript. By leveraging FoundationDB’s lock-free system and introducing features like the transaction layer and speculative execution, we aimed to address both performance and user experience.

However, technology and needs evolve. While we believe in the foundation and principles behind Deno KV, we’re also aware of the continuous advancements in the tech landscape. Our journey with Deno KV is an ongoing one, shaped by both our vision and the invaluable feedback from our user community.

As we look ahead, we are dedicated to refining Deno KV, responding to emerging needs, and ensuring its adaptability in a rapidly evolving web and cloud environment. We welcome all developers to try Deno KV, and more importantly, to share insights and feedback that can guide its future direction.

Deno KV is now in open beta.

Signup for Deno Deploy and get access to a zero-config, globally distributed database for free.