Security and performance are often reviewed in separate meetings. One group draws trust boundaries. Another estimates traffic and database load. In practice, the same design choices create both kinds of failure.

A public endpoint without a useful rate boundary can become an abuse path and a capacity problem. A synchronous provider call can leak sensitive failure details while tying up every request worker. A broad database query can expose another tenant's rows and collapse under production volume.

Reviewing the boundaries together produces better engineering questions.

Start with a concrete user journey

Choose a journey that matters: create an account, submit a payment, generate a report or import a file. Draw the components and data stores it crosses. Mark where identity is established, where permission is checked and where a third party becomes authoritative.

Then add workload facts:

The diagram becomes useful when every arrow has a trust assumption and a volume assumption.

Look for amplification

An inexpensive incoming request can trigger expensive downstream work. Examples include generating a PDF, asking an AI model to process a large document, sending many notifications or running an unbounded database search.

Ask what limits the input, who pays for the downstream call and whether the action can be repeated concurrently. Authentication alone is not a complete control; a valid low-cost account may still create a large bill or exhaust shared capacity.

Bound payloads, apply per-identity quotas where appropriate and move durable work onto a queue. Keep the customer-visible status truthful while the job runs.

Treat retries as part of the design

Timeouts do not tell the caller whether the remote system completed the action. Blind retries can create duplicate charges, reports or messages.

Use idempotency keys for actions that must happen once. Persist enough state to distinguish “not started,” “in progress,” “completed” and “failed in a way that can be retried.” This improves resilience and removes a class of business-logic security errors.

Backoff and jitter protect dependencies during an incident. A retry storm is both a reliability failure and an easy way for a small attack to gain amplification.

Keep tenant isolation efficient

Authorisation checks that require loading a large object graph are easy to skip in performance-sensitive code. Design queries so tenant and resource constraints are expressed together and supported by indexes.

The correct query should also be the efficient query. If every request loads a record and then performs several membership lookups, measure the path and consider a model that makes the boundary explicit without weakening it.

Cache carefully. A cache key that omits tenant, role or permission context can turn a performance optimisation into cross-account data exposure.

Define failure modes before load tests

Load testing without pass criteria produces attractive graphs and little confidence. Write measurable non-functional requirements:

Include security behaviour under load. Rate limits should fail predictably. Authentication and authorisation should not be bypassed through a “fast path.” Logs should remain useful without recording sensitive payloads or creating unbounded cost.

Finish with decisions, not diagrams

A practical architecture review should leave a threat model, workload model, important failure scenarios and an ordered set of decisions. It should say what to measure and which assumptions remain untested.

Run this review before a large implementation or migration. A queue boundary, cache key or tenancy model is still cheap to change while it remains a design decision.