The Operator's Mindset: How I'd Make an App Survive Its Own Success
A from-scratch observability and operations playbook for the moment your app actually gets users.
By Michael Yuanhao Deng· August 30, 2026· 9 min read
Most engineering advice is about how to build. This one is about how to not lose everything you built the week your app finally takes off.
I work as an infra and devops engineer at my day job. That job rewires how you look at software. Builders think about features. Operators think about the system metrics, the cascading failure, and the customer who quietly leaves because your thing was down when they needed it. Both mindsets matter, but almost nobody writing "ship fast with AI" content talks about the second one until it's too late.
Here's the uncomfortable truth: scale is not a trophy, it's a liability you take on. If you have real users on a critical workflow, one outage is painful, and frequent outages are fatal — not because of the downtime itself, but because trust doesn't come back. Once a customer decides your product is unreliable, winning them back is much harder.
So this is the piece I wish someone had handed me: if I were building an app from scratch today and knew it was going to scale, here's exactly what I'd do from a pure observability and operational-robustness standpoint.
The operator's mindset
Build your product so you can observe and monitor every behavior it produces — and so when things go down, you have simple, reliable ways to reset, recover, and get back to a known-good state.
On top of that, you should stay vigilant, and be safe. "Safe" means two concrete things: have a backup for everything, and try your best to prevent problems before they happen. That's still vague, so let me make it painfully specific. The rest of this post is a from-scratch playbook, roughly in the order I'd build it.
1. Log every request like you'll have to debug it blind
Your backend servers are the most error-prone part of the system. UI bugs usually have workarounds — users grumble and click somewhere else. Backend logic bugs block people. Nobody gets through.
The single highest-leverage thing you can do is stream everything into a monitoring tool: application error logs from inside your backend functions, plus HTTP request logs. Datadog and Sentry are the obvious picks. Ask your coding agent how to wire it up — this is a solved problem and the setup is mostly boilerplate.
But the setup detail that actually matters is structure. Every error log needs:
- The function name or API endpoint path
- Business context —
userID, tenant, request ID, the entities involved
The rule of thumb: in any given code path, use the same core context identifiers on every log line so you can reconstruct what happened. If the function runs inside a chat session, include sessionID on every log from that function — that’s what lets you trace the story end to end.
The principle to aim for: when an HTTP request comes in, you should be able to trace the entire call chain, from the entry point down to the individual DB query. The fancier your monitoring stack, the easier this is. But you don't need fancy. A vanilla version — stream everything into something like OpenSearch and point your coding agent at it to reconstruct the call chain — works completely fine.
2. Log async jobs the same way
Background jobs are backend code that nobody's watching in real time, which makes good logs more important, not less. When a job starts, log the job type, the business context around it, and then the same per-function traces and errors you'd capture for a request. Same discipline, same payoff. A silent job failure is the kind of bug that shows up three days later as an angry support ticket.
3. Monitor requests: load, latency, saturation
Logs tell you what happened. Metrics tell you the pulse. Your main goal here is to get a baseline for what's normal. For the sync request paths, watch:
- How many requests are currently hitting each server, and what are the error rates for each endpoint?
- Average and — more importantly — P95 response time
- Server load: CPU utilization and memory usage
- To what point (system throughput) does the above metrics starts degrading drastically?
Tail latency is the one people skip. Your average can look great while 5% of users get responses that are 10x slower. Users don't experience your average; they experience their own request. Watch P95 (and P99 if you can).
4. Monitor async jobs: build a baseline, then get paranoid
If I were doing this from scratch, I'd start simple: a Postgres queue with a fleet of dedicated workers (Modal sandboxes work well), or a service like Inngest that maintains the queue for you and calls an endpoint in your own server to run the job. For most early-stage products under a few thousand jobs an hour, a Postgres-backed queue removes an entire infrastructure dependency — no Redis, no separate broker. You outgrow it eventually, but "eventually" is further away than people think.
If jobs run in the same place as your server, apply all the caution from above — but add one more habit: build a baseline of how long each job type takes on average, and get suspicious when a type starts running long. A job suddenly taking 3x its normal time is often the first visible symptom of an overloaded server. Beyond runtime, watch CPU and queue depth. You need enough workers to actually drain the queue. And if you're multi-tenant, distribute processing power fairly across customers — otherwise one heavy tenant starves everyone else, and the quiet customers churn without ever telling you why.
5. Monitor the database
Your DB is the shared resource everything else leans on, so watch it closely:
- Slow queries
- CPU
- Read/write latency
- Read/write IOPS
- Storage usage
Most production incidents I've seen eventually touch the database. It's the common dependency, which makes it the common failure point.
6. Incident response: kill switch first, root cause second
Real incidents are rarely one clean thing. It's usually a chain: an innocent burst of requests or webhook events triggers an avalanche, one strained resource strains a dependent resource, and it snowballs.
When production is already down, resist the urge to be clever. Flip a kill switch first. Think of it as "what would happen if the whole system rebooted?" — and make that easy to trigger on purpose. Get production back to a known-good state, buy yourself breathing room, then hunt the root cause with your logs.
A kill switch means different things for different systems. For my company, it's two moves: kill all long-running transactions in the DB, and literally restart every EC2 server. Boring, blunt, effective. The goal isn't elegance — it's getting your users unblocked while you think.
When production is down, elegance is the enemy. Reset to a known-good state first, diagnose second.
7. When to autoscale: run a redline test
Don't guess your server's capacity. Measure it with a redline test: push load at a single instance until P95 latency starts climbing sharply. That inflection point is the traffic baseline one instance can handle.
Two ways to generate the load:
- Skew your load balancer to send more weight to the instance under test, or
- Emulate realistic traffic with a load-testing tool like k6.
k6 is the de facto open-source choice here (over 90 million Docker pulls) precisely because it makes this cheap to do. Once you know one instance's ceiling, you can estimate the incoming-request threshold where you need to add another — and wire that threshold into your autoscaling instead of scaling on vibes. Bonus move: put a P95 threshold in CI (e.g. fail the build if P95 > 500ms or errors > 1%) so performance regressions never reach production in the first place.
8. Build a control center
Once you have logs and metrics, stop hunting for them across five tabs during an incident. Build one internal control center for your app. Mine would have:
- Core metrics — each subsystem's health and the numbers you care most about
- DB slow queries
- Every HTTP endpoint with its error rate and P95 latency, each linking straight to the traces for its failed requests
- Kill switches — one-click server resets
- Release versions — so you can instantly correlate "it broke" with "we deployed"
- DB connections — current count, connection limit, hot table sizes
- Jobs — failed jobs, with links to the traces for each failure
The whole point is that during an incident you look at one screen, not ten dashboards, and every number is one click from the trace that explains it.
9. The endgame: outsource operations to an agent
Here's the part that's actually new. You do not want to sit in front of dashboards all day watching for fires. You want to be talking to customers and building. So the ultimate goal is to hand day-to-day observability and operations to an agent.
Most of this is thinner than it sounds — it's mostly wrapping the APIs of tools you already pay for (Datadog, Sentry, your cloud provider). You can build a small tool surface, or wrap it into a composite Claude skill that teaches Claude Code how to reach those resources. Give the agent:
- Tools to query logs and the DB
- Tools to query system metrics
- Tools to trigger CI/CD pipelines
- Tools to manage infrastructure — autoscaling settings, server restarts, terminating instances, DNS, the operational stuff
Now your first responder is an agent that can pull the traces, correlate them with the last deploy, and either fix the problem or hand you a tight summary instead of a wall of logs. That's the leverage: you built the observability once, and now something else watches it for you.
A note on scaling, from first principles
Strip it down and almost every app has two kinds of work: sync (requests) and async (jobs). To handle scale, you need infrastructure that can autoscale horizontally on both. There are two patterns:
- Pattern 1 — separate fleets. Process jobs in a dedicated pool of workers and scale that pool as job volume grows; process requests in a separate pool of long-running workers and scale that as traffic grows. Clean separation, more moving parts.
- Pattern 2 — one instance type does both. Handle requests and jobs on the same server instances and scale on a combination of CPU, in-flight jobs, and incoming request count.
Personally I lean toward Pattern 2. You don't have to babysit a separate worker fleet, and you scale on one blended signal. Fewer things to operate is itself a reliability feature.
And if you want the simplest possible autoscaling story: host on serverless functions and let the platform scale for you. Supabase functions have excellent cold starts, with Modal and Lambda right behind. You trade some control for not having to think about instance counts at all — a great deal when you're small and would rather spend that attention on customers.
The takeaway
Building the app is the fun half. Keeping it alive when people actually depend on it is the half that decides whether you have a business. The operator's mindset — backup everything, prevent what you can, reset fast when you can't, and eventually let an agent hold the pager — is what turns "we got users" from a threat into a foundation.
Start with logs. Add metrics. Build the control center. Hand it to an agent. In that order.