MongoDB Champion 2026 · Published on Medium
I’m part of the MongoDB Champions 2026 community. This story was also published in MongoDB’s official publication on Medium: read Part II on Medium.
Adapted from the public project README. This is Part II of a two-part series; start with the platform build. Figures are linked to the original repository. Results and limitations describe the documented experiment, not the current state of a private environment.
This part separates mechanism from measurement. It documents the kernel and allocator settings, the WiredTiger working sets, the controlled THP ON/OFF procedure, and results from the 500k and 50M datasets. Claims about causal THP effects are limited to what the repeated application-level measurements and telemetry support.
THP and TCMalloc deployment prerequisites
Enabling Transparent Huge Pages on RK1 requires a kernel compiled with the
appropriate option. The stock kernel does not expose the THP interface, so the
custom kernel packages must be installed before enabling THP through Ansible.
setup_rk1cluster.yml installs the two custom kernel packages from the repository
root and holds them so the package upgrade cannot replace them. Both .deb files
must be present beside the playbook before running the setup.
# MongoDB 8 on ARM64: enable THP and upgraded TCMalloc per-CPU caches
ansible-playbook --ask-vault-pass -i inventory.yml thp_enable.yml
# Optional A/B comparison: disable THP and restart MongoDB one node at a time
ansible-playbook --ask-vault-pass -i inventory.yml thp_disable.yml
# MongoDB 8 THP values: always, defer+madvise, 0, and 1
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag
cat /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none
cat /proc/sys/vm/overcommit_memory
# MongoDB nodes: /dev/nvme0n1 is mounted at /data by deploy_mongodb_cluster.yml
mount -o noatime,nodiratime /dev/nvme0n1 /data
# Monitoring node (rk1-node4): /dev/nvme0n1 is mounted at /monitoring by setup_monitoring.yml
mount -o noatime,nodiratime,logbufs=8 /dev/nvme0n1 /monitoring
# Tune network buffers for ARM
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
MongoDB 8 uses upgraded TCMalloc on supported ARM64 Linux systems. The
deployment enables THP before Docker starts MongoDB, sets the required
always, defer+madvise, max_ptes_none=0, and vm.overcommit_memory=1
values, and sets GLIBC_TUNABLES=glibc.pthread.rseq=0 in the container so
TCMalloc can use per-CPU caches. The thp_enable.yml and thp_disable.yml
playbooks provide a serial, restart-and-report A/B comparison for benchmarks.
Run the enable playbook again after comparison benchmarks to restore the
MongoDB 8-recommended configuration.
The RK1 kernel must expose /sys/kernel/mm/transparent_hugepage with CONFIG_TRANSPARENT_HUGEPAGE enabled. The playbooks
cannot enable THP when the kernel omits that interface; install or boot a
kernel built with that option first. Check the active kernel with:
uname -r
zgrep CONFIG_TRANSPARENT_HUGEPAGE /proc/config.gz 2>/dev/null || \
grep CONFIG_TRANSPARENT_HUGEPAGE "/boot/config-$(uname -r)"
ls -ld /sys/kernel/mm/*transparent_hugepage
Controlled THP evaluation on ARM64
Starting with MongoDB 8, Transparent Huge Pages (THP) are recommended on
supported Linux systems. Because the stock RK1 kernel does not expose the
required THP interface, this cluster uses a custom kernel built with
CONFIG_TRANSPARENT_HUGEPAGE=y.
The objective of this benchmark is to determine whether MongoDB’s use of THP produces a measurable application-level benefit on the RK3588 ARM64 platform. Two complementary datasets are used:
| Dataset | Records | Purpose |
|---|---|---|
| Small | 500,000 | Observe THP with a relatively small working set |
| Large | 50,000,000 | Exercise WiredTiger eviction and storage under cache pressure |
The tests compare identical MongoDB, Docker, YCSB, storage, and hardware
configurations. Only the host THP state changes. Docker does not isolate a
container from host kernel memory management, so THP applies directly to the
containerized mongod process. The MongoDB containers have no CPU, memory, or
CPU-affinity limits, and /data/db is a host bind mount rather than Docker’s
writable overlay filesystem.
Test Environment
| Component | Configuration |
|---|---|
| MongoDB | 8.3.8, three-node replica set |
| Architecture | ARM64, Rockchip RK3588 |
| Memory | 16 GB per MongoDB node |
| Storage | Local NVMe, XFS |
| Deployment | Docker, without resource limits |
| WiredTiger cache | Approximately 7.2 GiB |
| YCSB client | redis/go-ycsb ARM64 container on rk1-node4 |
| Target | Direct connection to the primary |
| Measured write concern | majority |
Memory-stack interpretation: THP, TCMalloc, and WiredTiger
The three layers answer different questions. THP is a host-kernel page
mapping feature; TCMalloc manages mongod’s user-space allocations; and
WiredTiger manages database pages in its own cache. Seeing activity in one
layer does not establish a throughput benefit in another.
| Layer | What it changes | Evidence examined | Limit of the evidence |
|---|---|---|---|
| THP | Backs eligible anonymous memory with larger pages | Host AnonHugePages, fault allocations/fallbacks, collapse/split telemetry |
Host-level huge pages do not, by themselves, quantify exactly how many belong to mongod |
| TCMalloc | Reuses memory in allocator caches and per-CPU structures | Allocated/free/unmapped memory and cache breakdown | Free memory retained by the allocator is not identical to WiredTiger cache occupancy |
| WiredTiger | Reads, dirties, and evicts database pages | Cache fill, read/eviction rates, dirty ratio, eviction triggers | Cache churn and NVMe I/O can dominate translation-overhead improvements |
The dashboard is interpreted as follows:
- In the 500k campaign, host
AnonHugePagesgrows to roughly 4–5 GiB under THP ON, while cache fill rises over time without the sustained high-rate NVMe reads seen in the 50M campaign. The TCMalloc free/unmapped and cache panels evolve during both campaigns; they indicate allocator state changes, not a standalone THP performance result. - In the 50M campaign, THP-backed anonymous memory grows further, to roughly 6–7 GiB per data node. WiredTiger cache sits around 80% full, with sustained reads, eviction, and dirty-page cycles. These are distinct sources of latency variability that must be considered alongside the YCSB numbers.
The THP dashboard counters and AnonHugePages are host measurements. A
process-specific smaps_rollup/smaps check for mongod would strengthen the
attribution of huge-page coverage to MongoDB. No separate TCMalloc ON/OFF or
WiredTiger-cache-size A/B test was performed here: THP is the controlled
variable; the other two subsystems provide explanatory telemetry.
Workload and Measurement Method
Both datasets use the same mixed workload:
- 60% READ, 25% UPDATE, and 15% SCAN
- Zipfian request distribution
- uniform scan-length distribution
readPreference=primarydirectConnection=true- 4, 8, 16, 32, and 64 YCSB client threads
- 600 seconds per concurrency level
The configured operation count is deliberately much larger than can be completed during a run. The playbook uses an external timeout to terminate each test after 600 seconds, making the comparison time-bound instead of operation-count-bound. The go-ycsb limitation and shutdown behavior are documented in the Part I gotchas.
Dataset creation is separate from the measured workload. Loading uses 32
threads and writeConcern=1 to reduce preparation time; measured runs use
writeConcern=majority. The load phase is not included in benchmark results.
Benchmark Procedure
- Enable THP and reboot if requested:
ansible-playbook --ask-vault-pass -i inventory.yml thp_enable.yml
- Confirm the reported THP settings, then run the benchmark and give it a descriptive prefix:
ansible-playbook --ask-vault-pass -i inventory.yml ycsb_cache_pressure_test.yml \
-e confirm_unauthenticated_ycsb=true \
-e load_dataset=true \
-e file_prefix=thpon
- Disable THP and reboot if requested:
ansible-playbook --ask-vault-pass -i inventory.yml thp_disable.yml
- Confirm the reported THP settings and repeat the benchmark with a different prefix:
ansible-playbook --ask-vault-pass -i inventory.yml ycsb_cache_pressure_test.yml \
-e confirm_unauthenticated_ycsb=true \
-e file_prefix=thpoff
The dataset load is opt-in. Load it once before the first comparison run with
-e load_dataset=true; subsequent runs reuse the existing dataset and should
leave that option unset. Loading uses 32 client threads and primary-only write
acknowledgement for faster preparation. The measured workload uses majority
write concern and direct primary connections.
For every reuse run, pass the intended ycsb_record_count and database
explicitly (for example, -e ycsb_database=ycsbsmall -e ycsb_record_count=500000). The protected playbook compares the actual
collection document count with the configured record count before the timed
thread sweep and refuses to run on a mismatch. The attached summary CSVs
still omit READ error counters, so raw thread logs remain necessary for a
final request-validity audit. The record-count failure that motivated this
safeguard is described in the Part I gotchas.
Playbook Parameters
The main parameters are defined near the top of
ycsb_cache_pressure_test.yml and can be overridden with -e:
| Parameter | Default | Purpose |
|---|---|---|
ycsb_record_count |
50000000 |
Number of documents in the dataset |
ycsb_operation_count |
50000000000 |
Deliberately unreachable operation limit |
ycsb_run_duration_seconds |
600 |
Duration of each concurrency level |
ycsb_threads_sweep |
4, 8, 16, 32, 64 |
Client concurrency levels |
load_dataset |
false |
Load or reuse the dataset |
ycsb_load_threads |
32 |
Concurrent clients used during loading |
ycsb_load_write_concern |
1 |
Load-phase acknowledgement level |
ycsb_cache_pressure_write_concern |
majority |
Measured-run acknowledgement level |
file_prefix |
Current YYYYMMDD_HHMMSS |
Prefix for all result files |
The workload is read/update/scan only: 60% reads, 25% updates, and 15% scans.
Inserts are disabled when reusing a populated dataset, avoiding duplicate-key
errors. The measured runs use mongodb.write_concern=majority and
mongodb.read_preference=primary.
Generated Results
Results are written to ycsb_results/ on the control node. For a prefix of
nothp, the playbook creates one raw file per thread level:
ycsb_results/nothp-threads-4.txt
ycsb_results/nothp-threads-8.txt
ycsb_results/nothp-threads-16.txt
ycsb_results/nothp-threads-32.txt
ycsb_results/nothp-threads-64.txt
When load_dataset=true, the loader output is also saved as
nothp-load.txt. The thread files contain YCSB throughput, operation counts,
average latency, and percentile latency for READ, UPDATE, SCAN, and TOTAL.
Python Report Generator
ycsb_report.py parses the raw thread files and generates an aggregate CSV and
PNG chart. Matplotlib is installed by setup_facility-node.sh as
python3-matplotlib.
The playbook runs the report generator automatically. To regenerate a report manually or process an existing run:
python3 ycsb_report.py \
--results-dir ycsb_results \
--prefix nothp \
--output-prefix nothp
This creates ycsb_results/nothp-summary.csv and
ycsb_results/nothp-report.png. The chart contains throughput, average
latency, p95 latency, and operation-count panels plotted against client
threads. The CSV is intended for further comparison or archival.
Small Dataset: 500,000 Documents
The 500,000-document campaign contains four repetitions under each THP
setting. The load logs confirm recordcount=500000 and report 500,000
inserts. Run 1 is excluded consistently as the post-load transient/warm-up
repetition; the tables use the arithmetic mean of the final CSV totals from
runs 2-4.
Figure 1 — Small-dataset Grafana overview (THP OFF and THP ON campaigns).
View source figure: MongoDB RK1 THP small-dataset benchmark
| Threads | THP OFF ops/s | THP ON ops/s | ON vs. OFF | Average total latency OFF/ON | P95 OFF/ON |
|---|---|---|---|---|---|
| 4 | 561.3 | 560.2 | -0.2% | 7.10 / 7.12 ms | 32.02 / 32.09 ms |
| 8 | 972.5 | 976.8 | +0.4% | 8.21 / 8.17 ms | 32.28 / 32.39 ms |
| 16 | 1,287.3 | 1,292.0 | +0.4% | 12.41 / 12.37 ms | 51.89 / 52.15 ms |
| 32 | 1,341.7 | 1,335.9 | -0.4% | 23.83 / 23.94 ms | 123.92 / 121.90 ms |
| 64 | 1,335.0 | 1,335.8 | +0.1% | 47.91 / 47.89 ms | 304.38 / 297.21 ms |
THP effects on throughput are mixed in sign and within roughly 0.5% across these stabilized repetitions. Neither average nor P95 total latency shows a consistent THP advantage. In Figure 1, THP-backed anonymous memory rises to roughly 4-5 GiB on the enabled nodes; allocation and collapse activity appears after THP is switched on, while flow control remains inactive. THP is thus visibly in use, but not associated with a material YCSB performance gain in this campaign.
The summary CSVs contain completed-operation throughput and latency, not
READ success/error counters. The load size and configured recordcount are
aligned, but a complete request-validity audit requires the raw YCSB thread
logs. Error-free READs should be confirmed there before treating these
numbers as final.
Sixteen threads is the practical throughput/latency sweet spot for this small-dataset workload. Averaging the THP OFF and ON results exposes the cost of pushing concurrency past the knee:
| Threads | Mean throughput | Mean average latency | Throughput vs. 16 | Latency vs. 16 | Mean P95 |
|---|---|---|---|---|---|
| 4 | 560.8 ops/s | 7.11 ms | -56.5% | -42.6% | 32.06 ms |
| 8 | 974.7 ops/s | 8.19 ms | -24.4% | -33.9% | 32.34 ms |
| 16 | 1,289.6 ops/s | 12.39 ms | baseline | baseline | 52.02 ms |
| 32 | 1,338.8 ops/s | 23.89 ms | +3.8% | ~1.9x | 122.91 ms |
| 64 | 1,335.4 ops/s | 47.90 ms | +3.5% | ~3.9x | 300.80 ms |
From 16 to 32 threads, throughput rises by only 3.8% while mean latency almost doubles; at 64 threads throughput slips slightly below the 32-thread level while mean latency nearly quadruples relative to 16 threads. P95 also rises by roughly 2.4x and 5.8x respectively. The 32- and 64-thread settings are poor latency trade-offs for this specific small-dataset profile.
Large Dataset: 50 Million Documents
The large dataset is intentionally much greater than the effective WiredTiger cache. It creates sustained cache churn and exercises the full path through WiredTiger eviction, NVMe I/O, dirty-page processing, checkpointing, and replica-set acknowledgement.
Figure 2 — Large-dataset Grafana overview (50M documents, THP OFF followed by THP ON).
View source figure: MongoDB RK1 THP large-dataset benchmark
The table below uses the arithmetic mean of the final YCSB totals from repeated runs 2-4. Run 1 is excluded consistently as the transient/warm-up repetition. Latency values are total-operation latency.
| Threads | THP OFF ops/s | THP ON ops/s | Throughput change | Avg latency OFF/ON | P95 OFF/ON |
|---|---|---|---|---|---|
| 4 | 77.7 | 79.0 | +1.7% | 51.4 / 50.6 ms | 341.8 / 337.4 ms |
| 8 | 144.6 | 150.6 | +4.1% | 55.3 / 53.1 ms | 369.8 / 356.9 ms |
| 16 | 257.9 | 261.9 | +1.6% | 62.0 / 61.1 ms | 424.4 / 411.9 ms |
| 32 | 411.1 | 440.4 | +7.1% | 77.8 / 72.7 ms | 535.9 / 493.7 ms |
| 64 | 605.4 | 638.4 | +5.5% | 105.6 / 100.1 ms | 718.0 / 670.7 ms |
Unlike the small dataset, the repeated large-dataset results show a modest THP advantage at every tested concurrency level. The largest mean throughput gain is approximately 7.1% at 32 threads, while average and P95 latency are also lower with THP enabled at all five thread counts. The gain is not linear, so it should be interpreted as a workload-level effect rather than a fixed THP improvement factor.
The dashboard confirms that this is a genuine cache-pressure workload:
- WiredTiger cache remains around 80% full on the active data nodes.
- eviction is sustained throughout the campaigns;
- NVMe reads frequently reach approximately 150-200 MiB/s;
- dirty-cache ratios rise and fall with checkpoint and writeback cycles;
- replication lag adds occasional latency variability.
Flow control requires a separate interpretation. The flow-control activity in Figure 2 occurs only during the 50M-document loading step, when documents are initially inserted. It is absent from the measured 4/8/16/32/64-thread YCSB sweeps. Flow control is therefore evidence of pressure during dataset creation, but it is not a limiting factor in the reported benchmark results.
THP use is also much greater than in the small experiment. The MongoDB nodes
reach approximately 6-7 GiB of AnonHugePages each. THP allocations are
continuous, fallbacks remain rare, and collapse/split activity does not show a
pathological storm. Docker therefore does not prevent THP from operating, and
the measured benefit is not accompanied by an obvious THP allocation failure
or compaction problem.
The 50M-record workload continues to gain throughput through 64 client threads, unlike the small workload. That gain comes with rapidly increasing latency. The following table averages THP ON and OFF values to isolate the concurrency trade-off from the THP comparison:
| Threads | Mean throughput | Mean average latency | Throughput vs. 16 | Latency vs. 16 | Operating interpretation |
|---|---|---|---|---|---|
| 4 | 78.4 ops/s | 51.0 ms | -69.8% | -17.1% | Under-utilized |
| 8 | 147.6 ops/s | 54.2 ms | -43.2% | -11.9% | Conservative |
| 16 | 259.9 ops/s | 61.6 ms | baseline | baseline | Sweet spot |
| 32 | 425.8 ops/s | 75.3 ms | +63.8% | +22.2% | Throughput-oriented |
| 64 | 621.9 ops/s | 102.9 ms | +139.3% | +67.0% | Maximum throughput, high latency |
At 32 and especially 64 threads, average latency rises much faster in absolute terms and P95 latency reaches roughly 494-536 ms and 671-718 ms respectively. Those settings remain useful when batch throughput is the priority, but they are no longer the most balanced choice. Sixteen threads is a reasonable latency-conscious operating point for this large-dataset profile, but not its sweet spot: unlike the small dataset, 32 and 64 threads provide substantial additional throughput. Select those higher settings when the extra completed work justifies the measured latency cost.
Figure 3 — Concurrency operating regions observed on the RK1 cluster.
Global Conclusion
Together, the two datasets show that MongoDB 8.3.8 successfully and extensively
uses Transparent Huge Pages on RK1/RK3588 ARM64, including when mongod runs
inside Docker. Allocation fallbacks remain very low, so the comparison is not
between an effective THP OFF configuration and a nominal-but-nonfunctional THP
ON configuration.
The performance effect depends on the working set:
- With the 500,000-document workload, THP ON and OFF differ by less than approximately 0.5% in mean throughput across the tested thread counts. The 16-thread knee is clear: moving to 32 or 64 adds only 3.8% or 3.5% throughput on average, while average total latency rises by roughly 1.9x or 3.9x, respectively. Raw READ error counters should be checked before final publication of this new campaign.
- With 50 million documents and sustained WiredTiger cache pressure, repeated runs 2-4 show a small but consistent THP benefit: approximately 1.6-7.1% higher mean throughput, accompanied by lower mean and P95 total latency at every tested concurrency level. Throughput continues to scale above 16 threads, but average and P95 latency rise sharply, making 16 threads the most balanced operating point and 32-64 threads throughput-oriented choices.
The evidence therefore supports a more nuanced conclusion than either “THP always improves MongoDB” or “THP has no effect”:
On this RK1 ARM64 cluster, THP is neutral for the small, relatively cache-resident workload and modestly beneficial for the 50M-document cache-pressure workload in these repeated runs. The larger workload shows both greater THP-backed memory and higher mean throughput with THP ON; these measurements do not isolate the memory-coverage increase as the cause. WiredTiger, NVMe I/O, replication, and concurrency remain the dominant determinants of overall performance. Sixteen client threads is the small-dataset throughput/latency sweet spot; on the large dataset, 32 and 64 threads can offer substantial additional throughput, but also increase average and P95 latency.
The operations-per-watt chart in Part I describes the platform’s power envelope, but it is based on server-side MongoDB operations rather than YCSB requests. A per-thread and time-aligned power comparison would be required to establish whether THP improves completed YCSB work per watt.
These findings are specific to this RK3588 hardware, MongoDB 8.3.8, custom Linux kernel, Docker deployment, WiredTiger configuration, NVMe subsystem, replica-set topology, and YCSB workload. They should not be generalized into a universal MongoDB THP rule. For this project, the MongoDB 8-recommended THP-enabled configuration remains the operational default, while THP OFF is retained for controlled A/B testing.
Further Performance Optimizations Worth Testing
- A/B test MongoDB in Docker versus bare metal to quantify the actual
containerization overhead on RK1. The current container has no resource
limits,
/data/dbis a host bind mount, and the metrics confirm that THP is already active for the containerizedmongod, so Docker is not expected to prevent THP operation. - Benchmark
mongodb_wiredtiger_cache_size_gbat4,6, and8on the 16 GB RK1 nodes. The system currently defaults to6to keep more RAM available for Linux page cache, Docker overhead, and monitoring while still preserving a useful WiredTiger working set. - A/B test
mongodb_wiredtiger_collection_compressorbetweenzstdandsnappy. The currentzstddefault is space-efficient, butsnappyoften improves CPU-bound write throughput on ARM. - Consider pinning the
mongodcontainer to the RK3588 Cortex-A76 cores during write-heavy benchmarks. The big.LITTLE topology can otherwise mix latency-sensitive database threads with background tasks. - Compare the current direct-primary workload with a replica-set-aware client
profile to quantify driver-routing and failover behavior. The present
measured workload already uses
writeConcern=majority. - Pin the monitoring containers away from
:latestbefore long benchmark campaigns so exporter behavior and Prometheus/Grafana feature changes do not skew repeatability.