6 lessons

Profiling

Where cost goes inside one process. CPU and allocation profiles, reading a flame graph without fooling yourself, and the CPU-bound versus I/O-bound distinction that decides which fix can possibly work.

Symptom→Signal→Measurement→Hypothesis→Evidence→Root Cause→Change→Validation→Regression Check

Every lesson below starts from an observable symptom and ends with the measurement that proves the fix worked. Numbers carry a label saying whether they were measured, estimated, simulated or invented to show a shape.

When the Trace Runs Out of Answers

The trace says `pricing-service` spent 240 ms and has no children. That is where tracing stops and profiling starts: one tells you which process is expensive, the other tells you which function inside it is.

Symptom · A fat leaf span: one wide bar in the waterfall with nothing underneath it, in a service that should not need 240 ms to do its job.
Self Time, Total Time, and Where the CPU Went

A CPU profile ranks functions two ways, and confusing them wastes afternoons. Total time says "this subtree is expensive"; self time says "this function is expensive". Only one of them tells you where to put the fix.

Symptom · CPU utilization near saturation, latency climbing with load, and a profile whose top entry is `main` at 100% — technically true and completely useless.
Reading a Flame Graph
▶ lab

Width is time, height is stack depth, and the horizontal axis is not time at all. Getting that last part wrong is the single most common flame-graph misreading, and it makes people look for patterns that cannot exist.

Symptom · A flame graph is open, it looks like a mountain range, and every frame seems equally plausible as the culprit.
Allocation Rate Is a Cost Even Without a Leak

Memory that is allocated and immediately freed never shows up as growth, so leak hunting finds nothing. It still costs: every megabyte allocated is a megabyte the collector must eventually walk, and at 500 MB/s that is where your latency went.

Symptom · Steady resident memory, no leak, but GC CPU share in double digits and a latency histogram with a second bump caused by collection pauses.
Computing or Waiting?

The first fork in every performance investigation. A CPU-bound service wants better algorithms or more cores; an I/O-bound service wants concurrency, batching or a faster dependency. Applying either fix to the other problem reliably makes things worse.

Symptom · Latency is high. That is all you know so far, and the next decision — profile the code or chase the dependency — depends entirely on this distinction.
Always-On Profiling, and the Diff That Finds Regressions

Profiling during an incident means capturing a baseline you do not have, on an instance that may be healthy, after the pathology has passed. Continuous profiling makes the baseline a query — and turns "did this release get slower" into a diff.

Symptom · CPU per request crept up 30% over six releases, nobody can say which one caused it, and the profile you would need to compare against was never taken.