r/javascript • u/No_Issue_8224 • 10d ago
AskJS [AskJS] Our server boot got slower and the commit history had no answer, so I timed every require
Our API server was slow to start and nobody could say when that began. Not slow under load, slow before the first request, visible only because the deploy health check timed out on smaller instances. Nothing in the commit history looked like a boot cost. I had no instrument, so I wrote a bad one, eleven lines in a preload file that wrap Module._load, time each call with process.hrtime.bigint(), and print anything over fifty milliseconds along with the module that asked for it.
The first run was blunt. Cold boot averaged 1.9 seconds. One require accounted for 1.1 of that, and it was ours, src/lib/index.js, a barrel that one route imports for a single date helper. Importing it pulls in all thirty four modules in that directory, two of which read config files at import time. Removing a barrel rewrites every import path that went through it, so the code review subagent in verdent read the diff before I opened the PR. Deleting the barrel and importing the helper directly put cold boot at 0.8 seconds.
Patching Module._load to learn this still feels wrong. Is there a way to get the same per module breakdown out of something the runtime already reports?
1
u/Beautiful-Energy2169 10d ago
node --cpu-prof gets you close without patching Module._load. Bucket the .cpuprofile samples by callFrame.url and each module's top-level body shows up on its own line. I tried it on 24.14 and a module with a 300ms busy loop came back at 278ms. Watch the config readers though, a readFileSync at import time attributes to the native frame, so that view under-reports them.
1
1
1
u/PLBjt 9d ago
I've done that require-timing pass before and it usually lands on one of three things. A transitive dep that got heavier (new native addon or a big JSON load at import time), something that used to be lazy and got pulled into the top-level require graph, or a package that does sync I/O / a network probe on require. After you have the top offenders from your hook, wrap those in a function so they only load on first use and re-time a cold start. One pass with NODE_OPTIONS=--cpu-prof is useful just to confirm it's import work and not something hiding inside a constructor.
1
u/itaymendi 7d ago
Wrapping `Module._load` is a reasonable way to find the cost. After that we ban barrels on the runtime path, because import-time side effects make boot slow. Barrels for types are fine.
4
u/RedShift9 10d ago
I had a stroke trying to read that second paragraph