README
¶
Mongo Gateway Benchmark Harness
mongo_gateway_bench runs the same MongoDB-driver workload against either the
TreeDB Mongo gateway or a MongoDB server. It is intended to make the first
Mongo-compatible product benchmarks reproducible, especially ops/sec and disk
usage comparisons.
Build
GOWORK=off go build ./cmd/mongo_gateway_bench
TreeDB Target
GOWORK=off go run ./cmd/mongo_gateway_bench \
-target treedb \
-treedb-dir /tmp/treedb-mongo-bench \
-keep-treedb-dir \
-documents 10000 \
-batch-size 1000 \
-insert-producers 4 \
-mongo-max-pool-size 32 \
-mongo-max-connecting 8 \
-reads 10000 \
-range-reads 1000 \
-updates 1000 \
-concurrent-read-kinds id,email,range \
-concurrent-readers 8 \
-concurrent-reads 10000 \
-concurrent-writers 4 \
-concurrent-writes 1000 \
-secondary-indexes 2 \
-format json
For -target treedb, the command starts an in-process TreeDB Mongo gateway on a
loopback listener, connects with the official MongoDB Go driver, and reports
the sum of regular file sizes for the TreeDB directory after load, after a final
checkpoint, and by default after the full TreeDB maintenance stack. If
-treedb-dir is provided and already exists, the harness
removes and recreates it before loading deterministic fixtures so repeated runs
are reproducible. Obvious unsafe reset targets such as root, the current
checkout, the temp directory itself, a home directory, or an immediate child of
a home directory are rejected.
The default -client-mode driver uses the official MongoDB Go driver
Collection.InsertMany path for the load phase and app-style decoded read
phases. -client-mode driver-find-raw still uses official-driver
Collection.Find for range reads, but iterates cursor.Current as raw BSON
instead of decoding documents into bson.M; use it to isolate official-driver
find/cursor overhead from application decode overhead. -client-mode driver-command still uses the official driver but sends the load
phase as a raw insert command through Database.RunCommand, avoiding the
driver's InsertMany _id discovery and InsertedIDs bookkeeping. It is useful
for isolating driver CRUD-helper overhead and works against both TreeDB and
MongoDB targets. -client-mode driver-command-raw also uses RunCommand, but
passes a prebuilt raw BSON insert command to reduce driver-side command encoding
when -prebuild-documents is enabled; its age range-read phase also uses a raw
find command and parses cursor.firstBatch as raw BSON instead of decoding
documents into bson.M. -client-mode driver-unack uses official-driver
InsertMany with unacknowledged write concern; its sampled load metric is
client enqueue cost, while the phase waits for the final inserted _id to
become visible before reporting wall ops/sec. -client-mode raw-wire is
TreeDB-only and calls the in-process gateway directly with raw OP_MSG document
sequences. -client-mode raw-wire-tcp sends the same raw OP_MSG traffic over
the gateway's loopback listener, isolating TreeDB gateway network/wire-server
cost from Mongo Go driver cost. -client-mode raw-wire-tcp-pipeline uses the
same raw TCP load path and pipelines age range find requests on one connection
up to -raw-wire-tcp-pipeline-depth (default 128), which isolates
single-connection request/response latency from server execution. The gateway
coalesces already-buffered pipelined responses before writing, so deeper
pipelines can keep the socket full across server write cycles without changing
the server's per-write coalescing cap. Raw-wire modes use raw OP_MSG
document sequences for the insert load phase and raw OP_MSG find requests for
the age range-read phase while keeping setup and non-range phases on the driver.
-client-mode direct is a TreeDB-only path
that calls collections.Collection directly for the same phase names, using the
selected -treedb-document-format (json, template-v1/collections-v1, or
bson) while bypassing the MongoDB Go driver, loopback sockets, and Mongo gateway
command/response handling. Use direct mode to answer whether a slow Mongo API
phase is already slow in the collection engine and selected storage format; use
raw-wire mode to estimate the gateway/server ceiling without the driver's
per-document marshal and _id discovery overhead; use driver mode for
user-visible Mongo compatibility throughput.
-client-mode native-wire-inproc and -client-mode native-wire-tcp are
TreeDB-only native protocol load paths. They use TreeDB collection IDs and
stored document bytes over the native-wire server/client instead of Mongo OP_MSG,
then keep setup and later read/update phases on the Mongo compatibility driver
so they remain comparable with the existing benchmark sweep.
When -prebuild-documents is enabled, the harness builds both structured BSON
documents and raw BSON bytes before the measured workload. driver-command and
raw-wire reuse the raw bytes during the load phase so their insert-call timing
does not include fixture BSON marshaling. Direct mode also reuses prebuilt raw
BSON-derived stored documents for direct collection inserts.
Use -insert-producers N to split the insert load phase across producer
goroutines. The effective producer count is capped at the number of insert
batches so small runs do not open unused clients. Official-driver modes share one
mongo.Client, so
-mongo-max-pool-size, -mongo-min-pool-size, and -mongo-max-connecting
control the driver pool used by those producers. When -mongo-max-pool-size is
left unset, validation treats the driver default max pool size as 100 for
-mongo-min-pool-size checks. raw-wire-tcp and
raw-wire-tcp-pipeline open one fastclient connection per effective producer
for the load phase, and raw-wire uses one in-process wire owner per effective
producer. Raw-wire TreeDB modes use raw find commands for both _id and
secondary-index email read phases so client-mode labels do not hide ordinary
Mongo driver indexed-find overhead. JSON output includes effective_producers
and producer_results for the load phase plus mongo_pool_stats_after_load and
mongo_pool_stats_final when the official driver pool is involved.
The TreeDB benchmark target defaults to the explicit benchmark-only no-WAL
ceiling because the default cell creates secondary indexes. Use
command_wal_relaxed for command-WAL coverage once collection catalog index
commands are supported for this workload:
-treedb-profile bench-treedb-document-format template-v1-treedb-data-root-storage compressed-treedb-index-state-root-storage compressed-treedb-index-root-storage compressed-treedb-buffered-indexed-write-max-documents 0(use the collection default: 256000 for default async threshold publish; 96000 when async flush is disabled)-treedb-buffered-indexed-write-max-root-runs 0(explicit0disables this trigger; when this flag is omitted while document or byte thresholds are overridden, the tool keeps the matching root-run compatibility default)-treedb-maintenance full-client-mode driver
The TreeDB target always opens with outer leaves in the leaf value log and the
cached leaf-log backend, so collection and secondary-index roots exercise the
same leaf-vlog path as the optimized collection benchmarks. The default full
maintenance mode runs the high-level CompactStorage path first, then closes
the benchmark gateway and runs offline index vacuum to shrink index.db for
final-footprint reporting. Use -treedb-maintenance checkpoint to reproduce the
older checkpoint-only disk metric, or none to skip final TreeDB disk
reporting. No-index YCSB-style command-WAL comparisons are covered by
scripts/ycsb_compare_mongodb_treedb.sh; this indexed benchmark default should
move to command WAL after catalog index commands are implemented.
-treedb-document-format accepts json, template-v1/collections-v1, and bson. BSON mode
stores Mongo wire documents as native BSON collection records, avoiding the
canonical Extended JSON bridge used by the JSON/template-v1 gateway paths.
Use -treedb-buffered-indexed-write-max-documents,
-treedb-buffered-indexed-write-max-bytes, and
-treedb-buffered-indexed-write-max-root-runs to reproduce indexed write-domain
auto-flush threshold experiments. The benchmark report records the effective
normalized collection thresholds after index creation. For document thresholds,
0 means use the collection default. For byte and root-run thresholds, 0
disables that trigger unless all indexed-write thresholds are otherwise left at
their native defaults. For compatibility with older threshold experiments, if a
document or byte threshold is overridden and the root-run flag is omitted, the
tool fills in the matching root-run default; pass
-treedb-buffered-indexed-write-max-root-runs 0 explicitly to keep root-run
flushing disabled in that case.
The Go profile benchmarks in profile_bench_test.go use the collection default
indexed async flush mode. They can force foreground threshold publish for
baseline comparisons, or override async queue limits for focused root-publish
experiments:
MONGO_GATEWAY_PROFILE_BENCH_DISABLE_BUFFERED_INDEXED_ASYNC_FLUSH=true \
go test ./cmd/mongo_gateway_bench \
-run '^$' \
-bench '^BenchmarkDirectCollectionConcurrentUpdateBSONIndexes2$' \
-benchtime=100000x \
-benchmem
MONGO_GATEWAY_PROFILE_BENCH_BUFFERED_INDEXED_ASYNC_FLUSH_MAX_QUEUED_UNITS=4 \
go test ./cmd/mongo_gateway_bench \
-run '^$' \
-bench '^BenchmarkDirectCollectionConcurrentUpdateBSONIndexes2$' \
-benchtime=100000x \
-benchmem
When enabled, those benchmark rows include buffered_async_flush and the
normalized buffered_async_max_units so they are not compared against
synchronous-threshold rows by accident. The concurrent update profile benchmark
times a final FlushAll() drain before reporting docs/sec, so async rows include
deferred indexed publish work rather than enqueue latency alone.
For update-combiner ingress experiments, set
MONGO_GATEWAY_PROFILE_BENCH_UPDATE_COMBINE_SHARDS=N. The default 1 preserves
the single-queue combiner. Values above 1 shard request ingress by document ID
while preserving one global merged publish batch, and benchmark rows report
update_combine_shards so shard-count runs are not mixed accidentally.
For sharded-combiner lane-worker runs, add
MONGO_GATEWAY_PROFILE_BENCH_UPDATE_COMBINE_LANE_WORKERS=true to let each
document-ID shard prepare direct buffered update plans concurrently, then merge
prepared plans back into one global buffered staging path. This flag is effective
only when MONGO_GATEWAY_PROFILE_BENCH_UPDATE_COMBINE_SHARDS is greater than
1; only effective lane-worker runs report update_combine_lane_workers.
Stale prepared plans are admitted only when their target document IDs have not
changed in the buffered layer since the plan's read generation; conflicting
plans fall back to the ordinary per-document update path and read buffered
writes before returning.
MongoDB Target
GOWORK=off go run ./cmd/mongo_gateway_bench \
-target mongo \
-mongo-uri mongodb://127.0.0.1:27017 \
-documents 10000 \
-batch-size 1000 \
-insert-producers 4 \
-mongo-max-pool-size 32 \
-mongo-max-connecting 8 \
-reads 10000 \
-range-reads 1000 \
-updates 1000 \
-concurrent-read-kinds id,email,range \
-concurrent-readers 8 \
-concurrent-reads 10000 \
-concurrent-writers 4 \
-concurrent-writes 1000 \
-secondary-indexes 2 \
-format json
For -target mongo, the command connects to the supplied URI, drops the
benchmark database by default, can compact the collection before final stats
collection with -mongo-compact, and reports dbStats fields after load and
at the end of the run.
Reusable Comparison Harness
Use scripts/mongo_gateway_compare.sh when you want a complete
TreeDB-vs-MongoDB matrix bundle instead of hand-running each target. The
harness builds mongo_gateway_bench, runs matching TreeDB and MongoDB cells,
writes raw JSON for every target, records physical du bytes where available,
and generates a Markdown report plus TSV summary.
For the standard fast Mongo/native client-shape matrix used by the TreeDB client-attribution tracker, prefer:
OUT_DIR=/tmp/treedb_fast_client_matrix_$(date +%Y%m%d_%H%M%S) \
scripts/mongo_gateway_fast_client_matrix.sh
That wrapper pins gateway-shaped BSON documents, two secondary indexes, 16
insert producers/readers, command_wal_relaxed, settled read state, and the
current TreeDB/MongoDB client-mode sets. It also writes
fast_client_matrix_context.md, which explains client-mode, acknowledgement,
raw-command construction, direct-storage, and settled drain/flush boundaries.
To compare every TreeDB document format in one bundle:
TREEDB_DOCUMENT_FORMATS="json template-v1 bson" scripts/mongo_gateway_compare.sh
To include the raw-wire TreeDB insert load path and the driver command path
beside the normal MongoDB Go driver InsertMany path:
TREEDB_DOCUMENT_FORMATS="bson" \
TREEDB_CLIENT_MODES="driver driver-find-raw driver-command driver-command-raw driver-unack direct raw-wire-tcp raw-wire-tcp-pipeline raw-wire native-wire-tcp native-wire-inproc" \
scripts/mongo_gateway_compare.sh
To run matching TreeDB and MongoDB client-mode rows where MongoDB supports the mode:
TREEDB_DOCUMENT_FORMATS="bson" \
TREEDB_CLIENT_MODES="driver driver-find-raw driver-command driver-command-raw driver-unack" \
MONGO_CLIENT_MODES="driver driver-find-raw driver-command driver-command-raw driver-unack" \
scripts/mongo_gateway_compare.sh
When the MongoDB side runs only the ordinary driver mode, the matrix keeps
the legacy baseline config names mongo and mongo_range_index. Explicit
mongo_driver config names are used only when driver is part of a multi-mode
MongoDB client matrix.
scripts/mongo_gateway_compare.sh \
--out /tmp/gomap_mongo_gateway_compare \
--docs "1000 10000" \
--indexes "0 2" \
--concurrent-read-kinds "id,email,range" \
--concurrent-readers 8 \
--concurrent-reads 10000 \
--concurrent-writer-sweep "1,4" \
--concurrent-writes 1000 \
--insert-producers 4 \
--mongo-max-pool-size 32 \
--mongo-max-connecting 8 \
--mongo-mode docker
Docker mode starts a fresh MongoDB container and isolated data directory per matrix cell, which makes MongoDB physical disk usage reproducible enough for local comparisons. If you already have a MongoDB server, use:
scripts/mongo_gateway_compare.sh \
--mongo-mode external \
--mongo-uri mongodb://127.0.0.1:27017
The bundle contains:
report.md: reviewable Markdown with highlights, disk bytes/doc, ops/sec ratios, and raw input paths.summary.tsv: machine-readable per-phase comparison rows.matrix.tsv: target/config/document/index/raw-json/physical-byte index.raw/*.json: unmodifiedmongo_gateway_bench -format jsonoutput.profiles/: per-phase TreeDB pprof artifacts when--profile-treedbis used.treedb_data/and, in Docker mode,mongodb_data/: final data directories for post-run inspection.
The first checked-in bundle is
docs/benchmarks/mongo_gateway_compare_2026-04-29/.
YCSB Attribution Harness
Use scripts/mongo_gateway_ycsb_attribution.sh for the focused TreeDB Mongo
gateway YCSB load shape. It keeps the workload close to go-ycsb mongodb
load: BSON documents, batch size 1, 16 insert producers, fast TreeDB profile,
no post-load maintenance, and 0/1 secondary-index rows. The matrix separates
the official MongoDB Go driver path from raw Mongo wire over TCP, nativewire
over TCP, and direct collection insertion.
PROFILE=true scripts/mongo_gateway_ycsb_attribution.sh
The bundle contains:
summary.mdandsummary.tsv: per-client throughput for every enabled phase, sampled ns/op, latency percentiles, producer count, and raw result paths.index_<n>/<client_mode>/result.json: unmodifiedmongo_gateway_bench -format jsonoutput for each cell.index_<n>/<client_mode>/profiles/: per-phase pprof artifacts and*_top.txtCPU summaries whenPROFILE=trueand the cell matchesPROFILE_MODES/PROFILE_INDEXES.
Useful focused overrides:
DOCUMENTS=1000 CLIENT_MODES="driver direct" INDEXES_LIST=0for a smoke run.CLIENT_MODES="driver raw-wire-tcp native-wire-tcp direct"to reproduce the standard attribution rows.DOCUMENT_SHAPE=ycsb POINT_READ_PROJECTION=ycsb INDEXES_LIST=0 CLIENT_MODES="driver raw-wire-tcp direct" CONCURRENT_READERS=16 CONCURRENT_READS=100000 READS=0 RANGE_READS=0to attribute the exactgo-ycsb mongodbpoint-read shape:_idlookup, projection offield0throughfield9,_idexcluded, and decode intomap[string][]byteon the driver path.PROFILE=true PROFILE_MODES="driver raw-wire-tcp"to capture gateway and raw wire CPU evidence without profiling every cell.READS,UPDATES,CONCURRENT_READERS, andCONCURRENT_WRITERScan enable additional run-style phases, but exact YCSB run semantics should still be measured withgo-ycsb mongodb.
To regenerate only the report from an existing bundle:
GOWORK=off go run ./cmd/mongo_gateway_compare_report \
-matrix /tmp/gomap_mongo_gateway_compare/matrix.tsv \
-report /tmp/gomap_mongo_gateway_compare/report.md \
-summary /tmp/gomap_mongo_gateway_compare/summary.tsv
Useful overrides:
DOCS_LIST="1000 10000 100000"INDEXES_LIST="0 1 2"TREEDB_CLIENT_MODES="driver driver-find-raw driver-command driver-command-raw driver-unack direct raw-wire-tcp raw-wire-tcp-pipeline raw-wire native-wire-tcp native-wire-inproc"MONGO_CLIENT_MODES="driver driver-find-raw driver-command driver-command-raw driver-unack"READS=50000,RANGE_READS=5000,UPDATES=5000DELETES=1000RANGE_INDEX=trueor--range-indexto createage_1and reportage_range_indexed_limit_10instead of scan fallback.PROFILE_TREEDB=trueor--profile-treedbto pass-profile-dirfor every TreeDB cell and retain per-phase profiles under the bundle'sprofiles/directory.CONCURRENT_READERS=16,CONCURRENT_READS=50000- Use either
CONCURRENT_READ_KINDS="id,email,range"withCONCURRENT_READER_SWEEP, orCONCURRENT_RANGE_READER_SWEEPwithCONCURRENT_RANGE_READS; the two range-concurrency paths share phase names and are intentionally not combined. CONCURRENT_WRITERS=8,CONCURRENT_WRITES=10000BATCH_SIZE=1000MONGO_IMAGE=mongo:8
For a larger MongoDB comparison that keeps the 1M-doc cell bounded enough for a local run, use explicit operation counts:
BATCH_SIZE=5000 scripts/mongo_gateway_compare.sh \
--out /tmp/gomap_mongo_gateway_compare_large \
--docs "100000 1000000" \
--indexes "2" \
--reads 50000 \
--range-reads 5000 \
--range-index \
--updates 5000 \
--concurrent-read-kinds "id,email,range" \
--concurrent-reader-sweep "1,2,4,8,16" \
--concurrent-reads 50000 \
--concurrent-writer-sweep "1,2,4,8,16" \
--concurrent-writes 10000 \
--timeout 120m
Manual Matrix
Run both targets with the same values and keep the raw JSON outputs:
for docs in 1000 10000 100000; do
for indexes in 0 1 2; do
GOWORK=off go run ./cmd/mongo_gateway_bench \
-target treedb \
-treedb-dir "/tmp/treedb-mongo-${docs}-${indexes}" \
-keep-treedb-dir \
-documents "$docs" \
-reads "$docs" \
-range-reads "$((docs / 10))" \
-updates "$((docs / 10))" \
-concurrent-read-kinds id,email,range \
-concurrent-readers 8 \
-concurrent-reads "$((docs / 10))" \
-concurrent-writer-sweep 1,4 \
-concurrent-writes "$((docs / 20))" \
-secondary-indexes "$indexes" \
-format json > "treedb-${docs}-${indexes}.json"
GOWORK=off go run ./cmd/mongo_gateway_bench \
-target mongo \
-documents "$docs" \
-reads "$docs" \
-range-reads "$((docs / 10))" \
-updates "$((docs / 10))" \
-concurrent-read-kinds id,email,range \
-concurrent-readers 8 \
-concurrent-reads "$((docs / 10))" \
-concurrent-writer-sweep 1,4 \
-concurrent-writes "$((docs / 20))" \
-secondary-indexes "$indexes" \
-format json > "mongo-${docs}-${indexes}.json"
done
done
The initial workload phases are:
load_insert_many: batched document inserts. The exact client call depends onclient_mode:InsertManyfordriveranddriver-find-raw,RunCommand({insert, documents})fordriver-command,RunCommandwith a prebuilt raw BSON command fordriver-command-raw, unacknowledgedInsertManyplus a post-load visibility wait fordriver-unack, directCollection.InsertBatchin the selected storage format fordirect, and raw OP_MSG document sequences forraw-wire/raw-wire-tcp/raw-wire-tcp-pipeline. When-insert-producersis greater than 1, this phase reports aggregate wall-clock throughput and per-producer call latency inproducer_results.id_find_one: point lookup by_id.email_find_one: point lookup by theemailfield; emitted only when the email secondary index is part of the cell.age_range_scan_limit_10/age_range_indexed_limit_10: bounded range query withlimit: 10; operations count range queries, not returned documents. The indexed variant is emitted when-range-indexcreatesage_1.concurrent_age_range_scan_limit_10_rN/concurrent_age_range_indexed_limit_10_rN: total age range queries split acrossNgoroutines. Use-concurrent-range-reader-sweep 1,2,4,8,16with-concurrent-range-readsto emit multiple range-reader fanout phases from one loaded database. The legacy-concurrent-range-readers Nflag emits one fanout phase and cannot be combined with-concurrent-range-reader-sweep.id_update_set:$setupdate by_id.concurrent_id_find_one_rN,concurrent_email_find_one_rN,concurrent_age_range_scan_limit_10_rN, andconcurrent_age_range_indexed_limit_10_rN: total read operations split acrossNgoroutines. Use-concurrent-read-kinds id,email,rangeor-concurrent-read-kinds allto select the saturated read shapes. Use-concurrent-reader-sweep 1,2,4,8,16with-concurrent-readsto emit multiple reader-count phases from one loaded database. The comparison report groups_id/email rows into the "Concurrent Read Sweep" table and rendersconcurrent_age_range_*_rNrows in the dedicated range-read section. The genericrangeread kind cannot be combined with-concurrent-range-readersor-concurrent-range-reader-sweep, because those options emit the same range phase names. The legacy-concurrent-readers Nflag still emits one reader-count phase per selected kind and cannot be combined with-concurrent-reader-sweep.concurrent_id_update_set_wN: total$setupdates split acrossNgoroutines. Use-concurrent-writer-sweep 1,2,4,8,16with-concurrent-writesto emit multiple writer-count phases from one loaded database.id_delete_one: optional deletes; disabled unless-deletesis non-zero.
Update phases change only non-indexed fields by default.
-update-indexed-field requires -secondary-indexes=2 or greater so the city
index exists and the indexed city field changes, exercising secondary-index
maintenance in the update path. -secondary-indexes=3 adds an unchanged
active_1 bool index so city-only update runs can verify that unchanged
secondary indexes are not rebuilt as total index count grows.
-range-index creates an additional age_1 index so the age range-read phase
materially exercises indexed range planning instead of the bounded scan
fallback.
Latency samples are per MongoDB driver/gateway call. Update phases build the
filter and update document before starting the sampled timer, so update samples
focus on the driver/gateway/DB call rather than request construction. ops_sec
is normalized by document count over the whole phase loop; sampled_ops_sec and
sampled_ns_per_op are derived from the aggregate sampled call duration. Prefer
sampled values when investigating gateway/client overhead with prebuilt
fixtures, and wall ops_sec when measuring the full benchmark loop. Insert
latency percentiles are per batch call. Range-query samples include cursor
materialization with cursor.All.
mongo_pool_stats_after_load is reset immediately before the insert load phase,
so its checkout counters describe the measured insert phase rather than setup or
index creation. mongo_pool_stats_final is cumulative from the load phase
through the later read/update/delete phases. Pool checkout latency percentiles are
computed from a bounded sample to keep high-concurrency benchmark overhead
predictable; checkout counts and aggregate checkout duration still cover every
recorded checkout event.
Use -timeout 0 to run without an overall benchmark deadline.
The package test TestTreeDBProfileSmokeFastAndWALOnFast runs a small write-only
TreeDB gateway smoke against both the no-WAL fast and legacy WAL relaxed-fast
profiles to catch large profile regressions without making the smoke a
replacement for the full matrix.
Phase Pprof Artifacts
Use -profile-dir when a full benchmark run exposes a scaling wall and the
next step is to inspect the TreeDB or gateway hot path. The command writes one
CPU profile per measured phase, plus heap, allocs, block, mutex, and goroutine
profiles captured after each phase. It also writes profile_manifest.json and
benchmark_result.json into the same directory so the profile files can be tied
back to the exact benchmark config and phase throughput.
The profile directory must be empty at startup; use a fresh mktemp -d
directory for each run so stale artifacts cannot be mixed into a new capture.
CPU profiles are phase-scoped. Heap, allocs, block, mutex, and goroutine profiles are runtime snapshots captured at phase end; block, mutex, and allocs profiles are cumulative within the benchmark process rather than reset between phases.
OUT=$(mktemp -d /tmp/gomap_mongo_gateway_pprof_XXXXXX)
GOWORK=off go run ./cmd/mongo_gateway_bench \
-target treedb \
-client-mode driver-command-raw \
-treedb-document-format bson \
-documents 1000000 \
-batch-size 5000 \
-insert-producers 8 \
-reads 0 \
-range-reads 0 \
-updates 0 \
-concurrent-writers 8 \
-concurrent-writes 80000 \
-secondary-indexes 2 \
-prebuild-documents \
-treedb-maintenance none \
-profile-dir "$OUT" \
-format json
Useful first-pass commands:
GOWORK=off go build -o ./bin/mongo_gateway_bench ./cmd/mongo_gateway_bench
go tool pprof -top -cum ./bin/mongo_gateway_bench "$OUT/load_insert_many.cpu.pprof"
go tool pprof -top -cum ./bin/mongo_gateway_bench "$OUT/concurrent_id_update_set_w8.cpu.pprof"
go tool pprof -top -cum ./bin/mongo_gateway_bench "$OUT/concurrent_id_update_set_w8.mutex.pprof"
By default, profiling mode enables block profiling at rate 1 and mutex
profiling at fraction 5. Use -profile-block-rate 0 or
-profile-mutex-fraction 0 to disable either profile. Runtime traces are larger
and are off by default; add -profile-trace when scheduler-level detail is
needed. Heap profiles are captured without forcing a garbage collection by
default so measured phases keep their normal heap state; add -profile-heap-gc
when you specifically want post-GC heap snapshots.
For insert-scaling investigations, run the same command repeatedly with
-insert-producers 1, 2, 4, 8, and 16 while keeping -documents,
-batch-size, -client-mode, and document format constant. For write-contention
investigations, keep the load shape fixed and use -concurrent-writer-sweep /
-concurrent-writes.
Reader/Writer Scaling Wrapper
Use scripts/mongo_gateway_scaling_bench.sh for a repeatable reader/writer
scaling sweep. It runs mongo_gateway_bench for each reader and writer count,
writes raw JSON for every cell, then feeds the matrix into
mongo_gateway_compare_report so the output shape matches the normal
TreeDB-vs-MongoDB comparison bundle.
scripts/mongo_gateway_scaling_bench.sh \
--out /tmp/gomap_mongo_gateway_scaling \
--docs 100000 \
--indexes 2 \
--batch-size 10000 \
--insert-producers 8 \
--writers "1 2 4 8 16" \
--readers "1 2 4 8 16" \
--concurrent-writes 80000 \
--concurrent-reads 80000
The default sweep is TreeDB-only, using the bench profile, native BSON
collection storage, driver-command-raw, prebuilt BSON documents, and no final
maintenance so the measured phases focus on concurrency. Add
--include-mongo --mongo-uri mongodb://127.0.0.1:27017 to run matching cells
against an existing MongoDB server. The bundle contains report.md,
summary.tsv, matrix.tsv, raw JSON, and a README that records the kept
TreeDB data path for profile follow-up. Depending on where --out is placed,
that kept TreeDB data directory may be outside the bundle directory.
Add --update-indexed-field to make writer-scaling cells update city and
therefore measure secondary-index update/publish work.
Gateway Profiling Benchmarks
The package also includes benchmark-only entry points for isolating Mongo gateway overhead from the underlying collection insert path:
OUT=$(mktemp -d /tmp/gomap_mongo_gateway_profile_XXXXXX)
MONGO_GATEWAY_PROFILE_BENCH_BATCH_SIZE=10000 \
GOWORK=off go test ./cmd/mongo_gateway_bench \
-run '^$' \
-bench '^(BenchmarkTreeDBGatewayLoadBSONIndexes2|BenchmarkTreeDBGatewayLoadGeneratedIDBSONIndexes2|BenchmarkTreeDBGatewayLoadObjectIDBSONIndexes2|BenchmarkTreeDBGatewayLoadUnackBSONIndexes2|BenchmarkTreeDBGatewayRunCommandLoadBSONIndexes2|BenchmarkTreeDBGatewayRunRawCommandLoadBSONIndexes2|BenchmarkTreeDBGatewayRawWireLoadBSONIndexes2|BenchmarkTreeDBGatewayRawWireTCPLoadBSONIndexes2|BenchmarkDirectCollectionLoadBSONIndexes2|BenchmarkDirectCollectionConcurrentUpdateBSONIndexes2|BenchmarkDirectCollectionConcurrentUpdateBSONCityIndex1|BenchmarkDirectCollectionConcurrentUpdateBSONIndexes2CityUpdate|BenchmarkDirectCollectionConcurrentUpdateBSONIndexes3CityUpdate|BenchmarkClientBSONBatchEncode)$' \
-benchtime 2000000x \
-count 1 \
-timeout 0 \
-benchmem \
-cpuprofile "$OUT/cpu.pprof" \
-memprofile "$OUT/mem.pprof"
The benchmark shapes are intentionally different:
BenchmarkTreeDBGatewayLoadBSONIndexes2uses the official MongoDB Go driver against the in-process TreeDB gateway.BenchmarkTreeDBGatewayLoadGeneratedIDBSONIndexes2uses the same official driver path with documents that omit_id, forcing the driver to generate ObjectIDs and avoiding its expensive explicit-_iddecode path. This is a diagnostic for workloads that do not require caller-supplied primary keys.BenchmarkTreeDBGatewayLoadObjectIDBSONIndexes2uses explicit ObjectID primary keys. If this remains close to the explicit string_idbenchmark, the cost is the driver's explicit-_idbookkeeping rather than string_idencoding.BenchmarkTreeDBGatewayLoadUnackBSONIndexes2uses official-driverInsertManywith unacknowledged writes. It measures client enqueue cost, not completed durable load throughput, and is only a diagnostic for response-path overhead.BenchmarkTreeDBGatewayRunCommandLoadBSONIndexes2uses the official MongoDB Go driverRunCommandinsert path against the in-process TreeDB gateway, bypassingInsertMany_idextraction while still using the driver transport.BenchmarkTreeDBGatewayRunRawCommandLoadBSONIndexes2sends a prebuilt raw BSON insert command through official-driverRunCommand, minimizing driver command encoding while preserving official-driver transport.BenchmarkTreeDBGatewayRawWireLoadBSONIndexes2sends raw OP_MSG insert document sequences to the gateway, bypassing the Go driver's document marshal and_iddiscovery work while still exercising gateway wire parsing and command handling.BenchmarkTreeDBGatewayRawWireTCPLoadBSONIndexes2sends the same raw OP_MSG insert document sequences over the gateway's TCP listener, isolating network and connection-serving cost from the official driver's CRUD-helper cost.BenchmarkDirectCollectionLoadBSONIndexes2inserts the same BSON document shape through the collection API without the Mongo gateway.BenchmarkDirectCollectionConcurrentUpdateBSONIndexes2preloads a BSON collection, then runs concurrent_idupdates throughCollection.Updatewithout the Mongo gateway. This is useful when comparing gateway update profiles with the storage/update path directly. The benchmark enables collection-manager detailed update timing for its measured phase and reports update attribution metrics such asupdate_current_read_ns/doc,update_callback_ns/doc,update_index_state_extract_ns/doc,update_old_index_state_extract_ns/doc,update_new_index_state_extract_ns/doc,update_primary_run_ns/doc,update_secondary_runs_ns/doc,update_index_value_changes/doc,update_index_value_unchanged/doc,update_unique_checks/doc,update_unique_check_skips/doc, per-index changed-delta metrics such asupdate_collection_bench.docs_index_1_city_1_changed/doc,update_collection_bench.docs_index_1_city_1_secondary_runs/doc,update_collection_bench.docs_index_2_email_1_unchanged/doc, andupdate_collection_bench.docs_index_2_email_1_unique_check_skips/docfor collections with at most eight index runtimes,update_buffer_stage_ns/doc, buffer-stage submetrics such asupdate_buffer_precheck_ns/doc,update_buffer_lock_wait_ns/doc,update_buffer_lock_hold_ns/doc,update_buffer_validation_ns/doc,update_buffer_root_scan_ns/doc,update_buffer_domain_prepare_ns/doc,update_buffer_freeze_ns/doc,update_buffer_root_table_ns/doc,update_buffer_primary_index_ns/doc,update_buffer_unique_index_ns/doc,update_buffer_primary_append_ns/doc,update_buffer_secondary_append_ns/doc,update_buffer_root_append_ns/doc,update_buffer_flush_ns/doc, combiner timings such asupdate_combine_enqueue_ns/doc,update_combine_wait_ns/doc,update_combine_drain_ns/doc, andupdate_combine_run_ns/doc,update_publish_ns/doc, andupdate_items/batchfrom the collection manager's measured-phase counters.update_buffer_lock_hold_ns/docis the enclosing domain mutex hold time for buffered staging, not an additive sibling of the other buffer-stage submetrics; the narrower root-table, append, and freeze submetrics are nested attribution inside the broader buffer-stage timers.update_buffer_flush_ns/docreports threshold-flush scheduling/publish time separately. The measured phase includes the finalFlushAll()drain so background async publish work is charged to the same throughput row that scheduled it. The same row also reports backend value-log mmap counters such asbackend_vlog_mmap_hits/doc,backend_vlog_mmap_miss_dead_mapping_cap/doc,backend_vlog_mmap_fallback_readat/doc, andbackend_vlog_mmap_hit_ratioplus sealed-map budget/residency counters such asbackend_vlog_mmap_sealed_denied_count_cap/doc,backend_vlog_mmap_sealed_segments, andbackend_vlog_mmap_active_bytes, so value-log mmap fallbacks can be separated from otherReadAtsources in CPU profiles and tied back to mmap residency limits.BenchmarkDirectCollectionConcurrentUpdateBSONCityIndex1,BenchmarkDirectCollectionConcurrentUpdateBSONIndexes2CityUpdate, andBenchmarkDirectCollectionConcurrentUpdateBSONIndexes3CityUpdaterun the same direct update shape while changing only the indexedcityfield. They are intended to guard per-index changed-delta planning: adding unchanged indexes such asemail_1andactive_1should not create extra secondary runs or affected secondary roots for a city-only update.BenchmarkClientBSONBatchEncodemeasures client-side BSON document encoding alone.
The benchmark-only helpers accept these optional environment variables:
MONGO_GATEWAY_PROFILE_BENCH_BATCH_SIZE,
MONGO_GATEWAY_PROFILE_BENCH_UPDATE_DOCUMENTS, and
MONGO_GATEWAY_PROFILE_BENCH_WRITERS. They also accept
MONGO_GATEWAY_PROFILE_BENCH_BUFFERED_INDEXED_ASYNC_FLUSH and
MONGO_GATEWAY_PROFILE_BENCH_BUFFERED_INDEXED_ASYNC_FLUSH_MAX_QUEUED_UNITS for
focused indexed async-flush experiments.
Use the official-driver row for user-visible Mongo compatibility throughput,
driver-find-raw to remove range-read bson.M decode while keeping
official-driver Find/cursor behavior, the driver-command rows to quantify the
driver's CRUD-helper overhead, the raw-wire rows to estimate the gateway/server
ceiling, raw-wire-tcp-pipeline to measure how much single-connection TCP
latency can be hidden by request pipelining, and the direct collection row to
estimate the storage-engine ceiling for the same document shape. For
acknowledged high-throughput ingest through the
public MongoDB Go driver,
driver-command-raw is the fastest current path because it keeps the official
driver transport while bypassing InsertMany's explicit-_id discovery and
InsertedIDs bookkeeping.
Interpreting Results
-secondary-indexes 2 creates email_1 and city_1; -secondary-indexes 3
adds active_1. The age range phase is a bounded scan unless -range-index is
set; benchmark output names the phase age_range_scan_limit_10 or
age_range_indexed_limit_10 so reports can separate fallback cost from indexed
range-search cost.
For TreeDB targets, -treedb-read-state settled (the default) calls
CollectionManager.FlushAll after load and before read phases, so reads measure
settled collection roots rather than mutable write-domain state. Use
-treedb-read-state unsettled to leave post-load memtables/write-domain state in
place for read phases when you want to compare settled and unsettled visibility
costs.
For TreeDB, prefer treedb_disk_after_maintenance.total_bytes when present, and
use treedb_disk_after_checkpoint.total_bytes for checkpoint-only runs. The
child path breakdown, especially index.db, leaf_vlog, and value_vlog,
shows where bytes landed. These are logical bytes: the sum of regular file
sizes, not allocated physical disk usage including block allocation, sparse-file
effects, filesystem compression, or metadata. Capture du separately when
physical on-disk usage is the comparison target.
For MongoDB, compare mongodb_stats_final.storageSize,
mongodb_stats_final.indexSize, and mongodb_stats_final.totalSize. If the
MongoDB server is local, also capture a filesystem du of the database path for
the final report. The comparison harness treats isolated physical du as the
preferred local disk metric because small WiredTiger workloads can report
dbStats.totalSize values that are much smaller than the actual data directory.
The benchmark intentionally keeps BSON format questions visible. If TreeDB load or read phases spend a meaningful amount of time re-encoding documents in future profiles, that is evidence for adding a native BSON collection document format beside JSON and template-v1.
Documentation
¶
There is no documentation for this package.