While auditing data-driven and AI-heavy companies, the same pattern keeps showing up.

They're excited. Proud, even. There's always a demo of impressive dashboards, clever automation, systems that react to the world in near real time. Data is very much the point. Performance problems aren't part of the demo.

Early in the conversation, I start asking very simple questions. Where does data come in? What happens to it? Who owns that code?

Somewhere in those answers, I hit my first red flag.

I notice how lightly data processing is described. It's there. It works. The how rarely gets the attention it deserves.

A little later, I hit the second red flag.

Nothing dramatic. No obvious mistake. Just a small detail in how responsibilities are described, who touches what, and where changes tend to accumulate.

At that point, I already know two things they haven't said out loud yet. Performance issues will surface, or they already have. Delivery will slow down, and the backend will quietly become the bottleneck.

This is usually the moment, often well within the first hour, where companies start treating me like some kind of oracle.

Not because I've seen their monitoring dashboards. Not because I've read their code.

But because architecture leaks.

And once you've learned how to listen for it, it's very hard to ignore.

The illusion of one system

On the surface, everything looks simple.

There's a web application. It serves users. It talks to a database.

That's the system people describe. That's the diagram that gets drawn. One backend, one database, one mental model.

But that model only supports synchronous work.

The moment a data-driven company starts ingesting real-world input, a second kind of work appears.

Emails arrive. Webhooks fire. Files get uploaded.

Let's take a concrete example: a smart email parser.

Every incoming email triggers asynchronous processing. It needs to:

  • load configuration from the production database
  • parse and transform content
  • apply AI and/or business rules
  • write the resulting state

This work does not run in response to a user waiting on a page load. It runs when data arrives, at its own pace, sometimes in bursts, sometimes continuously.

That difference matters because synchronous and asynchronous work compete very differently for the same resources.

Architecturally, however, this asynchronous work often ends up treated exactly the same as synchronous user requests. Because of simplicity.

Same backend. Same runtime. Same database. Same connection pool.

Everything lives together. Allegedly.

And that's where the illusion starts to crack.

The organisational cost of one system

The second red flag has nothing to do with velocity charts.

It shows up in how teams are structured.

In data-driven companies, the asynchronous side of the system almost always gravitates toward Python. That's where the data tooling lives, where parsing happens, where models run. Frontend profiles rarely operate there, and most don't want to.

So the asynchronous workload ends up owned by a specific group of engineers.

Now here's the subtle part.

Because the synchronous web application and the asynchronous data processing live in the same backend, that same group is suddenly responsible for both.

Not frontend versus backend. Not React versus Python.

But sync versus async.

At that point, one team is effectively supporting two systems.

One backlog is driven by user-facing features and product changes. The other is driven by data ingestion, processing, correctness, and reliability.

Both matter. Both evolve constantly. Both touch the same codebase and the same database.

So every change competes with every other change.

That's when delivery slows down.

Not because anyone is underperforming, but because the system shape forces a constant trade-off between two very different kinds of work.

This is also why "just hire more backend engineers" rarely helps.

The problem isn't capacity. It's coupling.

As long as synchronous and asynchronous concerns live together, the engineers responsible for the async side are permanently on the critical path. They're supporting the web application and the data system at the same time.

In practice, that means a double backlog.

There's a simpler alternative that often gets missed.

Instead of splitting teams by technology, split responsibility by workload.

A web application can be owned end-to-end by engineers focused on synchronous, user-facing work. In many cases, that can be a single fullstack profile responsible for both frontend and the web backend.

The asynchronous data side then becomes its own concern, owned by engineers focused on ingestion, processing, and data correctness.

Same product. Different workloads. Clear boundaries.

When that separation doesn't exist, delivery friction isn't a surprise.

It's the second red flag doing exactly what it always does.

The problem with one system

At this point, the problem should look familiar.

It's not the email parser. It's not Python. It's not the database.

It's the assumption that all work in the system is the same.

User-facing requests and background processing have fundamentally different expectations.

One is latency-sensitive. The other is throughput-oriented.

One needs fast, predictable responses. The other needs time, retries, and room to breathe.

When you force both into the same architecture, you don't get simplicity. You get contention instead.

And that contention shows up exactly where you'd expect:

  • in performance
  • in delivery
  • in team dynamics

This isn't a data problem. It's a workload problem.

Isolation is not overengineering

Simplicity is still a good instinct.

Especially early on, founders are right to resist complexity. One system feels easier to reason about. Fewer moving parts. Fewer things to deploy. Fewer things to explain. Fewer things to worry about. For now.

And for a while, that instinct is rewarded.

The problem is that simplicity and uniformity are not the same thing.

When you treat fundamentally different kinds of work as if they belong together, you're not simplifying. You're deferring complexity until it shows up at the worst possible moment. Usually in production, on a Friday at 5 pm.

This is where I often hear the same concern:

"Isn't this overengineering for where we are right now?"

Two execution paths. Two architectural concerns. Two very different problems. It sounds like something you do later, when you're bigger, more mature, more serious.

But that framing is backwards.

Isolation isn't about scale. It's about a mismatch.

Some things simply don't belong together, no matter how small you are. Like making construction workers and investors share the same porta-potty because you don't want the sanitary overhead. It works, technically. One group uses it constantly. The other has very different expectations. Pretending otherwise doesn't improve the smell. It just adds an extra meeting about air fresheners.

Synchronous web traffic and asynchronous data processing are exactly that kind of mismatch. Architecture always ~~reeks~~ leaks.

They have different expectations. Different resource profiles. Different failure modes.

Trying to make them behave like one system doesn't reduce complexity. It just hides it.

The good news is that isolation doesn't have to be heavy.

It doesn't mean microservices everywhere or a rewrite into seventeen repositories. It means being intentional about where work happens and what it depends on.

A few principles go a long way.

You don't need to hit the production database for every step of background processing. Configuration rarely changes at runtime. Load it once. Export it. Cache it.

You don't need a database connection pool tuned for multi-user concurrency to support high-throughput background work. For a lot of data processing, a filesystem or object storage is still the simplest and most reliable data lake you can use.

Compute for web traffic is not the same as compute for data throughput. User-facing requests want fast startup and low latency. Background processing wants sustained CPU, memory, and time.

And queues are not a necessary evil. They're a gift.

They give you back pressure, retries, visibility, and breathing room. They turn spikes into flows and decouple systems that were never meant to move in lockstep.

None of this is about building a complex system early.

It's about acknowledging reality early.

You already have two workloads. Pretending you don't won't make them behave like one.

Isolation isn't overengineering. It's the smallest possible admission that not all work is the same.

And systems that make that distinction early tend to age far more gracefully than the ones that don't.