Stoolap/Node: 一个快速且令人惊讶的本地 Node.js 驱动。
Stoolap/Node: A Native Node.js Driver That's Surprisingly Fast

原始链接: https://stoolap.io/blog/2026/02/19/introducing-stoolap-node/

## Stoolap:适用于Node.js的快速、嵌入式Rust SQL数据库 Stoolap是一个新的嵌入式SQL数据库,最初用Go构建,后为提高性能改写为Rust。作者认识到Node.js是一个关键的应用场景,因此开发了`@stoolap/node`,一个使用NAPI-RS的本地驱动,用于直接访问数据库,绕过HTTP开销。 虽然SQLite是一个强大的选择,但Stoolap提供了诸如MVCC事务、基于成本的查询优化器、并行执行和时间查询等高级功能。与`better-sqlite3`(SQLite的Node.js标准)的基准测试表明,Stoolap在**53项测试中赢得了47项**,在复杂的查询(如`COUNT DISTINCT`和子查询)中实现了显著的加速(高达138倍)。SQLite在简单、单行操作中仍保持优势。 Stoolap的速度源于其无锁MVCC、智能查询优化和并行执行能力。`@stoolap/node`驱动程序提供熟悉的API,支持异步/同步操作、预处理语句和事务。它还支持基于文件的持久化,并通过WAL提供可配置的持久性。 目前版本为v0.3.1,驱动程序已通过npm提供,并带有预构建的二进制文件,无需Rust工具链即可安装。未来的开发包括连接池和流式查询支持。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Stoolap/Node: 一个出奇快速的原生 Node.js 驱动 (stoolap.io) 9 分,murat3ok 发表于 1 小时前 | 隐藏 | 过去 | 收藏 | 1 条评论 帮助 petesergeant 发表于 2 分钟前 [–] Stoolap 看起来很有意思,有人愿意分享使用体验吗?回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

I’ve been working on Stoolap for a while now – an embedded SQL database written in pure Rust. It started as a Go project, grew into something much bigger, and recently hit a point where I thought: okay, this thing is fast, but how do people actually use it outside of Rust?

The answer, for a lot of developers, is Node.js. So I built @stoolap/node – a native driver powered by NAPI-RS that gives you direct access to Stoolap from JavaScript and TypeScript.

No HTTP server in between. No serialization overhead. Just your Node.js process talking directly to the database engine through native bindings.

Why Not Just Use SQLite?

Look, SQLite is great. I use it myself. It’s battle-tested, well-documented, and everywhere. But there are things it doesn’t do well – or doesn’t do at all.

Stoolap has MVCC transactions, a cost-based query optimizer, parallel execution, semantic query caching, and temporal queries with AS OF. These aren’t checkbox features; they actually show up in real workloads.

But the question I kept getting was: is it actually faster?

Fair question. So I ran the benchmarks.

The Benchmark

I wrote a comprehensive benchmark suite that runs 53 identical SQL operations against both @stoolap/node and better-sqlite3 (the gold standard for SQLite in Node.js). Same data, same queries, same machine.

The setup: 10,000 rows, a mix of point queries, joins, aggregations, subqueries, and analytical operations. Everything runs in-memory to keep it fair.

Here’s the summary:

Stoolap wins:  47 / 53 tests
SQLite wins:    6 / 53 tests

I wasn’t expecting that ratio, honestly. Let me break down where the biggest gaps are.

The differences aren’t small. Some of these numbers surprised me:

Operation Stoolap SQLite How Much Faster
COUNT DISTINCT 0.003 ms 0.41 ms 138x
DELETE (complex WHERE) 0.02 ms 2.44 ms 122x
Compare with subquery 0.04 ms 2.56 ms 64x
NOT EXISTS subquery 0.17 ms 9.70 ms 57x
Aggregation (GROUP BY) 0.32 ms 7.68 ms 24x
Scalar subquery 0.08 ms 1.68 ms 21x
DISTINCT + ORDER BY 0.04 ms 0.56 ms 14x
NOT IN subquery 0.61 ms 8.02 ms 13x
Window PARTITION BY 0.06 ms 0.43 ms 7x
IN subquery 0.69 ms 4.67 ms 7x

The COUNT DISTINCT result at 138x faster is probably the most dramatic. Stoolap maintains internal data structures that make distinct counting nearly free, while SQLite has to scan and deduplicate every time.

The subquery performance (EXISTS, NOT EXISTS, IN, NOT IN) comes from Stoolap’s semi-join optimization – it builds a HashSet from the subquery result and probes it, rather than running correlated subqueries row by row.

Where SQLite Still Wins

Let’s be honest about where SQLite is faster:

Operation SQLite Stoolap SQLite’s Edge
SELECT by ID 0.001 ms 0.002 ms 1.57x
UPDATE by ID 0.003 ms 0.004 ms 1.39x
Batch INSERT (100 rows) 0.39 ms 0.53 ms 1.35x
INSERT single row 0.008 ms 0.009 ms 1.13x
INNER JOIN 0.10 ms 0.11 ms 1.13x
Self JOIN 0.11 ms 0.11 ms 1.02x

These are all small margins – mostly in the 1.0x to 1.6x range. SQLite’s single-row operations benefit from decades of optimization on that specific path. The B-tree page cache is incredibly well-tuned for point lookups.

But notice the pattern: SQLite’s wins are on simple, single-row operations where both databases are already sub-millisecond. Stoolap’s wins are on the analytical and complex queries where the difference is 10x to 100x+.

What Makes It Fast

Three things, mainly:

MVCC without locks. Stoolap uses multi-version concurrency control, which means readers never block writers. In the Node.js driver, this means your async queries don’t stall behind pending writes.

Cost-based optimizer. Instead of always doing a sequential scan or always using an index, Stoolap estimates the cost of different execution strategies and picks the cheapest one. For queries with multiple conditions or joins, this makes a huge difference.

Parallel execution. Queries over large datasets automatically parallelize using Rayon’s work-stealing scheduler. Filters, hash joins, sorts, and distinct operations all scale across cores.

Using the Driver

The API should feel familiar if you’ve used better-sqlite3 or any other embedded database driver:

import { Database } from '@stoolap/node';

const db = await Database.open(':memory:');

await db.exec(`
  CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price FLOAT,
    category TEXT
  )
`);

// Positional parameters
await db.execute(
  'INSERT INTO products VALUES ($1, $2, $3, $4)',
  [1, 'Laptop', 999.99, 'Electronics']
);

// Named parameters
await db.execute(
  'INSERT INTO products VALUES (:id, :name, :price, :cat)',
  { id: 2, name: 'Book', price: 12.99, cat: 'Media' }
);

// Query returns plain objects
const products = await db.query(
  'SELECT * FROM products WHERE price > $1',
  [10]
);
// [{ id: 1, name: 'Laptop', ... }, { id: 2, name: 'Book', ... }]

Both async and sync APIs are available. Async runs on the libuv thread pool so it won’t block your event loop. Sync is slightly faster for simple operations if you’re in a context where blocking is fine (scripts, CLI tools, tests).

For hot paths, prepared statements skip parsing entirely:

const lookup = db.prepare('SELECT * FROM products WHERE id = $1');

// Reuse without re-parsing
const p1 = lookup.queryOneSync([1]);
const p2 = lookup.queryOneSync([2]);

Transactions work the way you’d expect:

const tx = await db.begin();
try {
  await tx.execute('INSERT INTO products VALUES ($1, $2, $3, $4)', [3, 'Phone', 699, 'Electronics']);
  await tx.execute('UPDATE products SET price = $1 WHERE id = $2', [899, 1]);
  await tx.commit();
} catch (e) {
  await tx.rollback();
  throw e;
}

File-Based Persistence

In-memory is great for benchmarks, but real applications need persistence. Stoolap uses WAL (Write-Ahead Logging) with configurable durability:

// Maximum durability -- fsync on every write
const db = await Database.open('./mydata?sync=full');

// Balanced (default) -- fsync on commit batches
const db = await Database.open('./mydata');

// Maximum throughput -- no fsync
const db = await Database.open('./mydata?sync=none');

Data survives process restarts. Snapshots run periodically in the background so WAL doesn’t grow forever.

Getting Started

npm install @stoolap/node

Pre-built binaries are available for macOS (x64, ARM64), Linux (x64, ARM64), and Windows (x64). No Rust toolchain required.

If you want to build from source:

git clone https://github.com/stoolap/stoolap-node.git
cd stoolap-node
npm install && npm run build
npm test

The full API documentation is in the driver docs.

What’s Next

The Node.js driver is at v0.3.1 right now. It covers the full Stoolap API – databases, transactions, prepared statements, batch operations, and all the query methods.

I’m planning to add connection pooling helpers and streaming query support in upcoming releases. If you run into issues or have feature requests, open an issue on GitHub.

Give it a try and let me know what you think.

联系我们 contact @ memedata.com