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.

SymptomSignalMeasurementHypothesisEvidenceRoot CauseChangeValidationRegression 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.