前两天有幸拿到 KingbaseES V9R2C13(MongoDB兼容版)内测软件包,我第一件事是找台机器装上,然后把 Node.js 的 mongodb 驱动直接连过去。验证是否真的"兼容 MongoDB"。
环境是一台 Oracle Linux 8.10 x86 主机。软件放 /opt/kingbase/KingbaseES,数据放 /opt/kingbase/data,kingbase 用户跑库,systemd 管进程。
服务起来之后有个细节值得说:同一个 KingbaseES 进程监听了两个端口。54321 走 SQL,27017 走 MongoDB 协议。一套实例,两个入口。
驱动用的是 mongodb@6.21.0。基础回归过了 19 个场景,深度探测过了 38 个。几组我心里没底的用例,另外在 MongoDB 7.0.28 上跑了同一份 Node.js 脚本,两边对返回值和数据变化。
关于安装
原厂 ISO,setup.sh -i console,一路装完。目录和权限按常规数据库服务的习惯来:软件目录 /opt/kingbase/KingbaseES,数据目录 /opt/kingbase/data,kingbase 用户运行数据库,systemd 负责服务启停和开机自启。
MongoDB 协议这块能力来自 DocumentDB 扩展。测试库装了三个,版本都是 0.110-0:
text
documentdb_core
documentdb
documentdb_extended_rum
代码还是熟悉的写法
驱动用 MongoDB URI 连接,认证方式按手册显式指定成 SCRAM-SHA-256。应用代码那边继续用 MongoClient、Db、Collection,没有额外包装。
javascript
const { MongoClient } = require('mongodb');
const uri =
'mongodb://<user>:<password>@127.0.0.1:27017/test' +
'?authSource=admin' +
'&authMechanism=SCRAM-SHA-256' +
'&directConnection=true';
const client = new MongoClient(uri);
await client.connect();
const db = client.db('test');
const users = db.collection('users');
await users.insertOne({ name: 'Alice', tags: ['mongo', 'kingbase'] });
console.log(await users.findOne({ name: 'Alice' }));
await client.close();
握手回来 hello.ok = 1,maxWireVersion = 21,buildInfo.version = 7.0.42。驱动那边认为自己连的是一个 7.0 的 MongoDB。后面所有集合创建、文档写入、查询和更新,都在这个连接上做完。
BSON 和文档操作
先测的是 ObjectId、Date、Binary、Decimal128、数组和嵌套文档的写入与读取。深度用例还专门塞了 4MB 的文档和 50 层嵌套的文档,读回来核对长度和层级。
查询覆盖了范围条件、逻辑条件、数组匹配、exists、exists、exists、type、regex 和 expr。排序、分页、投影、计数、distinct 也都进了回归脚本。
更新这块用例最多:inc、inc、inc、mul、min、min、min、max、currentDate、currentDate、currentDate、rename、unset、unset、unset、bit,加上 push、push、push、pull、addToSet、addToSet、addToSet、pop 这些数组操作。
arrayFilters 我是单独挑出来测的。订单明细、库存条目、多层配置,改数组里指定的某一条是日常写法,兼容层最容易在这种地方露馅。
javascript
await orders.updateOne(
{ orderNo: 'SO-20260723' },
{ $set: { 'items.$[item].status': '已发货' } },
{ arrayFilters: [{ 'item.sku': 'SKU-1001' }] }
);
结果是通过的:只有 SKU-1001 那条明细的状态变了,同一数组里的其他明细原样没动。upsert、$setOnInsert、replaceOne、updateMany、deleteMany 一并过了。bulkWrite 的有序和无序两种模式分别跑,写入条数和最终文档内容都和预期一致。
TTL 确实删掉了过期文档
索引测了普通、复合、唯一、稀疏、部分、TTL、文本、通配符和哈希。唯一索引撞到重复值时,驱动收到 11000,和原生一致。
TTL 这一项我没停在"索引建出来了"。元数据建得出来,不代表后台任务真的在跑。所以写了一条已经过期的文档,等后台任务执行,再去查剩余数量,结果是 0。
javascript
await sessions.createIndex(
{ expireAt: 1 },
{ expireAfterSeconds: 0 }
);
collMod 改 TTL 过期时间也测了,命令会返回旧值和新值。英文文本索引用 text 加 search 查关键词,没问题。
聚合跑到了关联、递归和窗口计算
基础管道这一层没什么悬念:match、match、match、project、group、group、group、sort、facet、facet、facet、bucket、bucketAuto、bucketAuto、bucketAuto、sortByCount、$sample。
真正想看的是重的那几个。跨集合的 lookup、合并结果集的 unionWith、递归查询的 graphLookup,各写了单独用例。graphLookup,各写了单独用例。graphLookup,各写了单独用例。setWindowFields 的排名和窗口计算也过了。
javascript
const result = await orders.aggregate([
{ $match: { status: '已完成' } },
{ $group: {
_id: '$customerId',
totalAmount: { $sum: '$amount' },
orderCount: { $sum: 1 }
} },
{ $sort: { totalAmount: -1 } },
{ $limit: 20 }
]).toArray();
out把聚合结果写进集合,一次通过。out 把聚合结果写进集合,一次通过。out把聚合结果写进集合,一次通过。merge 一开始报错,按 MongoDB 对 on 字段唯一索引的要求补上索引之后完成了合并。这个行为和原生也是一样的。
JSON Schema 拦得住不合规文档
测试环境开了 documentdb.enableSchemaValidation=on。建集合时定义 $jsonSchema,要求 name 必填且是字符串,age 是非负整数。
javascript
await db.createCollection('validated_users', {
validator: {
$jsonSchema: {
bsonType: 'object',
required: ['name'],
properties: {
name: { bsonType: 'string' },
age: { bsonType: 'int', minimum: 0 }
}
}
}
});
合规文档正常写入。缺 name、name 类型不对、age 小于 0,数据库都返回 121 Document failed validation。也就是说,一部分基础数据规则可以压到集合上,不用全堆在应用层。
事务和 GridFS
事务从 startSession 开始,提交和回滚分别测。提交后查得到,回滚后查不到,多集合事务用同样的方式核对。
还有一条用例我比较在意:起一个活动事务,再用 killSessions 把会话干掉。会话结束之后,那个事务的未提交写入没有留在集合里。这条要是不通过,应用异常断连的场景会很难收拾。
GridFS 就是走完一圈:上传文件、下载、比对内容、删除。
javascript
const bucket = new GridFSBucket(db, { bucketName: 'attachments' });
const uploadId = new ObjectId();
await new Promise((resolve, reject) => {
const stream = bucket.openUploadStreamWithId(uploadId, 'report.pdf');
stream.once('finish', resolve);
stream.once('error', reject);
stream.end(Buffer.from('report content'));
});
这轮测下来,我的判断
兼容度比我预期的高。
19 加 38 个场景,从 BSON 类型、CRUD、索引,一路到聚合、JSON Schema、事务和 GridFS,官方驱动全程没换写法,也没有为了绕开哪个不支持的特性去改代码。graphLookup、graphLookup、graphLookup、setWindowFields、arrayFilters、TTL 后台回收、killSessions 之后的事务清理,这几条是我事先觉得最容易出问题的,结果都过了。$merge 那次报错也不算兼容问题,补上 on 字段的唯一索引就通过,要求和原生 MongoDB 一致。
对已有项目来说,改造入口很清楚:换掉测试环境的连接串,把集合初始化、核心读写、索引和聚合管道跑一遍回归,跑完就知道数据层要动多少。以这次的结果看,用 Node.js 驱动的应用大概率不用动多少代码。
双端口是我觉得最实用的一点。老系统继续走 54321 的 SQL 口,新应用走 27017 的 MongoDB 口,数据平台按自己的习惯选。存量系统改造最怕的就是"一次性切换",这个设计直接把它变成了可以分阶段推进的事。
这轮的定位是功能验证,结论是能用,而且用得很顺。接下来我打算拿真实的集合结构和数据量再跑一轮,重点看性能、复制和高可用。功能这一层已经看清楚了,剩下的是把量压上去。各位朋友觉得Kingbase的这个版本如何?