Result
- Out-of-memory crashes on a 2 GB droplet stopped; memory graph stayed flat
- Loading a million points became faster than JSON.parse on the same data
- Roughly 400% gain on the load-and-serve path vs the JSON version, same box
Stack
Node.jsTypeScriptFloat64Array / Int32ArrayArrayBuffer
Built at my day job, where I lead the software team behind the Envizom platform. The code belongs to the company; this write-up comes from my public blog post.
The problem
Every sensor reading was a plain object with a map of gas values. Reports asked for millions of them at once, and the backend, on a DigitalOcean droplet with 2 GB of RAM, kept dying with out-of-memory errors on exactly those queries. A million small objects in V8 means a million allocations, pointer chasing on every property read and a garbage collector that never catches up.
What I built
- Treat the data as an image. One
Float64Arrayfor all decimal values (rows are times, columns are gases) and oneInt32Arrayholding indexes into a dictionary of unique strings, so a repeated label is stored once. One allocation for the whole table. - One class on top. A report asks for a row and gets
[gas, value]pairs; nothing else in the codebase touches the buffers. - Version two. The first version was correct and 8x slower than JSON, because it kept rows in order on every insert. Tracking free rows instead fixed it.
- No JSON on the wire. Database rows go straight into the table, the buffer goes to the browser as is, and the frontend rebuilds the same class.
Result
The memory graph stayed flat where the JSON version fell over, loading a million points became faster than JSON.parse on the equivalent array, and the load-and-serve path measured roughly a 400% gain against the JSON version on the same box.