Know when not to add infrastructure
Use explicit latency, availability, isolation, operability, and cost requirements to decide whether new infrastructure is justified.
When Not To Add Infrastructure
Senior system design is often about declining complexity. Infrastructure is justified only when it buys a real property: lower latency, higher availability, stronger isolation, better operability, or lower cost at scale.
The default should be to improve the current system until there is evidence that a new component changes the operating envelope. New infrastructure is not neutral. It creates deployment paths, dashboards, credentials, capacity limits, ownership questions, and failure modes.
Do Not Add It Yet If
The current system has no measured bottleneck. A simpler database/index/query change solves the problem. The team cannot operate the new component. Failure modes get worse than the problem being solved. The product requirement is still unstable. The "scale" is hypothetical and cheap vertical scaling remains available.
Before adding a queue, cache, search cluster, stream processor, service boundary, or orchestration layer, name the property it must provide.
Proposed Component Property It Should Buy Evidence To Ask For --- --- --- Cache Lower read latency or database load. Hot keys, query latency, cacheability. Queue Isolation from slow or retryable work. Request timeouts, retry needs, async tolerance. Search index Better retrieval semantics. Query patterns SQL cannot serve well. New service Independent scaling or ownership. Clear API, team boundary, release pressure. Multi-region Availability or latency requirement. User geography, RTO/RPO, data constraints.
If the evidence is weak, keep the design reversible. Make the module boundary cleaner, add instrumentation, and defer the infrastructure decision.
Temptation Simpler First Move --- --- Kafka for every async action Start with a task queue or outbox. Microservices for ownership Start with module boundaries and clear APIs. Redis for every read Add indexes, fix queries, then cache hot paths. Kubernetes for a small app Use a managed platform or simpler deploy. Global multi-region writes Start with single-primary plus read replicas unless product requires active-active writes.
Add the missing index and verify the query plan. Move slow side effects behind a local job table or outbox. Add idempotency keys before adding distributed retries. Split code modules before splitting deployable services. Increase instance size while measuring whether the bottleneck moves. Add backpressure and rate limits before scaling every worker pool.
These moves are not anti-scale. They preserve learning speed while the product and traffic shape are still changing.
Infrastructure becomes justified when the system has a repeated, measured problem that simpler changes cannot fix. Good signals include saturated database resources after indexing, user-visible latency from slow dependencies, operational need for durable retries, clear team ownership boundaries, regulatory isolation requirements, or a reliability target that the current topology cannot meet.
At that point, the design should include the new component's failure behavior. A cache must define fallback. A queue must define retry, dead-letter, and ordering semantics. A new service must define API ownership, observability, and deployment compatibility.
The team adds a cache but still cannot tolerate stale reads. A queue hides failures until work piles up silently. A microservice split turns local transactions into partial failures. A stream processor becomes the only source of truth by accident. A platform migration absorbs engineering time without improving the product constraint.
Every new infrastructure component adds an on-call surface. If no one can debug it at 3 AM, it is not a free abstraction.