A proposal for an internal tool serving 50 requests per second specifies a service mesh, distributed tracing, three databases, a message broker, distributed locks and multi-cluster deployment. What do you cut and what do you keep?
Show the full answer Hide the answer
What is being tested
Whether you can size infrastructure to a workload, and whether you can distinguish the elements that are cheap at any scale from those with a permanent operational cost.
The relevant number
Fifty requests per second is roughly 4.3 million requests a day. A single modest application server and a single well-configured relational database handle that with enormous headroom. Nothing in this workload requires distribution of any kind.
What to cut
Service mesh. It earns its cost with many services, several languages, a real mTLS requirement, and a platform team to own it. Here it adds two proxy hops per call, per-pod resource overhead, a control plane that is a new failure surface, and a class of debugging problem the team has never seen. If service-to-service encryption is required, TLS between a handful of services is a configuration change.
Three databases. Polyglot persistence is justified by genuinely different access patterns at a scale where one engine cannot serve them. At 50 RPS, one PostgreSQL instance covers relational, JSON, full-text and geospatial needs adequately. Three databases means three backup and restore procedures, three upgrade paths, three sets of expertise, and no transactions across them — plus whatever consistency mechanism now has to be invented to keep them agreeing.
Message broker. Unless there are genuinely multiple independent consumers of the same events, a database-backed job table is simpler, participates in your transactions (making the outbox problem disappear), and is trivially inspectable with SQL during an incident.
Distributed locks. These are a serious source of subtle bugs — a worker that pauses for a long garbage collection can hold an expired lock while another worker also holds it, and without fencing tokens the storage layer cannot tell. With one database, a row-level lock or a conditional update is correct and does not have that failure mode.
Multi-cluster. This is availability engineering for a workload whose users are internal and whose realistic requirement is probably "up during business hours".
What to keep
Not everything here is ceremony. These are cheap now and expensive to retrofit:
- Structured logging with a correlation ID. Costs an afternoon.
- Basic metrics — request rate, error rate, latency percentiles, and a small number of business counters.
- Infrastructure as code, so the environment is reproducible.
- Automated deployment with a rollback, and separate environments.
- Secret management and encrypted transit.
- Backups with a tested restore. Untested backups are not backups.
Tracing is a reasonable conditional keep: if the tool genuinely spans several services, a lightweight tracing setup is worth it. If it is one service, structured logs with a correlation ID give you the same answers.
How to make the argument
Not "this is over-engineered" — that is an aesthetic claim and it will be resisted. Ask for each component: what specific failure or requirement does this address, and do we have it? A mesh exists to manage connection concerns across many polyglot services. With three services in one language, there is no such problem to manage.
Then price the alternative honestly: every component is an on-call burden, an upgrade path, a security patching obligation, and a thing that must be understood by whoever inherits the system. For an internal tool, the total cost of ownership is very likely to exceed the cost of the problem it solves.
The senior framing
The most scalable choice for a small workload is usually the boring one, because the binding constraint is engineering attention rather than throughput. Complexity that is not paying for itself is consuming the capacity you will need when a real scaling problem arrives.