Zero-Deficit Engineering: Treat Trade-offs as Design Questions
The phrase “engineering trade-off” can end a design discussion before the evidence supports it.
More throughput must weaken ordering. Reliable data movement must require more infrastructure. Bounded memory must cause data loss or force an unbounded source stall.
Each statement can be true for a specific design. None is a universal law.
Compromise is sometimes necessary. The problem starts when a team presents a current design choice as an unavoidable constraint. The team then ends its search. A local limitation becomes an architectural rule, and the next system inherits it.
I developed Zero-Deficit Engineering as I built Remac, a high-performance transaction log processor. The work forced me to examine a pattern in software systems. We often accept a conflict between two valid objectives because the current architecture cannot satisfy both.
The architecture limits the current design. We then mistake that design limit for a limit of the problem.
Zero-Deficit Engineering starts before that mistake becomes permanent.
Zero-Deficit Engineering treats each claimed trade-off as a design question before it accepts the trade-off. It changes the architecture, data model, execution method, or resource use to remove avoidable deficits. It accepts physical limits. It rejects deficits caused only by inherited design.
Systems still have finite speed, cost, and operating limits. Zero-Deficit Engineering requires a precise account of what the system must do, what prevents it, and what evidence supports that limit.
Define the deficit first
A team cannot remove a deficit that it has not defined.
At the start of an investigation, the team does not yet know whether a hard constraint requires the observed gap.
A candidate deficit is a measurable or observable gap between a declared engineering objective and a design result.
The candidate status remains while the team investigates the claimed constraint.
A deficit is a candidate deficit that a feasible design can remove under the same declared objectives, system boundary, operating conditions, and applicable resource limits.
If a verified fundamental, resource, or boundary constraint requires the gap under the declared operating conditions, the gap is a limit. A decision can justify accepting a deficit for a release, but it does not make the deficit technically unavoidable.
If the evidence establishes neither a feasible design nor a required limit, the gap remains a candidate deficit.
The deficit definition has four parts.
First, the team must declare the engineering objective. “Make it fast” is too vague. “Process at least 50,000 events per second on this machine” gives the team a testable boundary. “Preserve source commit order at each required sink” states a separate correctness objective.
Second, the result must be observable. A benchmark can measure throughput. A fault test can verify recovery. A memory profile can show growth. A deployment record can count external processes and network hops.
Third, the gap must be clear. If the design reaches the throughput target but removes ordering, the loss of ordering is the candidate deficit. If it preserves ordering but misses the throughput target, the throughput gap is the candidate deficit.
Fourth, evidence must show that a feasible design can remove the gap under the declared conditions. If the evidence establishes neither a feasible design nor an unavoidable limit, the gap remains a candidate deficit.
The word “declared” matters. Every system has many desirable properties, but a release cannot optimize an unlimited list. The team must state the objectives for that system and release. The team cannot omit a requirement that the product’s stated behavior or its users depend on.
If a decision changes an objective, record what changed, why it changed, and who approved it. A team cannot claim zero deficit by deleting an unmet objective.
Declared objectives can include correctness, latency, throughput, bounded memory, recovery time, deployment cost, operator work, and energy use. Different systems need different objectives.
A design has zero deficit only relative to its declared objectives, system boundary, operating conditions, and verified constraints. It cannot claim zero deficit while a candidate deficit remains unresolved.
Classify the claimed constraint
Trade-off discussions become confusing when they treat every limit as the same type of fact.
Four classes help separate hard limits from current decisions.
| Constraint class | Examples | What the claim can establish |
|---|---|---|
| Fundamental | A mathematical impossibility, an established lower bound, or a physical relationship that does not depend on the selected deployment | A result can be unavoidable when the system matches the proof’s assumptions |
| Resource | The configured memory, CPU, storage, bandwidth, or power of the actual system | The current system has a finite capacity, but the selected capacity is not universal |
| Boundary | An external protocol, API, source, sink, compatibility rule, applicable law, regulation, or binding contract | A limit exists outside the current design authority and can change only when the boundary or obligation changes |
| Decision | Budget, delivery date, supported environment, or user-selected policy | An authorized choice can justify accepting a deficit for a release. It does not establish technical impossibility |
The first class gives the strongest basis for a claim of inevitability. Even then, the assumptions control the conclusion.
The Fischer, Lynch, and Paterson result gives one example. The original paper shows that no deterministic consensus protocol can guarantee termination in every admissible execution of its fully asynchronous model, even if one process can crash. The result depends on the paper’s model and correctness conditions. It does not apply to every timing or failure model.
The proof and its scope are both important.
Resource limits need the same precision. Finite memory is a hard constraint. A decision to use one host with 16 GB of memory is a current capacity choice. The team can change the data structure, reduce retained state, spill to durable storage, add capacity, or reduce the workload.
An external API can impose a real boundary. An applicable law, regulation, or binding contract can also impose a boundary outside the design authority. These limits remain until the team changes the integration, system boundary, obligation, or applicable scope.
Budget and deadline constraints are valid. Engineers work inside them every day. They support statements such as, “We will ship the serial path in this release.” They do not support, “Parallel correctness is impossible.”
The language must match the evidence.
Match the evidence to the claim
A trade-off can feel obvious and still be wrong. Familiarity is weak evidence.
Zero-Deficit Engineering uses evidence that matches the claim. No single evidence type answers every engineering question.
An established theorem or formal proof can establish an impossibility or lower bound inside a model that matches the system. It cannot establish measured throughput on a deployed system.
A direct resource calculation can establish a capacity limit. A 10 GB queue lasts about 100 seconds if its backlog grows by 100 MB each second. Scheduling can change the observed duration. It cannot make finite storage infinite.
A reproducible benchmark can support an observed result under stated conditions. It must identify the workload, environment, method, duration, repetitions, and variation. A comparative claim also needs a baseline. An objective test also needs a pass condition.
A failed design experiment shows that one design did not meet the objective under the test conditions. It does not prove that all designs must fail.
A cost model can support a decision when it states the prices, workload, labor assumptions, and period. A different price or deployment can change the result.
An external specification can establish the declared behavior of a source, sink, service, or protocol. It cannot establish a limit beyond that boundary.
This evidence standard changes common engineering language.
“We tried parallel writes and ordering broke” becomes, “The tested parallel-write design did not preserve order under the stated workload.”
“Correctness requires one worker” becomes, “This scheduler can prove order only in its one-worker configuration.”
“Recovery needs a full resynchronization” becomes, “The current checkpoint model cannot identify a safe restart position.”
The revised statements are longer. They are also useful. Each one identifies a design problem that the team can investigate.
A moved deficit still exists
Local optimization can improve one metric while the complete system misses another declared objective.
A pipeline can use an unbounded queue to increase its measured intake rate. The metric improves until memory fills.
An API can return faster if it acknowledges work before durable storage. Latency improves, but a restart exposes the recovery gap.
A service can remove a local process if it sends the work to a large external cluster. The local deployment becomes smaller. Infrastructure cost and operator work move elsewhere.
A data pipeline can preserve its input rate if it stores an unlimited backlog in the source database. The processor meets its local metrics. The source accumulates storage and recovery risk.
Zero-Deficit Engineering uses the complete system boundary. A design removes a deficit only if the original gap disappears without a new deficit elsewhere.
This rule does not require every resource metric to stay fixed. A design can use more memory, storage, or compute inside its declared limits. The team must show the full accounting.
The question is simple:
Which objective improved, which resource use increased, and did that increase create another deficit?
Fix the complete path, not one component
One example came from how Remac handled TRUNCATE during earlier development.
During earlier Remac development, the PostgreSQL source decoder already emitted TRUNCATE as a transaction log event, and the pipeline retained it as a transaction operation. The earlier PostgreSQL sink converted inserts and updates into upserts and handled deletes separately. Since it had no application path for TRUNCATE, the operation did not change the target database.
This was a completeness and consistency deficit. The processor could deliver every row change around the operation while the source and destination still reached different final states.
Adding a TRUNCATE statement to the sink was necessary, but it was not sufficient. The processing pipeline sent independent row changes through concurrent writers. A table-wide operation could therefore overlap with a later row change.
Assume that each statement runs in a separate transaction. Each transaction commits before the next statement starts.
Consider this source commit order:
INSERT INTO accounts (id) VALUES (1);
TRUNCATE TABLE accounts;
INSERT INTO accounts (id) VALUES (2);
PostgreSQL’s TRUNCATE command takes an ACCESS EXCLUSIVE lock, so these statements cannot overlap at the source. The ordering risk is created when the apply path reorders committed operations.
The correct final state contains row 2 and does not contain row 1.
With concurrent work, the last insert can finish before TRUNCATE. The delayed TRUNCATE then removes row 2. The processor can handle all three operations and still produce the wrong final state.
One safe response is to apply every operation serially in source commit order. That response preserves the table state, but it removes concurrency from ordinary row work. Ignoring TRUNCATE avoids the coordination cost but creates an integrity deficit. If TRUNCATE is part of the declared product contract, declaring it unsupported narrows that contract instead of solving the original deficit.
Remac changed both the processing layer and the PostgreSQL sink.
At the processing layer, the coordination rule is scope-aware. It identifies the affected table or table set, pauses only conflicting writes, and lets independent table work continue concurrently.
The PostgreSQL sink provided a second ordering boundary. It detected TRUNCATE before normal row reconciliation. It then opened a target transaction, applied earlier row operations, executed TRUNCATE at its position, applied later row operations, and committed the result.
This second boundary prevented the sink’s batching and reconciliation work from removing or moving the table-wide operation.
The design followed a deliberate sequence:
- Keep ordinary row changes on the normal concurrent path.
- Detect a transaction containing
TRUNCATEbefore normal row partitioning. For a large staged transaction, detect it while reading the staged events. - Pause only writes that conflict with the affected table or table set. Work for independent tables continues concurrently.
- Send the
TRUNCATEtransaction through one writer without splitting it by row key. - In the PostgreSQL sink, apply row operations before
TRUNCATE, executeTRUNCATEat its source position, and then apply later row operations inside one target transaction. - Complete the barrier after the table-wide operation, then return later work to the normal concurrent path.
This design kept ordinary row processing concurrent while introducing a temporary ordering boundary around the operation that required it. The processor and the sink both participated because either layer alone would leave part of the deficit unresolved.
As Remac’s architecture evolved, the lesson remained the same. A system does not remove a deficit until every component that controls the result follows the same objective.
Some limits remain
Zero-Deficit Engineering rejects avoidable deficits. It must also accept real limits.
A bounded backlog gives a clear example.
Assume a producer creates 100,000 work items each second. Its workers can complete 80,000 items each second. The backlog grows by 20,000 items each second while those rates remain unchanged.
Assume the net rate produces 100 MB of queued data each second. An 8 GB buffer fills in about 80 seconds while that rate remains unchanged. A detailed model can include rate variation, retries, item size, storage overhead, and work bursts. None of those details removes the sustained difference between production and service rates.
A durable queue can improve the design. It can move queued work out of memory, increase the outage window, and support controlled replay.
It still has finite capacity. The durable queue changes the size and location of the buffer. It does not make an endless rate mismatch disappear.
Eventually, one condition must change. The workers catch up. The producer slows. Capacity increases. Storage fills. A declared policy rejects work, drops work, or stops the system safely.
The backlog calculation above uses the difference between arrival and service rates. It does not use Little’s Law.
The usual steady-state form of Little’s Law does not apply here because the backlog grows without limit while those rates remain unchanged.
A zero-deficit response defines the bound and the applicable policy:
- Bound memory and storage.
- Measure the production and service rates.
- Define what happens at the bound.
- Select the policy explicitly.
- State which declared objective the policy changes.
If the operator selects a loss policy, that policy makes the loss explicit. If the declared objectives forbid loss, the system must apply backpressure, add capacity, or stop safely.
The limit remains. The ambiguity does not.
Know when to stop
A discipline that rejects trade-offs can become an excuse for endless redesign. That result would create its own delivery deficit.
Work can stop for a release when:
- Each objective has a testable pass condition.
- The evidence shows which conditions pass.
- Every unmet condition is recorded as a candidate deficit, an accepted release deficit, or a verified limit.
- The change did not hide or transfer a deficit.
- Each remaining gap has an owner and a review trigger.
- The claims made for the release match the evidence.
This rule permits future work. It does not permit false completion claims.
A team can ship a serial implementation when it meets the declared release objectives. The design record must identify its remaining limit and review condition.
A team can choose a lower-cost architecture for an early release. The resulting gap is an accepted release deficit. It must not become a permanent technical law through repetition.
The release rule protects both sides of the discipline. It prevents premature compromise, and it prevents endless optimization.
What Zero-Deficit Engineering adds
Engineers have challenged apparent trade-offs before. TRIZ examines technical contradictions. Axiomatic Design seeks independence between functional requirements. ATAM evaluates software architectures against competing quality attributes.
Zero-Deficit Engineering shares this broad instinct but applies a specific burden of proof. It organizes the work around explicit deficits, verified constraints, complete-system accounting, and release rules. Before a team classifies a candidate gap, it must test the claimed constraint and search for a feasible design. It must also inspect the complete system for deficit transfer and state when the evidence is sufficient for release.
Use the method
The method starts with a sentence:
We want objective A and objective B, but design D creates candidate deficit X because of claimed constraint C.
Write each part before you propose another component.
1. Declare the objectives
Use conditions that a test, measurement, or review can evaluate.
Weak:
The pipeline must be fast and correct.
Useful:
The processor must preserve source commit order at each required sink. It must also process work concurrently when it can prove that the work is independent.
2. Locate the candidate deficit
Name the observed gap without deciding whether it is removable or required.
The tested design can apply a table-wide operation concurrently with row writes for the same table.
3. Classify the constraint
Ask whether the claimed constraint belongs to one of the four classes:
- A theorem or physical limit.
- A finite resource.
- A boundary imposed by a source, sink, protocol, compatibility rule, law, regulation, or contract.
- A budget, delivery date, supported environment, or user-selected policy.
If no class explains the conflict, inspect the current architecture. An architectural limitation is a design problem, not proof of inevitability.
4. Test the claim
Build the smallest test that can reject the assumption. Record the initial result and the failure condition. Use a baseline when the test compares alternatives.
If one experiment fails, record the scope:
This design failed under these conditions.
Do not replace it with:
The objective is impossible.
5. Change the design space
Consider changes to:
- Architecture.
- Data representation.
- Work scheduling.
- State ownership.
- Confirmation rules.
- Hardware use.
- The system boundary.
The worked design changes the execution path around TRUNCATE. Ordinary row work remains concurrent, while the table-wide operation receives a temporary ordering boundary.
6. Account for the full system
Measure changes in memory use, compute use, storage, network traffic, recovery risk, and operator work.
The best local result can still be a system deficit.
7. Record the result
State:
- Whether the gap is a candidate deficit, deficit, or limit.
- The constraint.
- Its class.
- The evidence.
- The assumptions.
- The selected policy.
- The review trigger.
This record ensures that a temporary decision does not become an inherited law.
Trade-off is the start of the question
Some trade-offs survive investigation. Their constraints are real, their assumptions match the system, and the evidence holds.
Others disappear when the design space changes.
The engineer’s task is to tell the difference.
Before a team accepts a loss, it should identify the candidate deficit, name the claimed constraint, and test the evidence.
A feasible design establishes a deficit. A verified constraint establishes a limit. Otherwise, the gap remains a candidate.