31.12.25

How I think about performance

This article is a consolidation of how I think about performance. Not as a list of tools, tests, or best practices, but as a single way of reasoning about systems under load.

This is not a universal framework and not a promise that I will think the same way forever. It is a snapshot. A fixed point I can return to later and compare against.


Performance is a property of a system

Early in a career, performance usually looks like testing. You run load, collect numbers, and report results. That phase matters. It builds intuition and exposes obvious failures. But it is incomplete.

Performance does not appear during testing. It already exists in the system. Testing only reveals what has been designed and implemented earlier.

Latency, throughput, and stability are shaped by architecture, data flow, shared resources, and assumptions made long before any script is written. Decisions like synchronous versus asynchronous calls, shared databases, cache strategy, retry behavior, or connection limits quietly define performance characteristics.

By the time a test runs, most of these decisions are already locked in. Tests do not change reality at that point. They describe it.

This shift is fundamental. Performance stops being an activity you schedule and becomes a perspective you apply continuously. You stop asking how to run better tests and start asking how the system behaves under pressure, and why it behaves that way.


Boundaries turn opinions into engineering

Without boundaries, performance discussions turn into opinions. “Is it fast enough” has no meaning unless someone defines what “enough” means. A system can handle hundreds of users and still be unacceptable if expectations are different, implicit, or inconsistent.

Performance requirements are not bureaucracy. They are shared boundaries that define normal behavior and acceptable risk.

Good requirements describe conditions, not wishes. Response time under a specific load. Error rate during peak hours. Resource limits that indicate saturation or instability. Boundaries also create alignment. Teams stop arguing about feelings and start comparing results against agreed limits.

These boundaries change over time as products grow and usage evolves. But when they exist, performance work becomes engineering. Without them, it becomes guesswork and negotiation.


Real usage comes before numbers

Performance does not start with tools. It starts with understanding usage.

Who the users are. What they do most often. Which actions matter to the business. When peaks happen. How long sessions live. Where users pause, retry, or abandon flows. Only after this numbers make sense. Load, concurrency, pacing, and data distribution should reflect real behavior, not idealized paths.

Clean scenarios that ignore reality produce clean charts and misleading confidence. Systems rarely fail under perfect usage patterns.

Statistics, logs, and production monitoring are the primary source of truth. Tests exist to reflect that reality and explore its limits, not replace it with synthetic assumptions.


Performance is layered

Performance problems do not live on a single level.

Server-side metrics can look healthy while the user experience is clearly broken. Client-side behavior can feel slow even when APIs respond fast. Load generators can report stable numbers while the real bottleneck hides in shared infrastructure, databases, or service interactions.

Each layer lies in its own way. Each shows only part of the picture. Performance becomes real only when multiple layers tell the same story. Trusting a single viewpoint almost always leads to false conclusions and incorrect fixes.

This is why performance thinking always moves across layers, not deeper into one.


Start simple, then expand

A reliable investigation usually starts at the simplest possible level.

Single service. Single endpoint. Minimal load. Minimal noise. This makes behavior visible. Small inefficiencies, blocking calls, and configuration limits appear clearly when the system is not overwhelmed.

From there, scope expands step by step. More scenarios. More services. More realistic traffic. Each step adds complexity intentionally.

Jumping straight to complex end-to-end tests hides early signals. Many serious issues start as small inefficiencies that are easiest to see when noise is low.

This is not about shift-left as a slogan. It is about protecting clarity of thought.


Tests are questions, not rituals

Different tests exist because they answer different questions.

  • Smoke tests ask whether the system is even ready to respond.
  • Baselines define what normal looks like and create a reference point.
  • Load tests check stability under expected pressure.
  • Capacity tests show where the system stops scaling and why.
  • Soak tests reveal how behavior changes over time under sustained load.
  • Stress and spike tests explore behavior beyond normal conditions and recovery after overload.

None of these tests is valuable by default. Value comes from intent and clarity of risk. Maturity shows in choosing the smallest set of tests that address real risk.


Bottlenecks shape system behavior

Systems slow down at bottlenecks. Thread pools. Database connections. External dependencies. Shared locks. Blocking queues.

At low load, these limits are invisible. Under pressure, they define everything. Queues grow. Response time climbs. Errors appear. Rising latency does not always mean the system is out of capacity. Often it means one specific resource has become the narrow point.

Performance work is about finding that point, understanding why it exists, and deciding whether it should exist at all.


Metrics only matter in context

Numbers without context are noise. Response time alone explains little. Throughput without saturation hides limits. CPU utilization without understanding contention misleads.

Signals only make sense when connected. Latency with throughput. Errors with queues. Resource usage with concurrency.

Dashboards should answer questions, not decorate reports. Alerts should guide action, not create anxiety. The goal is not more data, but a coherent explanation of system behavior that supports decisions.


Measurement comes before change

A common failure in performance work is changing the system before stabilizing measurement. If test machines change, if environments scale differently between runs, or if client conditions vary, numbers lose meaning. Improvements and regressions turn into guesses.

Stable measurement is part of system design. Baselines, repeatable environments, and controlled variables matter as much as fixes. Without this stability, performance work becomes storytelling without evidence.


Root cause analysis is where learning compounds

Tools support thinking. They do not replace it.

Profilers, dumps, and traces help surface symptoms. Real root causes connect layers.

Application logic. Runtime behavior. Infrastructure limits. Workload shape.

The same patterns repeat across systems. Memory leaks. Lock contention. Slow queries. Misconfigured pools. This is where experience compounds fastest. Each investigation sharpens intuition for the next one.


Optimization is about trade-offs

Making something faster always costs something else. Caching adds complexity. Async execution complicates debugging. Indexes speed up reads and slow down writes. Scaling increases cost.

Optimization decisions are contextual. They are choices made under constraints, not universal improvements. At a senior level, optimization includes cost awareness and operational impact. Faster is not automatically better. Enough performance, delivered predictably and sustainably, is the real goal.


Reports exist to enable decisions

Reports are not archives. They are decision tools. Their purpose is to shorten discussion, clarify risk, and make next steps obvious.

Different audiences need different levels of detail. Executives need direction. Engineers need evidence. Mixing these goals in a single report reduces clarity for everyone.

Good reports do not explain everything. They explain what matters next.


Performance must live in delivery

Performance work that lives outside delivery pipelines is fragile. Checks belong where they provide fast feedback. Baselines to catch regressions. Focused load checks before risky releases.

Automation helps, but blind gates create false confidence. Every check must have meaning, ownership, and a clear response when it fails.

Performance becomes effective when it is continuous, visible, and expected.


System thinking changes the role

At some point, performance stops being something done after development. It becomes part of design. How systems scale. How they fail. How they recover.

Retries, timeouts, circuit breakers, and autoscaling are not performance features. They are controls over system behavior and failure modes. This is where performance engineering overlaps with architecture and reliability.


Culture is the real multiplier

No tool replaces shared understanding. Stable systems are built by teams that know their limits and talk about them openly.

Clear explanations, simple models, and repeated conversations matter more than perfect reports. When performance becomes part of everyday thinking, fewer problems reach production.


A note to my future self

Some parts of this text will age.

I may think differently about tooling depth, automation boundaries, or how much testing is enough. Cost models will change. Platforms will evolve.

What should not change is the core approach. Think in systems. Define boundaries. Look for bottlenecks. Connect signals. Question assumptions.

If this text still helps me reason clearly, even where I disagree with it, it has done its job.