rename project to MultiforaDB
Prose and benchmark tables use MultiforaDB; the binary, the CLI usage line, the log-message prefix and the default database file use multiforadb. Two consequences worth noting: - build.zig.zon's fingerprint is derived from the package name, so it had to change with it (Zig refuses to build otherwise). A consumer pinning this package by fingerprint needs updating. - the default --db path is now multiforadb.log, and getCmdLineOpts reports it as dbpath. An existing mongo-lite.log has to be passed explicitly with --db. The e2e harness abbreviated the old name as ML_; that is now MFDB_, including the documented ML_BIN override (MFDB_BIN) and the scratch file names. MD_ (mongod) is untouched. compare-run.sh spawned the server by absolute path under a sandbox/mongo-lite directory that no longer exists; that block already runs from tests/e2e, so it uses a relative path now. The archived reports under tests/e2e/results/ keep the old name: they record what the old binary measured.
This commit is contained in:
32
README.md
32
README.md
@@ -1,4 +1,4 @@
|
|||||||
# mongo-lite
|
# MultiforaDB
|
||||||
|
|
||||||
A lightweight, embedded MongoDB-compatible document database written in
|
A lightweight, embedded MongoDB-compatible document database written in
|
||||||
Zig 0.16. Like SQLite, it stores everything in a single file; unlike SQLite,
|
Zig 0.16. Like SQLite, it stores everything in a single file; unlike SQLite,
|
||||||
@@ -11,7 +11,7 @@ driver, PyMongo — connect over TCP and just work.
|
|||||||
zig build # build the server
|
zig build # build the server
|
||||||
zig build test # run the unit test suite
|
zig build test # run the unit test suite
|
||||||
|
|
||||||
zig-out/bin/mongo-lite --port 27017 --db data.log --compact-threshold 256m
|
zig-out/bin/multiforadb --port 27017 --db data.log --compact-threshold 256m
|
||||||
|
|
||||||
# in another terminal:
|
# in another terminal:
|
||||||
mongosh --port 27017
|
mongosh --port 27017
|
||||||
@@ -181,27 +181,27 @@ the `tests/e2e/big.js` harness (12-core/32 GB Mac):
|
|||||||
## Performance vs MongoDB
|
## Performance vs MongoDB
|
||||||
|
|
||||||
`tests/e2e/compare-run.sh` runs the same driver workload (1 GB, 65,536 ×
|
`tests/e2e/compare-run.sh` runs the same driver workload (1 GB, 65,536 ×
|
||||||
16 KB docs, every write durable — mongo-lite fsyncs per command, mongod
|
16 KB docs, every write durable — MultiforaDB fsyncs per command, mongod
|
||||||
runs with `j: true`) against each server and prints a side-by-side table.
|
runs with `j: true`) against each server and prints a side-by-side table.
|
||||||
With the ReleaseFast default build (MongoDB 8.3.7 on the same Mac):
|
With the ReleaseFast default build (MongoDB 8.3.7 on the same Mac):
|
||||||
|
|
||||||
| benchmark | mongo-lite | mongodb | winner |
|
| benchmark | MultiforaDB | mongodb | winner |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| insertOne (sequential) | 0.20 ms | 4.7 ms | **mongo-lite ×24** |
|
| insertOne (sequential) | 0.20 ms | 4.7 ms | **MultiforaDB ×24** |
|
||||||
| bulk insert (insertMany) | 752 MB/s | 744 MB/s | mongo-lite |
|
| bulk insert (insertMany) | 752 MB/s | 744 MB/s | MultiforaDB |
|
||||||
| createIndex({k: 1}) | 67 ms | 76 ms | **mongo-lite** |
|
| createIndex({k: 1}) | 67 ms | 76 ms | **MultiforaDB** |
|
||||||
| countDocuments({}) | 2.6 ms | 11.2 ms | **mongo-lite ×4** |
|
| countDocuments({}) | 2.6 ms | 11.2 ms | **MultiforaDB ×4** |
|
||||||
| findOne({_id}) | 0.45 ms | 0.65 ms | **mongo-lite** |
|
| findOne({_id}) | 0.45 ms | 0.65 ms | **MultiforaDB** |
|
||||||
| findOne indexed | 0.54 ms | 4.6 ms | **mongo-lite ×8** |
|
| findOne indexed | 0.54 ms | 4.6 ms | **MultiforaDB ×8** |
|
||||||
| range-scan count | 13.7 ms | 12.6 ms | mongodb ×1.1 |
|
| range-scan count | 13.7 ms | 12.6 ms | mongodb ×1.1 |
|
||||||
| sort + limit(20), on `_id` | 2.3 ms | 2.0 ms | mongodb ×1.1 |
|
| sort + limit(20), on `_id` | 2.3 ms | 2.0 ms | mongodb ×1.1 |
|
||||||
| sort + limit(20), indexed field | 1.0 ms | — | — |
|
| sort + limit(20), indexed field | 1.0 ms | — | — |
|
||||||
| aggregate $group | 8.1 ms | 12.3 ms | **mongo-lite** |
|
| aggregate $group | 8.1 ms | 12.3 ms | **MultiforaDB** |
|
||||||
| updateOne({_id}) | 0.15 ms | 0.19 ms | **mongo-lite** |
|
| updateOne({_id}) | 0.15 ms | 0.19 ms | **MultiforaDB** |
|
||||||
| updateMany (65 docs) | 1.7 ms | 6.1 ms | **mongo-lite ×3.6** |
|
| updateMany (65 docs) | 1.7 ms | 6.1 ms | **MultiforaDB ×3.6** |
|
||||||
| deleteOne + insert | 0.50 ms | 4.9 ms | **mongo-lite ×10** |
|
| deleteOne + insert | 0.50 ms | 4.9 ms | **MultiforaDB ×10** |
|
||||||
| server RSS | 539 MB | 1.3 GB | **mongo-lite ×2.4** |
|
| server RSS | 539 MB | 1.3 GB | **MultiforaDB ×2.4** |
|
||||||
| kill -9 → reopen | 0.8 s | 1.3 s | **mongo-lite** |
|
| kill -9 → reopen | 0.8 s | 1.3 s | **MultiforaDB** |
|
||||||
| db on disk | 97 MB | 91 MB | mongodb |
|
| db on disk | 97 MB | 91 MB | mongodb |
|
||||||
|
|
||||||
The engine now holds every document as canonical BSON bytes in a
|
The engine now holds every document as canonical BSON bytes in a
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "mongo-lite",
|
.name = "multiforadb",
|
||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
});
|
});
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
@@ -31,7 +31,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
const run_cmd = b.addRunArtifact(exe);
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
if (b.args) |args| run_cmd.addArgs(args);
|
if (b.args) |args| run_cmd.addArgs(args);
|
||||||
const run_step = b.step("run", "Run mongo-lite server");
|
const run_step = b.step("run", "Run multiforadb server");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
const test_mod = b.createModule(.{
|
const test_mod = b.createModule(.{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.{
|
.{
|
||||||
.name = .mongo_lite,
|
.name = .multiforadb,
|
||||||
.version = "0.0.1",
|
.version = "0.0.1",
|
||||||
.minimum_zig_version = "0.16.0",
|
.minimum_zig_version = "0.16.0",
|
||||||
.paths = .{""},
|
.paths = .{""},
|
||||||
.fingerprint = 0xcca51d85af632c24,
|
.fingerprint = 0x8305abefdc9db3e3,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ const std = @import("std");
|
|||||||
|
|
||||||
/// Panic unless `ok`. Active in every optimize mode; see the module comment.
|
/// Panic unless `ok`. Active in every optimize mode; see the module comment.
|
||||||
pub fn assert(ok: bool) void {
|
pub fn assert(ok: bool) void {
|
||||||
if (!ok) @panic("mongo-lite: assertion failed");
|
if (!ok) @panic("multiforadb: assertion failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Panic unless `ok`, naming the invariant that broke. Prefer this where the
|
/// Panic unless `ok`, naming the invariant that broke. Prefer this where the
|
||||||
/// condition alone does not say what went wrong -- the message lands in the
|
/// condition alone does not say what went wrong -- the message lands in the
|
||||||
/// crash output, which may be all an operator has to go on.
|
/// crash output, which may be all an operator has to go on.
|
||||||
pub fn assert_msg(ok: bool, comptime message: []const u8) void {
|
pub fn assert_msg(ok: bool, comptime message: []const u8) void {
|
||||||
if (!ok) @panic("mongo-lite: assertion failed: " ++ message);
|
if (!ok) @panic("multiforadb: assertion failed: " ++ message);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "assert passes on true and is callable in every mode" {
|
test "assert passes on true and is callable in every mode" {
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ pub fn dispatch(ctx: *Context, msg: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
// rather than turning an applied write into an error the client retries.
|
// rather than turning an applied write into an error the client retries.
|
||||||
if (ctx.engine.take_compact()) {
|
if (ctx.engine.take_compact()) {
|
||||||
ctx.engine.compact() catch |err| {
|
ctx.engine.compact() catch |err| {
|
||||||
std.debug.print("mongo-lite: compaction failed: {s}\n", .{@errorName(err)});
|
std.debug.print("multiforadb: compaction failed: {s}\n", .{@errorName(err)});
|
||||||
ctx.engine.request_compact();
|
ctx.engine.request_compact();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,7 @@ fn cmd_ping(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
|
|
||||||
fn cmd_build_info(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
fn cmd_build_info(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
||||||
try reply.put("version", .{ .string = "4.4.0" });
|
try reply.put("version", .{ .string = "4.4.0" });
|
||||||
try reply.put("gitVersion", .{ .string = "mongo-lite" });
|
try reply.put("gitVersion", .{ .string = "multiforadb" });
|
||||||
try reply.put("versionArray", .{ .array = try int_array(reply, &.{ 4, 4, 0, 0 }) });
|
try reply.put("versionArray", .{ .array = try int_array(reply, &.{ 4, 4, 0, 0 }) });
|
||||||
try reply.put("openssl", .{ .doc = &.{} });
|
try reply.put("openssl", .{ .doc = &.{} });
|
||||||
try reply.put("loaderFlags", .{ .string = "" });
|
try reply.put("loaderFlags", .{ .string = "" });
|
||||||
@@ -283,7 +283,7 @@ fn cmd_host_info(ctx: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
|||||||
|
|
||||||
fn cmd_get_cmd_line_opts(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
fn cmd_get_cmd_line_opts(_: *Context, _: *wire.Message, reply: *wire.Reply) !void {
|
||||||
const argv = try reply.arena_alloc().alloc(bson.Pair, 2);
|
const argv = try reply.arena_alloc().alloc(bson.Pair, 2);
|
||||||
argv[0] = .{ .key = "dbpath", .value = .{ .string = "mongo-lite.log" } };
|
argv[0] = .{ .key = "dbpath", .value = .{ .string = "multiforadb.log" } };
|
||||||
argv[1] = .{ .key = "port", .value = .{ .int32 = 27017 } };
|
argv[1] = .{ .key = "port", .value = .{ .int32 = 27017 } };
|
||||||
try reply.put("argv", .{ .array = &.{} });
|
try reply.put("argv", .{ .array = &.{} });
|
||||||
try reply.put("parsed", .{ .doc = argv });
|
try reply.put("parsed", .{ .doc = argv });
|
||||||
|
|||||||
10
src/db.zig
10
src/db.zig
@@ -986,7 +986,7 @@ pub const Engine = struct {
|
|||||||
// epilogue called us.
|
// epilogue called us.
|
||||||
self.request_compact();
|
self.request_compact();
|
||||||
std.debug.print(
|
std.debug.print(
|
||||||
"mongo-lite: compaction gave up after {d} attempts (concurrent writes); will retry\n",
|
"multiforadb: compaction gave up after {d} attempts (concurrent writes); will retry\n",
|
||||||
.{attempt_max},
|
.{attempt_max},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1045,7 +1045,7 @@ pub const Engine = struct {
|
|||||||
while (doc_it.next()) |doc_entry| {
|
while (doc_it.next()) |doc_entry| {
|
||||||
ix.append_doc_entries(self.gpa, coll.doc_bytes(doc_entry.value_ptr.*), doc_entry.key_ptr.*) catch |err| switch (err) {
|
ix.append_doc_entries(self.gpa, coll.doc_bytes(doc_entry.value_ptr.*), doc_entry.key_ptr.*) catch |err| switch (err) {
|
||||||
error.ParallelArrays => {
|
error.ParallelArrays => {
|
||||||
std.debug.print("mongo-lite: WARNING: index '{s}' cannot index an existing document; entry skipped\n", .{ix.name});
|
std.debug.print("multiforadb: WARNING: index '{s}' cannot index an existing document; entry skipped\n", .{ix.name});
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
else => return err,
|
else => return err,
|
||||||
@@ -1053,7 +1053,7 @@ pub const Engine = struct {
|
|||||||
}
|
}
|
||||||
// Tolerated, not enforced: the database must always open.
|
// Tolerated, not enforced: the database must always open.
|
||||||
if (try ix.finish_bulk(self.gpa, false)) {
|
if (try ix.finish_bulk(self.gpa, false)) {
|
||||||
std.debug.print("mongo-lite: WARNING: unique index '{s}' has duplicate keys in existing data; duplicates not enforced for existing documents\n", .{ix.name});
|
std.debug.print("multiforadb: WARNING: unique index '{s}' has duplicate keys in existing data; duplicates not enforced for existing documents\n", .{ix.name});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1103,7 +1103,7 @@ fn apply_record(ctx: *anyopaque, record: storage.Record, doc: *bson.Document) an
|
|||||||
switch (record.type) {
|
switch (record.type) {
|
||||||
storage.record_type_index_create => {
|
storage.record_type_index_create => {
|
||||||
self.register_index_from_spec(coll, doc) catch |err| {
|
self.register_index_from_spec(coll, doc) catch |err| {
|
||||||
std.debug.print("mongo-lite: index create record failed to apply: {s}\n", .{@errorName(err)});
|
std.debug.print("multiforadb: index create record failed to apply: {s}\n", .{@errorName(err)});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
@@ -1121,7 +1121,7 @@ fn apply_record(ctx: *anyopaque, record: storage.Record, doc: *bson.Document) an
|
|||||||
}
|
}
|
||||||
|
|
||||||
const id_value = doc.get("_id") orelse {
|
const id_value = doc.get("_id") orelse {
|
||||||
std.debug.print("mongo-lite: log record without _id, skipping\n", .{});
|
std.debug.print("multiforadb: log record without _id, skipping\n", .{});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const id_key = try bson.serialize_value(self.gpa, id_value);
|
const id_key = try bson.serialize_value(self.gpa, id_value);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// mongo-lite core library. Public entry point for tests and the server.
|
// multiforadb core library. Public entry point for tests and the server.
|
||||||
|
|
||||||
pub const assert = @import("assert.zig");
|
pub const assert = @import("assert.zig");
|
||||||
pub const bson = @import("bson.zig");
|
pub const bson = @import("bson.zig");
|
||||||
|
|||||||
20
src/main.zig
20
src/main.zig
@@ -2,12 +2,12 @@ const std = @import("std");
|
|||||||
const mongo = @import("mongo");
|
const mongo = @import("mongo");
|
||||||
|
|
||||||
const usage =
|
const usage =
|
||||||
\\mongo-lite — lightweight MongoDB-compatible document database
|
\\MultiforaDB — lightweight MongoDB-compatible document database
|
||||||
\\
|
\\
|
||||||
\\usage: mongo-lite [options]
|
\\usage: multiforadb [options]
|
||||||
\\ --port <n> listen port (default 27017)
|
\\ --port <n> listen port (default 27017)
|
||||||
\\ --bind <ip> bind address (default 127.0.0.1)
|
\\ --bind <ip> bind address (default 127.0.0.1)
|
||||||
\\ --db <path> database file (default mongo-lite.log)
|
\\ --db <path> database file (default multiforadb.log)
|
||||||
\\ --ttl-sweep-secs <n>
|
\\ --ttl-sweep-secs <n>
|
||||||
\\ seconds between TTL index sweeps (default 60, 0 disables)
|
\\ seconds between TTL index sweeps (default 60, 0 disables)
|
||||||
\\ --compact-threshold <bytes>
|
\\ --compact-threshold <bytes>
|
||||||
@@ -47,7 +47,7 @@ fn parse_size_suffix(v: []const u8) ?u64 {
|
|||||||
pub fn main(init: std.process.Init) !void {
|
pub fn main(init: std.process.Init) !void {
|
||||||
var port: u16 = 27017;
|
var port: u16 = 27017;
|
||||||
var bind_ip: []const u8 = "127.0.0.1";
|
var bind_ip: []const u8 = "127.0.0.1";
|
||||||
var db_path: []const u8 = "mongo-lite.log";
|
var db_path: []const u8 = "multiforadb.log";
|
||||||
var ttl_sweep_secs: i64 = 60;
|
var ttl_sweep_secs: i64 = 60;
|
||||||
var compact_threshold: u64 = 16 * 1024 * 1024;
|
var compact_threshold: u64 = 16 * 1024 * 1024;
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
if (std.mem.eql(u8, arg, "--port")) {
|
if (std.mem.eql(u8, arg, "--port")) {
|
||||||
const v = it.next() orelse return error.MissingValue;
|
const v = it.next() orelse return error.MissingValue;
|
||||||
port = std.fmt.parseInt(u16, v, 10) catch {
|
port = std.fmt.parseInt(u16, v, 10) catch {
|
||||||
std.debug.print("mongo-lite: invalid port '{s}'\n", .{v});
|
std.debug.print("multiforadb: invalid port '{s}'\n", .{v});
|
||||||
return error.InvalidPort;
|
return error.InvalidPort;
|
||||||
};
|
};
|
||||||
} else if (std.mem.eql(u8, arg, "--bind")) {
|
} else if (std.mem.eql(u8, arg, "--bind")) {
|
||||||
@@ -72,17 +72,17 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
// only thing parseInt would otherwise let through.
|
// only thing parseInt would otherwise let through.
|
||||||
ttl_sweep_secs = std.fmt.parseInt(i64, v, 10) catch -1;
|
ttl_sweep_secs = std.fmt.parseInt(i64, v, 10) catch -1;
|
||||||
if (ttl_sweep_secs < 0) {
|
if (ttl_sweep_secs < 0) {
|
||||||
std.debug.print("mongo-lite: invalid ttl sweep interval '{s}'\n", .{v});
|
std.debug.print("multiforadb: invalid ttl sweep interval '{s}'\n", .{v});
|
||||||
return error.InvalidTtlSweepSecs;
|
return error.InvalidTtlSweepSecs;
|
||||||
}
|
}
|
||||||
} else if (std.mem.eql(u8, arg, "--compact-threshold")) {
|
} else if (std.mem.eql(u8, arg, "--compact-threshold")) {
|
||||||
const v = it.next() orelse return error.MissingValue;
|
const v = it.next() orelse return error.MissingValue;
|
||||||
const parsed = parse_size_suffix(v) orelse {
|
const parsed = parse_size_suffix(v) orelse {
|
||||||
std.debug.print("mongo-lite: invalid compact threshold '{s}'\n", .{v});
|
std.debug.print("multiforadb: invalid compact threshold '{s}'\n", .{v});
|
||||||
return error.InvalidCompactThreshold;
|
return error.InvalidCompactThreshold;
|
||||||
};
|
};
|
||||||
if (parsed < 1024 * 1024) {
|
if (parsed < 1024 * 1024) {
|
||||||
std.debug.print("mongo-lite: compact threshold must be at least 1m\n", .{});
|
std.debug.print("multiforadb: compact threshold must be at least 1m\n", .{});
|
||||||
return error.InvalidCompactThreshold;
|
return error.InvalidCompactThreshold;
|
||||||
}
|
}
|
||||||
compact_threshold = parsed;
|
compact_threshold = parsed;
|
||||||
@@ -90,7 +90,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
try std.Io.File.writeStreamingAll(.stdout(), init.io, usage);
|
try std.Io.File.writeStreamingAll(.stdout(), init.io, usage);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
std.debug.print("mongo-lite: unknown option '{s}'\n{s}", .{ arg, usage });
|
std.debug.print("multiforadb: unknown option '{s}'\n{s}", .{ arg, usage });
|
||||||
return error.UnknownOption;
|
return error.UnknownOption;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ pub fn main(init: std.process.Init) !void {
|
|||||||
var engine = try mongo.db.Engine.open(init.gpa, init.io, db_path);
|
var engine = try mongo.db.Engine.open(init.gpa, init.io, db_path);
|
||||||
defer engine.deinit();
|
defer engine.deinit();
|
||||||
engine.compact_threshold = compact_threshold;
|
engine.compact_threshold = compact_threshold;
|
||||||
std.debug.print("mongo-lite: opened database '{s}' (compact threshold {d})\n", .{ db_path, compact_threshold });
|
std.debug.print("multiforadb: opened database '{s}' (compact threshold {d})\n", .{ db_path, compact_threshold });
|
||||||
|
|
||||||
var server = mongo.server.Server{
|
var server = mongo.server.Server{
|
||||||
.gpa = init.gpa,
|
.gpa = init.gpa,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub const Server = struct {
|
|||||||
var listener = try addr.listen(io, .{ .reuse_address = true });
|
var listener = try addr.listen(io, .{ .reuse_address = true });
|
||||||
defer listener.deinit(io);
|
defer listener.deinit(io);
|
||||||
|
|
||||||
std.debug.print("mongo-lite: listening on {s}:{d}\n", .{ self.bind_ip, self.port });
|
std.debug.print("multiforadb: listening on {s}:{d}\n", .{ self.bind_ip, self.port });
|
||||||
|
|
||||||
var group: std.Io.Group = .init;
|
var group: std.Io.Group = .init;
|
||||||
defer group.cancel(io);
|
defer group.cancel(io);
|
||||||
@@ -48,7 +48,7 @@ pub const Server = struct {
|
|||||||
const stream = listener.accept(io) catch |err| switch (err) {
|
const stream = listener.accept(io) catch |err| switch (err) {
|
||||||
error.Canceled => return,
|
error.Canceled => return,
|
||||||
else => {
|
else => {
|
||||||
std.debug.print("mongo-lite: accept error: {s}\n", .{@errorName(err)});
|
std.debug.print("multiforadb: accept error: {s}\n", .{@errorName(err)});
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -70,12 +70,12 @@ fn ttl_monitor(io: std.Io, server: *Server) error{Canceled}!void {
|
|||||||
try std.Io.sleep(io, interval, .awake);
|
try std.Io.sleep(io, interval, .awake);
|
||||||
const now_ms = std.Io.Timestamp.now(io, .real).toMilliseconds();
|
const now_ms = std.Io.Timestamp.now(io, .real).toMilliseconds();
|
||||||
_ = server.engine.ttl_sweep(now_ms) catch |err| {
|
_ = server.engine.ttl_sweep(now_ms) catch |err| {
|
||||||
std.debug.print("mongo-lite: TTL sweep failed: {s}\n", .{@errorName(err)});
|
std.debug.print("multiforadb: TTL sweep failed: {s}\n", .{@errorName(err)});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if (server.engine.take_compact()) {
|
if (server.engine.take_compact()) {
|
||||||
server.engine.compact() catch |err| {
|
server.engine.compact() catch |err| {
|
||||||
std.debug.print("mongo-lite: compaction failed: {s}\n", .{@errorName(err)});
|
std.debug.print("multiforadb: compaction failed: {s}\n", .{@errorName(err)});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ fn handle_connection_inner(io: std.Io, stream: std.Io.net.Stream, server: *Serve
|
|||||||
reader.interface.readSliceAll(&len_bytes) catch return; // clean client disconnect (EOF or RST)
|
reader.interface.readSliceAll(&len_bytes) catch return; // clean client disconnect (EOF or RST)
|
||||||
const total: u32 = std.mem.readInt(u32, &len_bytes, .little);
|
const total: u32 = std.mem.readInt(u32, &len_bytes, .little);
|
||||||
if (total < 16 or total > wire.max_message_size) {
|
if (total < 16 or total > wire.max_message_size) {
|
||||||
std.debug.print("mongo-lite: bad message length {d} on conn {d}\n", .{ total, connection_id });
|
std.debug.print("multiforadb: bad message length {d} on conn {d}\n", .{ total, connection_id });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,14 +129,14 @@ fn handle_connection_inner(io: std.Io, stream: std.Io.net.Stream, server: *Serve
|
|||||||
msg_buf.items.len = total;
|
msg_buf.items.len = total;
|
||||||
std.mem.writeInt(u32, msg_buf.items[0..4], total, .little);
|
std.mem.writeInt(u32, msg_buf.items[0..4], total, .little);
|
||||||
reader.interface.readSliceAll(msg_buf.items[4..]) catch |err| {
|
reader.interface.readSliceAll(msg_buf.items[4..]) catch |err| {
|
||||||
std.debug.print("mongo-lite: read error on conn {d}: {s} (body, len {d})\n", .{ connection_id, @errorName(err), total });
|
std.debug.print("multiforadb: read error on conn {d}: {s} (body, len {d})\n", .{ connection_id, @errorName(err), total });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
var msg = wire.Message.parse(server.gpa, msg_buf.items) catch |err| {
|
var msg = wire.Message.parse(server.gpa, msg_buf.items) catch |err| {
|
||||||
// Unparseable request: close the connection.
|
// Unparseable request: close the connection.
|
||||||
const op: i32 = if (msg_buf.items.len >= 16) std.mem.readInt(i32, msg_buf.items[12..16], .little) else 0;
|
const op: i32 = if (msg_buf.items.len >= 16) std.mem.readInt(i32, msg_buf.items[12..16], .little) else 0;
|
||||||
std.debug.print("mongo-lite: bad message on conn {d}: {s} (opCode {d})\n", .{ connection_id, @errorName(err), op });
|
std.debug.print("multiforadb: bad message on conn {d}: {s} (opCode {d})\n", .{ connection_id, @errorName(err), op });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer msg.deinit();
|
defer msg.deinit();
|
||||||
@@ -146,7 +146,7 @@ fn handle_connection_inner(io: std.Io, stream: std.Io.net.Stream, server: *Serve
|
|||||||
commands.dispatch(&ctx, &msg, &reply) catch |err| {
|
commands.dispatch(&ctx, &msg, &reply) catch |err| {
|
||||||
// Discard any partial reply (the client would read the first
|
// Discard any partial reply (the client would read the first
|
||||||
// ok field, which may already say 1) and send a clean error.
|
// ok field, which may already say 1) and send a clean error.
|
||||||
std.debug.print("mongo-lite: dispatch error on conn {d} cmd {s}: {s}\n", .{ connection_id, msg.command_name(), @errorName(err) });
|
std.debug.print("multiforadb: dispatch error on conn {d} cmd {s}: {s}\n", .{ connection_id, msg.command_name(), @errorName(err) });
|
||||||
reply.pairs.clearRetainingCapacity();
|
reply.pairs.clearRetainingCapacity();
|
||||||
reply.put_error(
|
reply.put_error(
|
||||||
@intFromEnum(commands.ErrorCode.internal_error),
|
@intFromEnum(commands.ErrorCode.internal_error),
|
||||||
@@ -161,16 +161,16 @@ fn handle_connection_inner(io: std.Io, stream: std.Io.net.Stream, server: *Serve
|
|||||||
else
|
else
|
||||||
reply.build(server.gpa, reply_request_id, msg.request_id, &out_buf);
|
reply.build(server.gpa, reply_request_id, msg.request_id, &out_buf);
|
||||||
built catch |err| {
|
built catch |err| {
|
||||||
std.debug.print("mongo-lite: reply build error on conn {d}: {s}\n", .{ connection_id, @errorName(err) });
|
std.debug.print("multiforadb: reply build error on conn {d}: {s}\n", .{ connection_id, @errorName(err) });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
reply_request_id +%= 1;
|
reply_request_id +%= 1;
|
||||||
writer.interface.writeAll(out_buf.items) catch |err| {
|
writer.interface.writeAll(out_buf.items) catch |err| {
|
||||||
std.debug.print("mongo-lite: write error on conn {d}: {s}\n", .{ connection_id, @errorName(err) });
|
std.debug.print("multiforadb: write error on conn {d}: {s}\n", .{ connection_id, @errorName(err) });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
writer.interface.flush() catch |err| {
|
writer.interface.flush() catch |err| {
|
||||||
std.debug.print("mongo-lite: flush error on conn {d}: {s}\n", .{ connection_id, @errorName(err) });
|
std.debug.print("multiforadb: flush error on conn {d}: {s}\n", .{ connection_id, @errorName(err) });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,14 +227,14 @@ pub const Log = struct {
|
|||||||
while (true) {
|
while (true) {
|
||||||
var hdr: [block_header_len]u8 = undefined;
|
var hdr: [block_header_len]u8 = undefined;
|
||||||
const n = self.file.readPositionalAll(self.io, &hdr, pos) catch |err| {
|
const n = self.file.readPositionalAll(self.io, &hdr, pos) catch |err| {
|
||||||
std.debug.print("mongo-lite: log read error at {d}: {s}\n", .{ pos, @errorName(err) });
|
std.debug.print("multiforadb: log read error at {d}: {s}\n", .{ pos, @errorName(err) });
|
||||||
return error.InvalidLog;
|
return error.InvalidLog;
|
||||||
};
|
};
|
||||||
if (n == 0) return; // clean end
|
if (n == 0) return; // clean end
|
||||||
if (n < block_header_len) return; // torn tail: partial block header
|
if (n < block_header_len) return; // torn tail: partial block header
|
||||||
const total: u32 = std.mem.readInt(u32, hdr[0..4], .little);
|
const total: u32 = std.mem.readInt(u32, hdr[0..4], .little);
|
||||||
if (total < block_header_len or total - block_header_len > max_block_payload) {
|
if (total < block_header_len or total - block_header_len > max_block_payload) {
|
||||||
std.debug.print("mongo-lite: corrupt block length {d} at {d}\n", .{ total, pos });
|
std.debug.print("multiforadb: corrupt block length {d} at {d}\n", .{ total, pos });
|
||||||
return; // torn tail: impossible length, nothing to validate
|
return; // torn tail: impossible length, nothing to validate
|
||||||
}
|
}
|
||||||
const payload_len: usize = total - block_header_len;
|
const payload_len: usize = total - block_header_len;
|
||||||
@@ -262,7 +262,7 @@ pub const Log = struct {
|
|||||||
codec_raw => try decomp.appendSlice(self.gpa, payload),
|
codec_raw => try decomp.appendSlice(self.gpa, payload),
|
||||||
codec_lz4 => try lz4_decompress(self.gpa, payload, &decomp),
|
codec_lz4 => try lz4_decompress(self.gpa, payload, &decomp),
|
||||||
else => {
|
else => {
|
||||||
std.debug.print("mongo-lite: unknown block codec {d} at {d}\n", .{ codec, pos });
|
std.debug.print("multiforadb: unknown block codec {d} at {d}\n", .{ codec, pos });
|
||||||
return error.InvalidLog;
|
return error.InvalidLog;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -284,12 +284,12 @@ pub const Log = struct {
|
|||||||
if (bytes.len < 4) return error.InvalidLog;
|
if (bytes.len < 4) return error.InvalidLog;
|
||||||
const total: u32 = std.mem.readInt(u32, bytes[0..4], .little);
|
const total: u32 = std.mem.readInt(u32, bytes[0..4], .little);
|
||||||
if (total < header_len) {
|
if (total < header_len) {
|
||||||
std.debug.print("mongo-lite: corrupt record length {d} at {d}\n", .{ total, pos });
|
std.debug.print("multiforadb: corrupt record length {d} at {d}\n", .{ total, pos });
|
||||||
return error.InvalidLog;
|
return error.InvalidLog;
|
||||||
}
|
}
|
||||||
const payload_len: usize = total - 4;
|
const payload_len: usize = total - 4;
|
||||||
if (payload_len > max_record_payload) {
|
if (payload_len > max_record_payload) {
|
||||||
std.debug.print("mongo-lite: record too large at {d}\n", .{pos});
|
std.debug.print("multiforadb: record too large at {d}\n", .{pos});
|
||||||
return error.InvalidLog;
|
return error.InvalidLog;
|
||||||
}
|
}
|
||||||
if (bytes.len < total) return error.InvalidLog; // record crosses the block end
|
if (bytes.len < total) return error.InvalidLog; // record crosses the block end
|
||||||
@@ -309,7 +309,7 @@ pub const Log = struct {
|
|||||||
const doc = try self.gpa.create(bson.Document);
|
const doc = try self.gpa.create(bson.Document);
|
||||||
doc.* = bson.Document.parse(self.gpa, doc_bytes) catch {
|
doc.* = bson.Document.parse(self.gpa, doc_bytes) catch {
|
||||||
self.gpa.destroy(doc);
|
self.gpa.destroy(doc);
|
||||||
std.debug.print("mongo-lite: unparseable doc in log at {d}\n", .{pos});
|
std.debug.print("multiforadb: unparseable doc in log at {d}\n", .{pos});
|
||||||
return error.InvalidLog;
|
return error.InvalidLog;
|
||||||
};
|
};
|
||||||
try callback(ctx, .{
|
try callback(ctx, .{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# End-to-end tests with the official MongoDB Node.js driver
|
# End-to-end tests with the official MongoDB Node.js driver
|
||||||
|
|
||||||
These exercise mongo-lite from a real driver over TCP: full CRUD, query
|
These exercise MultiforaDB from a real driver over TCP: full CRUD, query
|
||||||
operators, aggregation, error codes, concurrent clients, crash recovery, and
|
operators, aggregation, error codes, concurrent clients, crash recovery, and
|
||||||
the whole lifecycle including server restarts.
|
the whole lifecycle including server restarts.
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ Most suites expect a server running on port 27020:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
zig build
|
zig build
|
||||||
zig-out/bin/mongo-lite --port 27020 --db /tmp/ml-e2e.log --ttl-sweep-secs 1 &
|
zig-out/bin/multiforadb --port 27020 --db /tmp/mfdb-e2e.log --ttl-sweep-secs 1 &
|
||||||
|
|
||||||
node tests/e2e/e2e.js # CRUD + operators + aggregate + errors (29 checks)
|
node tests/e2e/e2e.js # CRUD + operators + aggregate + errors (29 checks)
|
||||||
node tests/e2e/e2e2.js concurrent # 8 clients: 4 writers + 4 readers (2 checks)
|
node tests/e2e/e2e2.js concurrent # 8 clients: 4 writers + 4 readers (2 checks)
|
||||||
@@ -43,7 +43,7 @@ E2E6_PORT=27300 node tests/e2e/e2e6.js # different port if 27220 is taken
|
|||||||
|
|
||||||
Rebuild with `zig build` after any change under `src/` before restarting the
|
Rebuild with `zig build` after any change under `src/` before restarting the
|
||||||
server: `zig build test` compiles the test binary only and leaves
|
server: `zig build test` compiles the test binary only and leaves
|
||||||
`zig-out/bin/mongo-lite` stale, so the suites keep running against the old
|
`zig-out/bin/multiforadb` stale, so the suites keep running against the old
|
||||||
rules and report failures that the source no longer explains.
|
rules and report failures that the source no longer explains.
|
||||||
|
|
||||||
`e2e2.js concurrent` is safe to repeat against a running server (it drops its
|
`e2e2.js concurrent` is safe to repeat against a running server (it drops its
|
||||||
@@ -92,9 +92,9 @@ Measured behavior (all documented in the top-level README):
|
|||||||
bash tests/e2e/compare-run.sh [size] [doc-size] # e.g. 1g 16k
|
bash tests/e2e/compare-run.sh [size] [doc-size] # e.g. 1g 16k
|
||||||
```
|
```
|
||||||
|
|
||||||
Starts mongod (`brew install mongodb-community`) on :27018 and mongo-lite
|
Starts mongod (`brew install mongodb-community`) on :27018 and MultiforaDB
|
||||||
on :27019, runs the same driver workload against each (durable writes:
|
on :27019, runs the same driver workload against each (durable writes:
|
||||||
mongo-lite fsyncs per command, mongod runs with `j: true`), measures kill -9
|
MultiforaDB fsyncs per command, mongod runs with `j: true`), measures kill -9
|
||||||
reopen for both, and prints a side-by-side table. `compare.js` alone runs
|
reopen for both, and prints a side-by-side table. `compare.js` alone runs
|
||||||
one side (see its `--help`-style header comment).
|
one side (see its `--help`-style header comment).
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ comparison (`concurrent.js`, N clients each doing sequential `insertOne`
|
|||||||
with `{w:1, j:true}` — the group-commit path under real contention), then
|
with `{w:1, j:true}` — the group-commit path under real contention), then
|
||||||
writes a machine-readable, versioned report to
|
writes a machine-readable, versioned report to
|
||||||
`tests/e2e/results/bench-<timestamp>.txt` and prints a diff of the
|
`tests/e2e/results/bench-<timestamp>.txt` and prints a diff of the
|
||||||
mongo-lite numbers against the previous run (`results/bench-latest.txt`).
|
MultiforaDB numbers against the previous run (`results/bench-latest.txt`).
|
||||||
The report has `[main]` / `[concurrency]` / `[meta]` sections with
|
The report has `[main]` / `[concurrency]` / `[meta]` sections with
|
||||||
`name<TAB>value` rows; `bench-run.sh 1g 16k` reproduces the phase8 gate
|
`name<TAB>value` rows; `bench-run.sh 1g 16k` reproduces the phase8 gate
|
||||||
(see `results/phase8.txt`).
|
(see `results/phase8.txt`).
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
# bash tests/e2e/bench-run.sh [size] [doc-size] [clients...]
|
# bash tests/e2e/bench-run.sh [size] [doc-size] [clients...]
|
||||||
# (defaults: 1g, 16k, "1 4 8 16 32"; clients apply to the concurrent phase)
|
# (defaults: 1g, 16k, "1 4 8 16 32"; clients apply to the concurrent phase)
|
||||||
#
|
#
|
||||||
# Runs the compare-run.sh main suite (mongo-lite vs mongod, same driver,
|
# Runs the compare-run.sh main suite (multiforadb vs mongod, same driver,
|
||||||
# durable writes) plus the concurrent durable-write comparison, then writes
|
# durable writes) plus the concurrent durable-write comparison, then writes
|
||||||
# a versioned machine-readable report to tests/e2e/results/ and prints a
|
# a versioned machine-readable report to tests/e2e/results/ and prints a
|
||||||
# diff of the mongo-lite numbers against the previous run.
|
# diff of the multiforadb numbers against the previous run.
|
||||||
set -u
|
set -u
|
||||||
cd "$(dirname "$0")/../.."
|
cd "$(dirname "$0")/../.."
|
||||||
SIZE="${1:-1g}"; DOC="${2:-16k}"; CLIENTS="${3:-1 4 8 16 32}"
|
SIZE="${1:-1g}"; DOC="${2:-16k}"; CLIENTS="${3:-1 4 8 16 32}"
|
||||||
@@ -35,8 +35,8 @@ echo "### concurrent durable insertOne (clients: $CLIENTS)" >&2
|
|||||||
rm -rf "$TMP/mongod" && mkdir -p "$TMP/mongod"
|
rm -rf "$TMP/mongod" && mkdir -p "$TMP/mongod"
|
||||||
mongod --dbpath "$TMP/mongod" --port 27018 --bind_ip 127.0.0.1 --quiet >"$TMP/md.out" 2>&1 &
|
mongod --dbpath "$TMP/mongod" --port 27018 --bind_ip 127.0.0.1 --quiet >"$TMP/md.out" 2>&1 &
|
||||||
MD_PID=$!
|
MD_PID=$!
|
||||||
./zig-out/bin/mongo-lite --port 27019 --db "$TMP/ml.log" --compact-threshold 1g >"$TMP/ml.out" 2>&1 &
|
./zig-out/bin/multiforadb --port 27019 --db "$TMP/mfdb.log" --compact-threshold 1g >"$TMP/mfdb.out" 2>&1 &
|
||||||
ML_PID=$!
|
MFDB_PID=$!
|
||||||
# Poll both servers with the real driver until they answer (fresh mongod
|
# Poll both servers with the real driver until they answer (fresh mongod
|
||||||
# dbpaths can take several seconds; a fixed sleep is flaky).
|
# dbpaths can take several seconds; a fixed sleep is flaky).
|
||||||
wait_ready() { # $1 = url
|
wait_ready() { # $1 = url
|
||||||
@@ -47,25 +47,25 @@ wait_ready() { # $1 = url
|
|||||||
done
|
done
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
wait_ready mongodb://127.0.0.1:27018 || { echo "mongod never became ready" >&2; kill -9 $MD_PID $ML_PID 2>/dev/null; exit 1; }
|
wait_ready mongodb://127.0.0.1:27018 || { echo "mongod never became ready" >&2; kill -9 $MD_PID $MFDB_PID 2>/dev/null; exit 1; }
|
||||||
wait_ready mongodb://127.0.0.1:27019 || { echo "mongo-lite never became ready" >&2; kill -9 $MD_PID $ML_PID 2>/dev/null; exit 1; }
|
wait_ready mongodb://127.0.0.1:27019 || { echo "multiforadb never became ready" >&2; kill -9 $MD_PID $MFDB_PID 2>/dev/null; exit 1; }
|
||||||
CC="$TMP/cc.txt"
|
CC="$TMP/cc.txt"
|
||||||
for C in $CLIENTS; do
|
for C in $CLIENTS; do
|
||||||
MD=$(node tests/e2e/concurrent.js --url mongodb://127.0.0.1:27018 --label mongodb --clients "$C" --per-client "$PER_CLIENT" 2>/dev/null | tail -1)
|
MD=$(node tests/e2e/concurrent.js --url mongodb://127.0.0.1:27018 --label mongodb --clients "$C" --per-client "$PER_CLIENT" 2>/dev/null | tail -1)
|
||||||
ML=$(node tests/e2e/concurrent.js --url mongodb://127.0.0.1:27019 --label mongo-lite --clients "$C" --per-client "$PER_CLIENT" 2>/dev/null | tail -1)
|
MFDB=$(node tests/e2e/concurrent.js --url mongodb://127.0.0.1:27019 --label multiforadb --clients "$C" --per-client "$PER_CLIENT" 2>/dev/null | tail -1)
|
||||||
ML_D=$(echo "$ML" | sed -E 's/.*\t([0-9.]+) docs\/s.*/\1/')
|
MFDB_D=$(echo "$MFDB" | sed -E 's/.*\t([0-9.]+) docs\/s.*/\1/')
|
||||||
MD_D=$(echo "$MD" | sed -E 's/.*\t([0-9.]+) docs\/s.*/\1/')
|
MD_D=$(echo "$MD" | sed -E 's/.*\t([0-9.]+) docs\/s.*/\1/')
|
||||||
if [ -z "$ML_D" ] || [ -z "$MD_D" ]; then
|
if [ -z "$MFDB_D" ] || [ -z "$MD_D" ]; then
|
||||||
echo "WARNING: concurrent run at $C clients produced no result (ml='$ML' md='$MD')" >&2
|
echo "WARNING: concurrent run at $C clients produced no result (mfdb='$MFDB' md='$MD')" >&2
|
||||||
fi
|
fi
|
||||||
RATIO=$(node -e "const m=Number('$ML_D'),d=Number('$MD_D');console.log(d>0?(m/d).toFixed(1)+'x':'—')")
|
RATIO=$(node -e "const m=Number('$MFDB_D'),d=Number('$MD_D');console.log(d>0?(m/d).toFixed(1)+'x':'—')")
|
||||||
echo "clients $C $ML_D $MD_D $RATIO" | tee -a "$CC"
|
echo "clients $C $MFDB_D $MD_D $RATIO" | tee -a "$CC"
|
||||||
done
|
done
|
||||||
kill -9 $MD_PID $ML_PID 2>/dev/null; wait 2>/dev/null
|
kill -9 $MD_PID $MFDB_PID 2>/dev/null; wait 2>/dev/null
|
||||||
|
|
||||||
# ---- assemble the report --------------------------------------------------
|
# ---- assemble the report --------------------------------------------------
|
||||||
{
|
{
|
||||||
echo "# mongo-lite vs MongoDB benchmark"
|
echo "# multiforadb vs MongoDB benchmark"
|
||||||
echo "# date: $(date -u +%Y-%m-%dT%H:%M:%SZ) git: $REV$DIRTY"
|
echo "# date: $(date -u +%Y-%m-%dT%H:%M:%SZ) git: $REV$DIRTY"
|
||||||
echo "# args: size=$SIZE doc-size=$DOC wc=j clients=$CLIENTS per-client=$PER_CLIENT"
|
echo "# args: size=$SIZE doc-size=$DOC wc=j clients=$CLIENTS per-client=$PER_CLIENT"
|
||||||
echo "# reproduce: bash tests/e2e/bench-run.sh $SIZE $DOC \"$CLIENTS\""
|
echo "# reproduce: bash tests/e2e/bench-run.sh $SIZE $DOC \"$CLIENTS\""
|
||||||
@@ -81,7 +81,7 @@ kill -9 $MD_PID $ML_PID 2>/dev/null; wait 2>/dev/null
|
|||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
};
|
};
|
||||||
const a = read("/tmp/mongo-cmp/ml-report.txt");
|
const a = read("/tmp/mongo-cmp/mfdb-report.txt");
|
||||||
const b = read("/tmp/mongo-cmp/mongo-report.txt");
|
const b = read("/tmp/mongo-cmp/mongo-report.txt");
|
||||||
const keys = ["insertOne (sequential) ×200","bulk insert throughput","docs loaded","createIndex({k: 1})",
|
const keys = ["insertOne (sequential) ×200","bulk insert throughput","docs loaded","createIndex({k: 1})",
|
||||||
"countDocuments({})","findOne({_id: <ObjectId>})","findOne({k: 500}) (indexed)","find({p: {$gte,$lt}}).count() (scan)",
|
"countDocuments({})","findOne({_id: <ObjectId>})","findOne({k: 500}) (indexed)","find({p: {$gte,$lt}}).count() (scan)",
|
||||||
@@ -104,7 +104,7 @@ kill -9 $MD_PID $ML_PID 2>/dev/null; wait 2>/dev/null
|
|||||||
# ---- diff against the previous run ---------------------------------------
|
# ---- diff against the previous run ---------------------------------------
|
||||||
echo "report: $REPORT"
|
echo "report: $REPORT"
|
||||||
if [ -f "$LATEST" ]; then
|
if [ -f "$LATEST" ]; then
|
||||||
echo; echo "### mongo-lite numbers vs previous run ($(head -2 "$LATEST" | tail -1 | sed 's/# //'))"
|
echo; echo "### multiforadb numbers vs previous run ($(head -2 "$LATEST" | tail -1 | sed 's/# //'))"
|
||||||
node -e '
|
node -e '
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const old = fs.readFileSync(process.argv[1], "utf8");
|
const old = fs.readFileSync(process.argv[1], "utf8");
|
||||||
@@ -136,9 +136,9 @@ if [ -f "$LATEST" ]; then
|
|||||||
const d = isFinite(num(ov)) && isFinite(num(v)) && num(ov) > 0 ? ((num(v) - num(ov)) / num(ov) * 100).toFixed(0) + "%" : "";
|
const d = isFinite(num(ov)) && isFinite(num(v)) && num(ov) > 0 ? ((num(v) - num(ov)) / num(ov) * 100).toFixed(0) + "%" : "";
|
||||||
console.log(k.padEnd(42) + fmt(ov) + fmt(v) + d);
|
console.log(k.padEnd(42) + fmt(ov) + fmt(v) + d);
|
||||||
}
|
}
|
||||||
// concurrency (mongo-lite docs/s per client count)
|
// concurrency (multiforadb docs/s per client count)
|
||||||
const oc = section(old, "concurrency"), nc = section(neu, "concurrency");
|
const oc = section(old, "concurrency"), nc = section(neu, "concurrency");
|
||||||
console.log("\nconcurrency — mongo-lite docs/s:");
|
console.log("\nconcurrency — multiforadb docs/s:");
|
||||||
for (const [k, v] of nc) {
|
for (const [k, v] of nc) {
|
||||||
const ov = oc.get(k);
|
const ov = oc.get(k);
|
||||||
const d = ov && Number(ov) > 0 ? ((Number(v) - Number(ov)) / Number(ov) * 100).toFixed(0) + "%" : "";
|
const d = ov && Number(ov) > 0 ? ((Number(v) - Number(ov)) / Number(ov) * 100).toFixed(0) + "%" : "";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Big-collection harness: how mongo-lite behaves with multi-GB collections.
|
// Big-collection harness: how multiforadb behaves with multi-GB collections.
|
||||||
//
|
//
|
||||||
// Spawns its own server, bulk-inserts up to ~5 GB of documents, measures
|
// Spawns its own server, bulk-inserts up to ~5 GB of documents, measures
|
||||||
// insert throughput, log/compaction behavior and server RSS, benchmarks
|
// insert throughput, log/compaction behavior and server RSS, benchmarks
|
||||||
@@ -22,14 +22,14 @@
|
|||||||
// --keep keep the db file after the run
|
// --keep keep the db file after the run
|
||||||
// --quick tiny run (256m, 16k docs)
|
// --quick tiny run (256m, 16k docs)
|
||||||
//
|
//
|
||||||
// Env: ML_BIN server binary (default ../../zig-out/bin/mongo-lite)
|
// Env: MFDB_BIN server binary (default ../../zig-out/bin/multiforadb)
|
||||||
// BIG_DB db file path (default .zig-cache/big.log)
|
// BIG_DB db file path (default .zig-cache/big.log)
|
||||||
const { MongoClient } = require('mongodb');
|
const { MongoClient } = require('mongodb');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const BIN = process.env.ML_BIN || path.resolve(__dirname, '../../zig-out/bin/mongo-lite');
|
const BIN = process.env.MFDB_BIN || path.resolve(__dirname, '../../zig-out/bin/multiforadb');
|
||||||
const PORT = Number(process.env.BIG_PORT || 27221);
|
const PORT = Number(process.env.BIG_PORT || 27221);
|
||||||
const DBFILE = process.env.BIG_DB || path.resolve(__dirname, '../../.zig-cache/big.log');
|
const DBFILE = process.env.BIG_DB || path.resolve(__dirname, '../../.zig-cache/big.log');
|
||||||
const URL = `mongodb://127.0.0.1:${PORT}`;
|
const URL = `mongodb://127.0.0.1:${PORT}`;
|
||||||
@@ -137,7 +137,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
fs.rmSync(DBFILE, { force: true });
|
fs.rmSync(DBFILE, { force: true });
|
||||||
const fmt = (n) => (n >= 1e9 ? (n / 1e9).toFixed(2) + ' GB' : n >= 1e6 ? (n / 1e6).toFixed(1) + ' MB' : n >= 1e3 ? (n / 1e3).toFixed(1) + ' KB' : n + ' B');
|
const fmt = (n) => (n >= 1e9 ? (n / 1e9).toFixed(2) + ' GB' : n >= 1e6 ? (n / 1e6).toFixed(1) + ' MB' : n >= 1e3 ? (n / 1e3).toFixed(1) + ' KB' : n + ' B');
|
||||||
console.log(`mongo-lite big-collection harness`);
|
console.log(`multiforadb big-collection harness`);
|
||||||
console.log(` size ${fmt(SIZE)} · doc ~${fmt(DOC_SIZE)} · batch ${opt.batch} · index ${opt.index || 'none'} · ids ${opt.oid ? 'ObjectId' : 'int'} · compact-threshold ${opt.compactThreshold || '16m'} · db ${DBFILE}`);
|
console.log(` size ${fmt(SIZE)} · doc ~${fmt(DOC_SIZE)} · batch ${opt.batch} · index ${opt.index || 'none'} · ids ${opt.oid ? 'ObjectId' : 'int'} · compact-threshold ${opt.compactThreshold || '16m'} · db ${DBFILE}`);
|
||||||
|
|
||||||
console.log('\n== server start (fresh log) ==');
|
console.log('\n== server start (fresh log) ==');
|
||||||
@@ -331,7 +331,7 @@ async function main() {
|
|||||||
await stopServer('SIGKILL');
|
await stopServer('SIGKILL');
|
||||||
|
|
||||||
console.log('\n== summary ==');
|
console.log('\n== summary ==');
|
||||||
console.log(` mongo-lite handles a ${fmt(bytes)} collection fully in RAM (RSS ${peakRss} MB)`);
|
console.log(` multiforadb handles a ${fmt(bytes)} collection fully in RAM (RSS ${peakRss} MB)`);
|
||||||
console.log(` insert: ${(bytes / 1e6 / (insertMs / 1000)).toFixed(1)} MB/s — fsync per write is by design (crash safety)`);
|
console.log(` insert: ${(bytes / 1e6 / (insertMs / 1000)).toFixed(1)} MB/s — fsync per write is by design (crash safety)`);
|
||||||
if (compactions > 0) {
|
if (compactions > 0) {
|
||||||
console.log(` ${compactions} compaction rewrites observed: every 16MB of writes rewrites the whole log — for multi-GB loads the cumulative rewrite traffic dominates`);
|
console.log(` ${compactions} compaction rewrites observed: every 16MB of writes rewrites the whole log — for multi-GB loads the cumulative rewrite traffic dominates`);
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Compare mongo-lite against a real MongoDB with the same workload, same driver.
|
# Compare multiforadb against a real MongoDB with the same workload, same driver.
|
||||||
#
|
#
|
||||||
# bash tests/e2e/compare-run.sh [size] [doc-size]
|
# bash tests/e2e/compare-run.sh [size] [doc-size]
|
||||||
# (defaults: 1g, 16k)
|
# (defaults: 1g, 16k)
|
||||||
#
|
#
|
||||||
# Starts mongod on :27018 and mongo-lite on :27019, runs compare.js against
|
# Starts mongod on :27018 and multiforadb on :27019, runs compare.js against
|
||||||
# each (durable writes: mongo-lite fsyncs per doc, mongod ack'd with j:true),
|
# each (durable writes: multiforadb fsyncs per doc, mongod ack'd with j:true),
|
||||||
# measures kill -9 reopen time for both, and prints a side-by-side table.
|
# measures kill -9 reopen time for both, and prints a side-by-side table.
|
||||||
set -u
|
set -u
|
||||||
cd "$(dirname "$0")/../.."
|
cd "$(dirname "$0")/../.."
|
||||||
SIZE="${1:-1g}"
|
SIZE="${1:-1g}"
|
||||||
DOC="${2:-16k}"
|
DOC="${2:-16k}"
|
||||||
echo "comparing mongo-lite vs mongodb — dataset ${SIZE}, docs ~${DOC}"
|
echo "comparing multiforadb vs mongodb — dataset ${SIZE}, docs ~${DOC}"
|
||||||
|
|
||||||
CMPDIR=/tmp/mongo-cmp
|
CMPDIR=/tmp/mongo-cmp
|
||||||
mkdir -p "$CMPDIR/mongod"
|
mkdir -p "$CMPDIR/mongod"
|
||||||
ML_LOG="$CMPDIR/ml.log"
|
MFDB_LOG="$CMPDIR/mfdb.log"
|
||||||
ML_OUT="$CMPDIR/ml-srv.out"
|
MFDB_OUT="$CMPDIR/mfdb-srv.out"
|
||||||
MD_OUT="$CMPDIR/md-srv.out"
|
MD_OUT="$CMPDIR/md-srv.out"
|
||||||
ML_PORT=27019
|
MFDB_PORT=27019
|
||||||
MD_PORT=27018
|
MD_PORT=27018
|
||||||
rm -f "$ML_LOG"
|
rm -f "$MFDB_LOG"
|
||||||
rm -rf "$CMPDIR/mongod" && mkdir -p "$CMPDIR/mongod"
|
rm -rf "$CMPDIR/mongod" && mkdir -p "$CMPDIR/mongod"
|
||||||
|
|
||||||
# ---- MongoDB -------------------------------------------------------------
|
# ---- MongoDB -------------------------------------------------------------
|
||||||
@@ -60,26 +60,26 @@ setTimeout(poll, 300);
|
|||||||
')
|
')
|
||||||
kill -9 $MD_PID 2>/dev/null
|
kill -9 $MD_PID 2>/dev/null
|
||||||
|
|
||||||
# ---- mongo-lite ----------------------------------------------------------
|
# ---- multiforadb ----------------------------------------------------------
|
||||||
echo; echo "### mongo-lite (recommended config: --compact-threshold 1g)"
|
echo; echo "### multiforadb (recommended config: --compact-threshold 1g)"
|
||||||
# Debug is ~10-200x slower (see the README's perf section) — the comparison
|
# Debug is ~10-200x slower (see the README's perf section) — the comparison
|
||||||
# must use the optimized build.
|
# must use the optimized build.
|
||||||
zig build -Doptimize=ReleaseFast 2>&1 | grep -c "^error" | grep -q "^0" || { echo "build failed"; exit 1; }
|
zig build -Doptimize=ReleaseFast 2>&1 | grep -c "^error" | grep -q "^0" || { echo "build failed"; exit 1; }
|
||||||
./zig-out/bin/mongo-lite --port $ML_PORT --db "$ML_LOG" --compact-threshold 1g >"$ML_OUT" 2>&1 &
|
./zig-out/bin/multiforadb --port $MFDB_PORT --db "$MFDB_LOG" --compact-threshold 1g >"$MFDB_OUT" 2>&1 &
|
||||||
ML_PID=$!
|
MFDB_PID=$!
|
||||||
wait_ready "mongodb://127.0.0.1:$ML_PORT" || { echo "mongo-lite never became ready" >&2; exit 1; }
|
wait_ready "mongodb://127.0.0.1:$MFDB_PORT" || { echo "multiforadb never became ready" >&2; exit 1; }
|
||||||
node tests/e2e/compare.js --url "mongodb://127.0.0.1:$ML_PORT" --label mongo-lite --size "$SIZE" --doc-size "$DOC" \
|
node tests/e2e/compare.js --url "mongodb://127.0.0.1:$MFDB_PORT" --label multiforadb --size "$SIZE" --doc-size "$DOC" \
|
||||||
> "$CMPDIR/ml-report.txt" 2>&1 || { echo "mongo-lite bench failed:"; tail -5 "$CMPDIR/ml-report.txt"; }
|
> "$CMPDIR/mfdb-report.txt" 2>&1 || { echo "multiforadb bench failed:"; tail -5 "$CMPDIR/mfdb-report.txt"; }
|
||||||
ML_RSS=$(ps -o rss= -p $ML_PID | awk '{printf "%.0f", $1/1024}')
|
MFDB_RSS=$(ps -o rss= -p $MFDB_PID | awk '{printf "%.0f", $1/1024}')
|
||||||
ML_DISK=$(du -sm "$ML_LOG" | awk '{print $1}')
|
MFDB_DISK=$(du -sm "$MFDB_LOG" | awk '{print $1}')
|
||||||
|
|
||||||
echo; echo "### mongo-lite kill -9 + reopen (replay)"
|
echo; echo "### multiforadb kill -9 + reopen (replay)"
|
||||||
kill -9 $ML_PID; wait $ML_PID 2>/dev/null
|
kill -9 $MFDB_PID; wait $MFDB_PID 2>/dev/null
|
||||||
ML_REOPEN=$(cd tests/e2e && node -e '
|
MFDB_REOPEN=$(cd tests/e2e && node -e '
|
||||||
const { spawn } = require("child_process");
|
const { spawn } = require("child_process");
|
||||||
const { MongoClient } = require("mongodb");
|
const { MongoClient } = require("mongodb");
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
const p = spawn("/Users/shkmv/workspace/sandbox/mongo-lite/zig-out/bin/mongo-lite", ["--port","27019","--db","/tmp/mongo-cmp/ml.log","--compact-threshold","1g"], {stdio:"ignore"});
|
const p = spawn("../../zig-out/bin/multiforadb", ["--port","27019","--db","/tmp/mongo-cmp/mfdb.log","--compact-threshold","1g"], {stdio:"ignore"});
|
||||||
const poll = async () => {
|
const poll = async () => {
|
||||||
const c = new MongoClient("mongodb://127.0.0.1:27019", {serverSelectionTimeoutMS: 800});
|
const c = new MongoClient("mongodb://127.0.0.1:27019", {serverSelectionTimeoutMS: 800});
|
||||||
try { await c.connect(); await c.db("admin").command({ping:1}); await c.close(); p.kill("SIGKILL"); console.log(((Date.now()-t0)/1000).toFixed(1)); }
|
try { await c.connect(); await c.db("admin").command({ping:1}); await c.close(); p.kill("SIGKILL"); console.log(((Date.now()-t0)/1000).toFixed(1)); }
|
||||||
@@ -87,12 +87,12 @@ const poll = async () => {
|
|||||||
};
|
};
|
||||||
setTimeout(poll, 300);
|
setTimeout(poll, 300);
|
||||||
')
|
')
|
||||||
kill -9 $ML_PID 2>/dev/null
|
kill -9 $MFDB_PID 2>/dev/null
|
||||||
|
|
||||||
# ---- side by side ----------------------------------------------------------
|
# ---- side by side ----------------------------------------------------------
|
||||||
echo; echo "### side by side — ${SIZE} dataset, ~${DOC} docs"
|
echo; echo "### side by side — ${SIZE} dataset, ~${DOC} docs"
|
||||||
cat > "$CMPDIR/meta.json" <<EOF
|
cat > "$CMPDIR/meta.json" <<EOF
|
||||||
{"ml_rss_mb": "$ML_RSS", "md_rss_mb": "$MD_RSS", "ml_reopen": "${ML_REOPEN}s", "md_reopen": "${MD_REOPEN}s", "ml_disk_mb": "${ML_DISK}MB", "md_disk_mb": "${MD_DISK}MB"}
|
{"mfdb_rss_mb": "$MFDB_RSS", "md_rss_mb": "$MD_RSS", "mfdb_reopen": "${MFDB_REOPEN}s", "md_reopen": "${MD_REOPEN}s", "mfdb_disk_mb": "${MFDB_DISK}MB", "md_disk_mb": "${MD_DISK}MB"}
|
||||||
EOF
|
EOF
|
||||||
node -e '
|
node -e '
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -105,7 +105,7 @@ const read = (p) => {
|
|||||||
}
|
}
|
||||||
return m;
|
return m;
|
||||||
};
|
};
|
||||||
const a = read("/tmp/mongo-cmp/ml-report.txt");
|
const a = read("/tmp/mongo-cmp/mfdb-report.txt");
|
||||||
const b = read("/tmp/mongo-cmp/mongo-report.txt");
|
const b = read("/tmp/mongo-cmp/mongo-report.txt");
|
||||||
const meta = JSON.parse(fs.readFileSync("/tmp/mongo-cmp/meta.json", "utf8"));
|
const meta = JSON.parse(fs.readFileSync("/tmp/mongo-cmp/meta.json", "utf8"));
|
||||||
const keys = ["insertOne (sequential) ×200","bulk insert throughput","docs loaded","createIndex({k: 1})",
|
const keys = ["insertOne (sequential) ×200","bulk insert throughput","docs loaded","createIndex({k: 1})",
|
||||||
@@ -113,17 +113,17 @@ const keys = ["insertOne (sequential) ×200","bulk insert throughput","docs load
|
|||||||
"find({}).sort({_id:-1}).limit(20)","find({}, {proj}).limit(1000)","aggregate $group by k",
|
"find({}).sort({_id:-1}).limit(20)","find({}, {proj}).limit(1000)","aggregate $group by k",
|
||||||
"updateOne({_id}) ×50","updateMany({k: 7}, {$inc})","deleteOne({_id}) + insertOne","node client RSS"];
|
"updateOne({_id}) ×50","updateMany({k: 7}, {$inc})","deleteOne({_id}) + insertOne","node client RSS"];
|
||||||
const col = (v) => String(v).padEnd(22);
|
const col = (v) => String(v).padEnd(22);
|
||||||
console.log(`${"benchmark".padEnd(42)} ${col("mongo-lite")} ${col("mongodb")} ratio`);
|
console.log(`${"benchmark".padEnd(42)} ${col("multiforadb")} ${col("mongodb")} ratio`);
|
||||||
for (const k of keys) {
|
for (const k of keys) {
|
||||||
const av = a[k] || "—", bv = b[k] || "—";
|
const av = a[k] || "—", bv = b[k] || "—";
|
||||||
const ar = parseFloat(av), br = parseFloat(bv);
|
const ar = parseFloat(av), br = parseFloat(bv);
|
||||||
const ratio = isFinite(ar) && isFinite(br) && ar > 0 && br > 0 ? (ar / br).toFixed(1) + "x" : "";
|
const ratio = isFinite(ar) && isFinite(br) && ar > 0 && br > 0 ? (ar / br).toFixed(1) + "x" : "";
|
||||||
console.log(`${k.padEnd(42)} ${col(av)} ${col(bv)} ${ratio}`);
|
console.log(`${k.padEnd(42)} ${col(av)} ${col(bv)} ${ratio}`);
|
||||||
}
|
}
|
||||||
console.log(`${`server RSS`.padEnd(42)} ${col(meta.ml_rss_mb + " MB")} ${col(meta.md_rss_mb + " MB")}`);
|
console.log(`${`server RSS`.padEnd(42)} ${col(meta.mfdb_rss_mb + " MB")} ${col(meta.md_rss_mb + " MB")}`);
|
||||||
console.log(`${`kill -9 reopen`.padEnd(42)} ${col(meta.ml_reopen)} ${col(meta.md_reopen)}`);
|
console.log(`${`kill -9 reopen`.padEnd(42)} ${col(meta.mfdb_reopen)} ${col(meta.md_reopen)}`);
|
||||||
console.log(`${`db on disk`.padEnd(42)} ${col(meta.ml_disk_mb)} ${col(meta.md_disk_mb)}`);
|
console.log(`${`db on disk`.padEnd(42)} ${col(meta.mfdb_disk_mb)} ${col(meta.md_disk_mb)}`);
|
||||||
'
|
'
|
||||||
|
|
||||||
pkill -9 -f "mongo-lite --port $ML_PORT" 2>/dev/null
|
pkill -9 -f "multiforadb --port $MFDB_PORT" 2>/dev/null
|
||||||
echo; echo "done — reports: $CMPDIR/ml-report.txt, $CMPDIR/mongo-report.txt"
|
echo; echo "done — reports: $CMPDIR/mfdb-report.txt, $CMPDIR/mongo-report.txt"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Benchmark: the same workload through the official driver against mongo-lite
|
// Benchmark: the same workload through the official driver against multiforadb
|
||||||
// and a real MongoDB. Run once per server URL, then diff the reports.
|
// and a real MongoDB. Run once per server URL, then diff the reports.
|
||||||
//
|
//
|
||||||
// node tests/e2e/compare.js --url mongodb://127.0.0.1:27018 --label mongodb
|
// node tests/e2e/compare.js --url mongodb://127.0.0.1:27018 --label mongodb
|
||||||
// node tests/e2e/compare.js --url mongodb://127.0.0.1:27019 --label mongo-lite
|
// node tests/e2e/compare.js --url mongodb://127.0.0.1:27019 --label multiforadb
|
||||||
//
|
//
|
||||||
// Options:
|
// Options:
|
||||||
// --url <u> server URL (required)
|
// --url <u> server URL (required)
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
// --batch <n> docs per insertMany (default 500)
|
// --batch <n> docs per insertMany (default 500)
|
||||||
// --index <field> field to index before the op benchmarks (default k)
|
// --index <field> field to index before the op benchmarks (default k)
|
||||||
// --wc <j|none> write concern: 'j' = {w:1, j:true} durable ack on every
|
// --wc <j|none> write concern: 'j' = {w:1, j:true} durable ack on every
|
||||||
// write (fair vs mongo-lite's fsync-per-write); 'none' =
|
// write (fair vs multiforadb's fsync-per-write); 'none' =
|
||||||
// driver default (default j)
|
// driver default (default j)
|
||||||
//
|
//
|
||||||
// Every benchmark is awaited (no fire-and-forget), which is exactly how the
|
// Every benchmark is awaited (no fire-and-forget), which is exactly how the
|
||||||
// big.js harness measures mongo-lite, so the numbers are directly comparable.
|
// big.js harness measures multiforadb, so the numbers are directly comparable.
|
||||||
const { MongoClient, ObjectId } = require('mongodb');
|
const { MongoClient, ObjectId } = require('mongodb');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
@@ -118,7 +118,7 @@ async function main() {
|
|||||||
try { collInfo = await db.command({ collStats: 'items' }); } catch {}
|
try { collInfo = await db.command({ collStats: 'items' }); } catch {}
|
||||||
if (collInfo) row('server-side data size', fmt(collInfo.size ?? 0), `storage ${fmt(collInfo.storageSize ?? 0)}`);
|
if (collInfo) row('server-side data size', fmt(collInfo.size ?? 0), `storage ${fmt(collInfo.storageSize ?? 0)}`);
|
||||||
|
|
||||||
// ---- secondary index (mongo-lite: planner uses it; mongodb: normal) ------
|
// ---- secondary index (multiforadb: planner uses it; mongodb: normal) ------
|
||||||
if (opt.index) {
|
if (opt.index) {
|
||||||
await bench(`createIndex({${opt.index}: 1})`, async () => { await coll.createIndex({ [opt.index]: 1 }); });
|
await bench(`createIndex({${opt.index}: 1})`, async () => { await coll.createIndex({ [opt.index]: 1 }); });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// insertOne ({w:1, j:true} by default) into their own collection, reporting
|
// insertOne ({w:1, j:true} by default) into their own collection, reporting
|
||||||
// aggregate docs/s. Exercises the group-commit path under real contention.
|
// aggregate docs/s. Exercises the group-commit path under real contention.
|
||||||
//
|
//
|
||||||
// node tests/e2e/concurrent.js --url mongodb://127.0.0.1:27019 --label mongo-lite
|
// node tests/e2e/concurrent.js --url mongodb://127.0.0.1:27019 --label multiforadb
|
||||||
// [--clients 8] [--per-client 2000] [--wc j|none]
|
// [--clients 8] [--per-client 2000] [--wc j|none]
|
||||||
//
|
//
|
||||||
// Output (stdout, tab-separated, one line):
|
// Output (stdout, tab-separated, one line):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// End-to-end test: official MongoDB Node.js driver against mongo-lite.
|
// End-to-end test: official MongoDB Node.js driver against multiforadb.
|
||||||
const { MongoClient, ObjectId } = require('mongodb');
|
const { MongoClient, ObjectId } = require('mongodb');
|
||||||
|
|
||||||
const URL = 'mongodb://127.0.0.1:27020';
|
const URL = 'mongodb://127.0.0.1:27020';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// server must reject with CannotCreateIndex (67).
|
// server must reject with CannotCreateIndex (67).
|
||||||
//
|
//
|
||||||
// The server must run with a short sweep interval, e.g.
|
// The server must run with a short sweep interval, e.g.
|
||||||
// zig-out/bin/mongo-lite --port 27020 --db /tmp/ml-e2e.log --ttl-sweep-secs 1
|
// zig-out/bin/multiforadb --port 27020 --db /tmp/mfdb-e2e.log --ttl-sweep-secs 1
|
||||||
const { MongoClient } = require('mongodb');
|
const { MongoClient } = require('mongodb');
|
||||||
|
|
||||||
const URL = 'mongodb://127.0.0.1:27020';
|
const URL = 'mongodb://127.0.0.1:27020';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// E2E part 6: the full lifecycle, self-contained.
|
// E2E part 6: the full lifecycle, self-contained.
|
||||||
//
|
//
|
||||||
// Spawns its own mongo-lite server on a fresh log file and drives the whole
|
// Spawns its own multiforadb server on a fresh log file and drives the whole
|
||||||
// feature surface through the official driver: CRUD + query operators +
|
// feature surface through the official driver: CRUD + query operators +
|
||||||
// aggregation + error codes + secondary indexes + TTL expiry + admin
|
// aggregation + error codes + secondary indexes + TTL expiry + admin
|
||||||
// commands, then restarts the server twice — once gracefully, once with
|
// commands, then restarts the server twice — once gracefully, once with
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// node tests/e2e/e2e6.js
|
// node tests/e2e/e2e6.js
|
||||||
//
|
//
|
||||||
// Env: E2E6_PORT listen port (default 27220)
|
// Env: E2E6_PORT listen port (default 27220)
|
||||||
// ML_BIN server binary (default ../../zig-out/bin/mongo-lite)
|
// MFDB_BIN server binary (default ../../zig-out/bin/multiforadb)
|
||||||
// E2E6_KEEP keep the log file after the run
|
// E2E6_KEEP keep the log file after the run
|
||||||
const { MongoClient, ObjectId } = require('mongodb');
|
const { MongoClient, ObjectId } = require('mongodb');
|
||||||
const { spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
@@ -20,7 +20,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const PORT = Number(process.env.E2E6_PORT || 27220);
|
const PORT = Number(process.env.E2E6_PORT || 27220);
|
||||||
const BIN = process.env.ML_BIN || path.resolve(__dirname, '../../zig-out/bin/mongo-lite');
|
const BIN = process.env.MFDB_BIN || path.resolve(__dirname, '../../zig-out/bin/multiforadb');
|
||||||
const DBFILE = process.env.E2E6_DB || path.resolve(__dirname, '../../.zig-cache/e2e6-full.log');
|
const DBFILE = process.env.E2E6_DB || path.resolve(__dirname, '../../.zig-cache/e2e6-full.log');
|
||||||
const URL = `mongodb://127.0.0.1:${PORT}`;
|
const URL = `mongodb://127.0.0.1:${PORT}`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "e2e",
|
"name": "e2e",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "These exercise mongo-lite from a real driver over TCP: full CRUD, query operators, aggregation, error codes, concurrent clients, and crash recovery.",
|
"description": "These exercise multiforadb from a real driver over TCP: full CRUD, query operators, aggregation, error codes, concurrent clients, and crash recovery.",
|
||||||
"main": "e2e.js",
|
"main": "e2e.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
|||||||
Reference in New Issue
Block a user