Modular feature performance testing
Modular feature test isolation provides the strategy for proving that a feature’s behaviour is correct through using contracts to define your boundaries. This page sets out the parallel strategy for performance: prove that a feature’s latency, throughput, and resource usage stay within agreed bounds at those same edges, catch regressions before merge, and keep watching after merge. It is the pattern Theseus steers every modular feature team towards, synthesised from the Performance Enablement team’s work on contracts, tooling, and observability-based testing.
The problem with existing load performance testing
GitLab today has strong load-testing tools, but they are focused on load testing the full system, not individual features. This leads to a number of problems:
- Feedback arrives late in the cycle. Tests can only be run when the full system is standing and working, and only after the feature has been merged.
- No contract, no shared language. Without a machine-readable target, “fast enough” is a conversation instead of a check. AI coding assistants and reviewers have nothing concrete to validate against.
- Tooling is tied to the monolith. As the monolith decomposes, every new service needs its own performance story. Rebuilding that story from scratch each time does not scale any better than rebuilding a functional test harness from scratch each time.
Load testing the full system is still necessary and important. A full system load test will uncover problems that no other test will, such as inter-service dependencies. However this is not the only approach.
Desired outcomes
- Earlier regression detection — every MR validated against a performance target.
- A shared, machine-readable target — one contract per service that humans, CI, and AI coding assistants can all validate against.
- A contract that outlives any one tool — the target stays stable even as the tooling that validates it continues to evolve.
- A closed loop — contract tests validate a point in time; observability and dashboards keep watching after merge.
The strategy
1. Define a performance contract at the same edges as the functional contract
The
bounded context
a modular feature establishes for functional testing is also the right
boundary for performance. A performance.yaml contract, co-located in the
service repository, encodes latency, throughput, and resource targets for
that boundary — the same spirit as the fakes in the functional strategy: a
concrete, checkable definition of “correct” for the feature’s edges, this
time on the performance axis rather than the behavioural one.
A contract covers, at minimum:
- Backend endpoint categories — routes grouped by expected load shape,
each with
latency_p95_ms,latency_p99_ms, and an error-rate threshold. Performance tiers give starting-point defaults for common archetypes (fast reads, standard reads, writes, Git operations) so teams tune from a baseline instead of guessing. - Resource budgets — memory, CPU, and connection-pool limits for the test environment.
- SLI mapping — the Prometheus metric names and labels a service emits via LabKit v2, so tooling can locate the right time series without reading source code. A service already on LabKit v2 needs zero extra instrumentation to adopt a contract.
The full schema, worked examples, and current milestone status live in Performance Testing for Modular Features.
2. Let the contract stay tool-agnostic
A modular feature’s performance surface is not one workload. The same service has backend endpoints, maybe a frontend, and hot paths worth catching at the unit level — no single execution engine covers all three. The contract is deliberately written to the target, not to a tool: it states the latency, throughput, and resource budget a feature must meet, and leaves the choice of what exercises that budget to whatever fits the workload being validated.
That separation matters for the same reason isolating dependencies behind configurable clients matters in the functional strategy — it keeps the strategy stable while the tooling underneath it is still settling. Component Performance Testing is the closest performance analogue to the fakes-and-Caproni loop: it runs at the MR level, against a transitory environment, scoped to the feature rather than the whole system. It is one of several valid ways to validate a contract, not the only one.
Which tool fits which workload — backend load, page-load budgets, or unit-level benchmarks — is a tooling decision, not a strategy decision. The Performance Testing Tool Selection Guide covers that choice in full.
3. Close the loop with observability after merge
A contract test proves the feature met its budget at one point in time, under synthetic load. It does not replace watching how the feature actually behaves once real traffic arrives. Dashboards — built from the same LabKit v2 metrics the contract’s SLI mapping references, and co-located with the service rather than managed centrally — give teams a standing view of production performance trends, so a slow drift gets caught before it becomes an incident rather than after.
The Observability Based Performance Testing approach — dashboard review as a habit, exploratory analysis across services, and tools like the Performance Bar for spotting oddities — is what fills the gap between contract runs: it does not replace load testing, it covers what load testing structurally cannot, which is real-world traffic shape.
Why this works
- One contract, many checks. The same
performance.yamldrives MR-level checks, frontend budgets, and the SLI mapping that dashboards key off — no separate performance story to maintain per tool. - Feedback moves earlier. MR-level checks give a signal before merge, broader load tests validate at scale before a release, and dashboards watch continuously after. A regression has multiple chances to be caught before it reaches a customer.
- It is AI-legible by design. A machine-readable contract lets Duo and other coding assistants reason about latency budgets and resource constraints the same way they reason about a functional contract’s shape — see the AI integration section of the contract docs.
- It scales the same way functional isolation does. Each modular feature owns its contract and its dashboards. Adding a hundredth feature does not require a hundredth bespoke performance-testing setup.
Trust but verify
Full-scale, full-system load tests against GPT still have a place — reference architecture validation and release-cut checks are not things a single component’s contract can stand in for. The strategy makes per-feature, per-MR checks the default early-warning system, not a replacement for that broader validation.
#g_performance-enablement before adopting either
on a modular feature today, and expect this page’s specifics to move as that
work lands.