Heap Space exhaustion during large Meteor.publish result sets
0 reputation · 15 Dec 2023, 18:58 UTC
0 reputation · 15 Dec 2023, 18:58 UTC
When a Meteor server publishes a collection containing thousands of documents, Node.js can throw a Heap Space exhaustion error. This occurs because each client subscription keeps a “head” of the data set, and the DDP merge-box on the client side buffers incoming added and changed messages.
The aim is to identify the precise point where server memory usage spikes before applying any optimization, such as pagination or subscription narrowing.
process.memoryUsage()) provides snapshots but not a continuous view of memory pressure during subscription churn.33275 reputation · 16 Dec 2023, 05:22 UTC
To pinpoint where server memory spikes during a large Meteor.publish, you must instrument the lifecycle of the publication observer. In cursor-returning publications, the heap typically peaks during the initial materialization phase—the window between the observer starting and the ready signal being sent to the client. This is when the server fetches documents from MongoDB, converts them to BSON/JSON, and generates the burst of DDP added messages.
Memory growth from one massive publication differs from growth caused by many small subscriptions in its timing and heap distribution:
old_space usage within a single subscription's initial-add window. Memory remains high if the observer must retain a large diff state to track changes.To capture the precise moment of exhaustion, avoid high-overhead snapshots in production. Instead, use process.memoryUsage() and v8.getHeapSpaceStatistics() at these specific hooks:
heapUsed and rss.added Message: Mark the start of the materialization burst.added Message / ready: Capture peak old_space usage before the initial burst completes.// Example instrumentation logic
const stats = v8.getHeapSpaceStatistics();
const oldSpace = stats.find(s => s.space_name === 'old_space');
console.log(`Heap Used: ${process.memoryUsage().heapUsed}, Old Space: ${oldSpace.used_heap_size}`);
The client-side MergeBox buffers incoming messages to ensure consistency, but this occurs after the server has already processed and sent the data. Therefore, MergeBox overhead does not contribute to server-side heap exhaustion. To isolate this, compare the server's old_space spike against the client's message backlog; if the server crashes while the client is still receiving the first 10% of documents, the bottleneck is entirely server-side materialization.
To verify the cause, run a controlled test in staging with a restricted heap (e.g., NODE_OPTIONS=--max-old-space-size=512):
If Test A crashes while Test B survives (despite the same total document count), the issue is the materialization burst of a single large result set.
Missing Diagnostic Detail: Does your publication return a Mongo cursor, or does it manually call this.added() and this.changed()? If manual, the instrumentation must shift from the observer to the server-side arrays used to build the result set.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.