Hey everyone,

Over the past few weeks, I’ve been chipping away at a series of performance tweaks and operator controls for the Hive-Engine node. What started as a sprawling wish list of potential optimizations quickly became a deliberate exercise in restraint. Instead of dropping one massive, high-risk pull request on the maintainers, I broke the work down into three smaller, easily reviewable bundles, all of which should currently be available in the qa branch of hive-engine/hivesmartcontracts (at least for now, as they get broader testing before hitting a main release):
In plain English: operators can now tune the JavaScript VM pool, customize MongoDB connection pooling, and let the streamer catch up faster after maintenance—all without touching the source code. Under the hood, transaction index writes are now cleanly batched, and the VM pool warms up predictably on first use.
Most importantly, none of this touches consensus, smart contract execution rules, or block processing order. Here’s a breakdown of what changed, why it matters, and how it held up in live testing on my witness node.
The first bundle focused on exposing two settings that were either hardcoded or awkward to adjust without maintaining custom forks.
Hive-Engine executes smart contracts inside isolated JavaScript VMs. Previously, the engine locked this down to a hardcoded ceiling of five VM instances. You can now configure this directly in config.json:
{
"maxJSVMs": 5
}
Five remains the default fallback if the setting is omitted or misconfigured.
Raising this isn't a magic button that makes your node twice as fast. It’s an operator tradeoff: bumping the VM count gives a heavily loaded node more execution headroom for concurrent tasks, but it also increases your memory footprint once that pool is spun up. If you're on a memory-constrained box, you'll want to leave this alone or even tune it down; if you have plenty of RAM and run a busy node, you now have the freedom to adjust it.
The node now also lets you define explicit connection pool settings for MongoDB:
{
"databasePool": {
"maxPoolSize": 20,
"minPoolSize": 2,
"maxIdleTimeMS": 30000,
"waitQueueTimeoutMS": 10000
}
}
Here is what these control:
maxPoolSize: The maximum number of concurrent MongoDB connections in the pool.minPoolSize: The minimum baseline of connections to keep open.maxIdleTimeMS: How long an idle connection can sit before being closed.waitQueueTimeoutMS: How long an operation will wait for an available connection before timing out.If you don't specify databasePool, the MongoDB driver falls back to its standard defaults.
This flexibility matters because different node roles have completely different workloads. A dedicated witness node usually wants a lean, conservative pool with minimal overhead. On the other hand, a public RPC node fielding dozens of queries alongside block processing can easily starve for database connections if it's stuck with default limits.
Where Knobs A was about configuration, Knobs B tackled two internal bottlenecks in the block-processing path that were quietly wasting time.
Every time the node commits a sidechain block, it generates transaction-index records so historical lookups and explorer queries can find them later. Historically, the code iterated through the block and inserted these records one by one:
insertOne()
insertOne()
insertOne()
...
Each insert meant another round-trip through the MongoDB driver. For blocks with lots of transactions, that back-and-forth adds noticeable latency.
Now, the node collects all index records for the block and flushes them in a single insertMany() call. I kept the insert explicitly ordered (ordered: true) because transaction indexes encode positions within the block, and keeping that guarantee crystal clear in the code makes life much easier for reviewers. Also, if a block happens to have zero transactions, it skips the call entirely rather than firing off an empty array.
With maxJSVMs now configurable, we also revisited how and when those VMs get initialized.
Previously, isolates were created lazily one at a time as smart contracts trickled in. That meant early contract calls suffered allocation jitter while spinning up new VMs on demand.
Now, the lifecycle is much cleaner:
maxJSVMs.By paying the initialization cost once up front, we smooth out execution time and eliminate staggered allocation stalls during block processing.
The third bundle tackles the block streamer—the component responsible for pulling layer-1 Hive blocks from upstream RPC nodes.
By default, the streamer operates with static, fixed parameters:
maxQps in the original config).lookaheadBufferSize).Static limits are safe and predictable when your node is happily humming along at the head of the chain. But if your node went down for OS updates, hardware maintenance, or a restart and finds itself 500 or 5,000 blocks behind, those conservative throttles make catch-up needlessly sluggish.
Knobs C introduces two opt-in controls to let the streamer shift gears when catching up, without being reckless.
You can enable dynamic request scaling with:
{
"maxQps": 2,
"adaptiveQps": true,
"adaptiveQpsMax": 4
}
When adaptiveQps is enabled, the streamer monitors RPC health metrics that were already being tracked:
adaptiveQpsMax.maxQps.(Quick naming clarification: it's called maxQps in Hive-Engine config history, but under the hood it's actually controlling in-flight concurrent requests per node, not a strict queries-per-second token bucket.)
Having an explicit ceiling (adaptiveQpsMax) is vital here. Just because a public RPC node is fast right now doesn't give our nodes a license to hammer it into the ground.
Along with concurrency, we can also let the lookahead buffer scale based on how far behind the node is:
{
"lookaheadBufferSize": 15,
"dynamicLookaheadBuffer": true,
"dynamicLookaheadBufferMaxSize": 50
}
When enabled, the streamer adjusts its prefetch window automatically:
dynamicLookaheadBufferMaxSize (e.g., 50 blocks).When the buffer resizes, pending block entries are safely preserved and ring indexes are cleanly reset. If dynamic lookahead is off, the streamer behaves exactly as it always has.
Here’s an example showing all of the new knobs configured together in config.json:
{
"maxJSVMs": 5,
"databasePool": {
"maxPoolSize": 20,
"minPoolSize": 2,
"maxIdleTimeMS": 30000,
"waitQueueTimeoutMS": 10000
},
"streamerConfig": {
"maxQps": 2,
"lookaheadBufferSize": 15,
"adaptiveQps": true,
"adaptiveQpsMax": 4,
"dynamicLookaheadBuffer": true,
"dynamicLookaheadBufferMaxSize": 50
}
}
Treat this snippet as an illustrative baseline, not gospel.
Every node environment is different. A production witness running on dedicated hardware with a local Hive RPC node has totally different constraints than a public API node running on shared cloud infrastructure. If you're running a witness, my recommendation is to start conservative, change one variable at a time, and monitor your RAM usage, Mongo cache, and RPC error rates before cranking up concurrency.
Unit tests and local testnets are great, but nothing replaces letting code chew on live mainnet traffic. With all three bundles merged into the qa branch, I deployed that branch directly to my live witness setup to see how it behaved in the wild.
To test Knobs C specifically, I stopped the service, let the node fall about 300 Hive blocks behind head, and kicked it back on.
With the QPS transitions logged at WARN level (so they show up cleanly in journalctl without having to turn on noisy debug logs), I watched the adaptive logic kick in right away. The streamer detected healthy RPC responses, bumped concurrency to the max limit, and steadily chewed through the backlog. Throughout the catch-up, the block hashes matched the main network identically at every sampled checkpoint until it locked right back onto the head block.
I did encounter one transient MongoDB NoSuchTransaction error during an initial restart, which cleared up cleanly on a second restart. While it almost certainly stems from MongoDB replica set transaction session cleanup rather than anything in the streamer, I’d rather document every weird blip openly than pretend software development is frictionless.
The node has been humming along steadily since, but remember: this is real-world validation, not an absolute benchmark. Your mileage will vary depending on your disk I/O, MongoDB version, upstream Hive RPC endpoints, and server resources.
When you're touching a blockchain node that witnesses rely on for consensus, rule number one is: don't break state.
To be 100% clear, these updates do not touch:
All we're touching is the plumbing around the edges: resource limits, batching database roundtrips, warming VMs up front, and scheduling block fetches more intelligently. The node processes the exact same data in the exact same sequence—it just spends less time sitting idle between steps.
I’m already outlining the next series of experiments, keeping the same incremental mindset:
None of these are set in stone; each will need its own isolated testing, safety review, and PR. The core philosophy won't change: backward-compatible defaults, small reviewable diffs, and never sacrificing consensus correctness for speed.
To recap what we have so far:
None of this is meant to be a silver bullet that magically solves every node performance headache. But by giving operators sensible knobs, reducing obvious bottlenecks, and verifying everything on a live witness, we get a node that’s noticeably more responsive, predictable, and easier to run.
Give the new settings a spin on your test nodes, and let me know how they perform for you! If you want to check out the code or test it in your own setup, most of the changes are currently live on the qa branch of hive-engine/hivesmartcontracts.
As always,
Michael Garcia a.k.a. TheCrazyGM
what are the hardware requirements for it to run : because the require option show me a 404 :) https://github.com/hive-engine/hivesmartcontracts-wiki/blob/master/Requirements.md
This seems to change over time and I'm not sure if anyone "knows" the answer anymore! Many people do have opinions - the one I have heard the most is "you'll want faster/bigger/better than mine!" so I'm not sure if it actually runs on a toaster but a few people do 😄
and does your toaster also run crysis ? 😀