The Hidden Skill Behind Reliable Software: Learning to Love More Complexity, Not Less

Jaeyeol Lee

Hatched by Jaeyeol Lee

Jun 05, 2026

10 min read

72%

0

The real problem is not complexity, it is denial

What if the biggest threat to software systems is not that they become too complex, but that teams keep pretending they are simple?

That is the uncomfortable reality behind modern infrastructure work. Systems grow, workloads shift, dependencies multiply, and every improvement creates new conditions that the old architecture was never designed to absorb. Yet many engineering cultures still treat complexity like a contaminant, something to be stripped away, abstracted away, or postponed until later. The result is not simplicity. It is fragile simplification, a system that looks elegant until the day it meets reality.

The deeper issue is not whether software can avoid complexity. It cannot. The issue is whether a team has the maturity to handle complexity intentionally. That difference separates systems that evolve from systems that merely survive.

Upgrading a large production database is a perfect case study. On the surface, it looks like a narrow technical task, a version change, a migration, a compatibility problem. In practice, it becomes a revelation about how organizations think. A database upgrade exposes assumptions about latency, failover, application behavior, observability, rollback, and human coordination. It turns out that upgrading infrastructure is less like replacing a part in a machine and more like changing the engine while the plane is mid flight.

And that is why complexity is not the enemy. Unexamined complexity is.


Why every successful system eventually becomes harder to understand

There is a seductive myth in software that if we design well enough, complexity will go away. In reality, complexity often grows precisely because the system is succeeding. More users create more traffic. More traffic demands caching, replication, sharding, and queueing. More integrations demand version management, compatibility layers, and fallback paths. More value attracts more edge cases.

This is the paradox at the heart of mature software: growth produces structure, and structure produces complexity. A small product can be understood by one team in a week. A large platform can only be understood through logs, dashboards, runbooks, rehearsals, and institutional memory. The complexity is not a mistake. It is the price of utility at scale.

Consider a city. A village can function with a few roads, one water source, and a shared memory of where everything is. A city needs traffic systems, zoning, utilities, emergency services, and maintenance schedules. Nobody calls a city defective because it is more complex than a village. The city is complex because millions of people rely on it every day. Software is no different when it becomes foundational infrastructure.

The problem arises when teams keep applying a village mindset to city scale systems. They expect one neat abstraction to contain every edge case. They assume the next layer of complexity is temporary. They build for the ideal path and leave the exception path as an afterthought. Then, when the system changes, the organization discovers that the exception path is the real system.

Complexity is not what happens after design fails. Complexity is what design must learn to accommodate.

This is why major upgrades are so revealing. A version upgrade is never just about the new software. It is about whether the surrounding architecture can absorb change without collapsing into fear, downtime, or uncontrolled side effects. The upgrade asks a brutal question: was the system designed to be changed, or only to be used?

That question reaches far beyond databases.


The hidden cost of pretending complexity is optional

When teams treat complexity as something to eliminate rather than manage, they usually pay in three ways.

1. They create brittle abstractions

A brittle abstraction is one that works beautifully until reality deviates from the idealized case. At that point, the abstraction does not degrade gracefully. It snaps.

This happens when engineering teams optimize for elegance over adaptability. A clean interface may hide the underlying mess, but hidden mess does not disappear. It accumulates interest. Then a migration, failover event, or schema change reveals that the abstraction was not a simplification, it was a blindfold.

2. They build organizational habits that fear change

The technical problem becomes a social problem. If every significant upgrade feels dangerous, people delay upgrades. If upgrades are delayed, the system falls behind. If the system falls behind, the next upgrade becomes even more dangerous. The organization enters a loop where the cost of change rises because change was treated as exceptional rather than routine.

This is one reason large systems can feel trapped by their own success. The more important they become, the more dangerous they seem to touch. The team starts saying things like, “We cannot risk it,” when what they mean is, “We did not design for continuous adaptation.”

3. They confuse control with understanding

A system may appear stable because its operators have learned to avoid disturbing it. But stability achieved through avoidance is not robustness. It is caution disguised as mastery.

Real understanding means being able to explain not just how the system works on a normal day, but how it fails, how it recovers, how it changes, and how it behaves under surprise. That is why observability, testing, staged rollout, and rollback are not bureaucratic overhead. They are the instruments of understanding. They make complexity legible.

In that sense, complexity is like weather. You do not eliminate it. You build forecasting, infrastructure, and response systems around it. The mature question is not “How do we make the weather go away?” It is “How do we live well in a world where weather exists?”


A better mental model: software as a living adaptation layer

The most useful way to think about modern software is not as a fixed artifact, but as a living adaptation layer between changing demands and finite resources.

That model changes everything.

If software is an artifact, success means freezing it into its best form. If software is an adaptation layer, success means preserving its ability to change without breaking. The goal shifts from permanence to responsiveness. What matters is not whether the system is complex, but whether its complexity is structured in a way that can evolve.

This suggests a practical distinction between two kinds of complexity:

  • Necessary complexity: the irreducible complexity created by scale, constraints, and real-world variability.
  • Accidental complexity: the extra burden introduced by poor interfaces, unclear boundaries, and incompatible assumptions.

The first must be embraced. The second must be reduced.

That distinction is easy to state and hard to practice because the two are often tangled together. A database upgrade, for example, forces a team to separate them. Which parts of the system are inherently complex because of scale, consistency, or traffic patterns? Which parts are complex only because of historical shortcuts or outdated assumptions? The upgrade process becomes a diagnostic tool for architectural truth.

This is why the best engineering organizations do not chase the fantasy of zero complexity. They build systems that can carry complexity transparently.

Think of a suspension bridge. It does not remove the forces acting on it. It channels them. Wind, traffic, and weight all exist, but the bridge survives because its design anticipates and distributes stress. Good software architecture works the same way. It does not pretend away load, failure, and evolution. It shapes them.


The real challenge is organizational, not technical

The phrase “embracing complexity” can sound abstract until you see what it demands from a team.

It demands patience when a quick fix would be easier. It demands rehearsal when shipping feels urgent. It demands discipline around schemas, compatibility, feature flags, backups, metrics, and rollback. Most of all, it demands a shared willingness to see the system as dynamic rather than static.

That is hard because organizations are naturally tempted to convert complexity into slogans. “We value simplicity.” “We move fast.” “We avoid unnecessary abstraction.” These statements can be healthy, but they become dangerous when they turn into identity. A team that believes it is a simple team will often interpret complexity as failure rather than as reality.

Mature organizations do something more interesting. They normalize complexity without romanticizing it. They make room for the fact that serious software has layers. They document the weird parts. They test the edge cases. They treat migration rehearsals, dependency audits, and observability work as central to product quality, not support work.

This is where database upgrades become such a useful metaphor. A successful upgrade is not mainly a triumph of a particular tool or procedure. It is evidence of an organizational worldview. It shows that the team believes change is inevitable, that failure modes are worth mapping, and that reliability comes from preparedness rather than denial.

In other words, the most advanced engineering skill today may be a very old one: the ability to live with uncertainty without becoming paralyzed by it.

Reliability is not the absence of surprises. Reliability is the presence of systems that are ready for surprises.


What this means in practice: build for change, not just for correctness

If complexity is unavoidable, then the question becomes how to design for it without being consumed by it. The answer is not more ceremony for its own sake. It is a different kind of rigor.

Here are a few principles that follow from this view.

Make change a normal operating mode

Upgrades should not feel like heroic events. The more a system changes in small, controlled increments, the less terrifying major change becomes. Frequent, routine adaptation is easier to absorb than rare, high stakes transformation.

Separate knowledge from memory

A system should not depend on a few people remembering tribal details. Document failure modes, operational runbooks, schema assumptions, and deployment sequences. Complexity that lives only in human memory is one incident away from becoming a liability.

Design for reversibility

If you cannot roll back, pause, or isolate a change, you have not really designed a change, you have designed a leap of faith. Reversibility turns complexity from a cliff into a staircase.

Observe the system at the right level

Not all visibility is useful. The goal is not more dashboards. The goal is the right signals: latency, error rates, saturation, replication lag, queue depth, and the behaviors that reveal whether the system is adapting or drifting toward trouble.

Distinguish stable interfaces from unstable internals

A system does not need to be simple everywhere. It needs clear boundaries. Stable interfaces let internal complexity evolve without forcing every dependent service to relearn the world.

These ideas may sound operational, but they are really philosophical. They reflect a deeper belief that systems should be built to meet the future, not merely to preserve the present.


Key Takeaways

  • Stop treating complexity as a failure condition. In mature systems, complexity is often a sign of scale, value, and real-world constraints.
  • Separate necessary complexity from accidental complexity. The first must be managed, the second must be reduced.
  • Make change routine. Systems become safer when upgrades, migrations, and experiments are expected rather than exceptional.
  • Build reversibility into the architecture. Rollbacks, isolation, and staged deployment turn uncertainty into something manageable.
  • Measure the system’s adaptability, not just its correctness. A correct system that cannot evolve is brittle.

The deepest lesson: the goal is not simplicity, it is survivable complexity

The temptation in software is to imagine a future where the system becomes clean enough that complexity no longer matters. That future does not arrive. Instead, systems accumulate users, dependencies, and consequences. The question is not whether they will become more complex. They will.

The real question is whether that complexity becomes a source of fragility or a source of strength.

A well designed system does not deny complexity, and it does not worship it. It converts complexity into something navigable. It uses boundaries, observability, rehearsals, and rollback paths to make change less catastrophic. It accepts that the world is dynamic and then builds like that fact matters.

That is the real upgrade, whether you are changing a database engine or redesigning a product platform. Not the new version itself, but the organizational maturity to admit that the world will keep changing, and to be grateful for the complexity that change requires.

In the end, the most important software skill is not simplification. It is earning the right to stay in motion.

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣