Analyzing Node.js .heapsnapshot Files and Using heapquery for Memory‑Leak Investigation
The article shows how to generate a Node.js V8 heap snapshot with v8.getHeapSnapshot, explains the JSON‑like .heapsnapshot structure of nodes, edges, and strings, and demonstrates using the heapquery tool to import the data into SQLite for SQL queries that locate and trace memory‑leaking objects such as a HugeObj instance.
This article explains how to obtain a heap snapshot in Node.js via v8.getHeapSnapshot, examines the internal .heapsnapshot file format, and demonstrates how to leverage the heapquery tool to query heap data with SQL.
Using v8.getHeapSnapshot
In Node you can call v8.getHeapSnapshot to generate a .heapsnapshot file that contains a snapshot of the current V8 heap. The following example creates a large object and writes a snapshot:
const { writeHeapSnapshot } = require("v8");
class HugeObj {
constructor() {
this.hugeData = Buffer.alloc((1 << 20) * 50, 0);
}
}
module.exports.data = new HugeObj();
writeHeapSnapshot();Running the script with node test.js produces a file such as Heap.20210228.154141.9320.0.001.heapsnapshot, which can be opened in Chrome DevTools.
Structure of a .heapsnapshot File
The file is JSON‑like. The top‑level snapshot object contains meta‑information, while the heavy data lives in three arrays: nodes, edges and strings. nodes and edges are stored as flat integer sequences, each line representing one node or edge.
Each node is serialized by HeapSnapshotJSONSerializer::SerializeNode and consists of the fields (type, name, id, self_size, edge_count, trace_node_id). The order of the fields is defined in snapshot.meta.node_fields, and the corresponding types are listed in snapshot.meta.node_types. Values are stored as numbers; strings are stored as indices into the strings array.
Example of a node line (formatted for readability):
{
"nodes": [9,1,1,0,10,0, // first node
9,2,3,0,23,0 // second node
]
}The edge array follows a similar scheme. Each edge stores (type, edge_name_or_index, to). The type and name/index are resolved using snapshot.meta.edge_fields and snapshot.meta.edge_types.
Node‑Edge Relationship
During snapshot generation V8 builds a graph where each node knows the number of outgoing edges ( edge_count). The method HeapSnapshot::FillChildren reorganises edges so that the first edge_count entries belong to the first node, the next edge_count entries belong to the second node, and so on. This allows the file to be traversed without storing explicit "from" references.
Querying the Snapshot with heapquery
heapqueryis a tiny utility (available in JavaScript and Rust) that parses a .heapsnapshot file and imports the data into a SQLite database. After running: npx heapquery path_to_your_heapdump.heapsnapshot you obtain path_to_your_heapdump.db. You can then explore the heap with ordinary SQL, for example: SELECT * FROM node ORDER BY self_size DESC; and trace ownership chains:
SELECT from_node, B.name AS from_node_name
FROM edge AS A
JOIN node AS B ON A.from_node = B.id
WHERE A.to_node = 51389;The article walks through a concrete investigation of a memory leak caused by a HugeObj instance, showing how to locate the leaking object, follow its incoming edges, and finally discover the constructor definition in the source code.
Conclusion
By understanding the .heapsnapshot format and the V8 internals that produce it, developers can go beyond the visual Chrome DevTools UI and perform custom analyses with SQL. The heapquery tool demonstrates one practical way to turn raw heap data into actionable insights for backend Node.js applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
