All articles

Modernize Legacy Systems Gradually—and Treat Caches as Operations

Choose legacy boundaries carefully, change them in small steps, and add a shared cache only after DB work and deployment failure modes are understood.

Source and AI note: This article is based on Gemini’s Devpractice on YouTube. It was generated and edited with the gpt-5.6-sol model.

Two technical changes that look unrelated often share the same failure: a large decision is made before the team understands its boundary and operating cost. Splitting a legacy monolith can freeze the wrong service map. Adding a cache can hide a database problem and introduce deployment failures the team has never practiced.

Both changes should begin with the smallest evidence-backed step.

In legacy work, the first boundary matters most

A monolith can be separated gradually, but “gradually” does not make every split safe. Choosing whether order or product should leave first can determine whether the modernization remains workable. If the boundary is wrong, later services inherit awkward dependencies and the project spends its time repairing the split.

Avoid decomposing a business by its nouns alone. A school system does not need separate services for desks, chairs, books, and lockers merely because those objects exist. The useful boundary comes from business responsibility and change, not from counting entities.

Start with an area whose role is understood and whose interactions can be observed. Move it in reviewable increments, verify the result, and let what the team learns change the next cut. A broad rewrite leaves fewer places to discover that the model was wrong before the cost becomes large.

A slow query is not yet a cache requirement

If a moderate data set takes many seconds to query, I first suspect the query, indexing, or data access design. A cache can make that path appear fast while preserving the defect underneath.

A stronger cache case looks different: the database query is already reasonable, request volume is high enough to burden the database, and many requests reuse a small set of values so the expected hit rate is high. In that condition, a shared cache can reduce repeated database work.

The example numbers used to explain this are not thresholds. Data shape, query cost, and traffic pattern decide whether the cache pays for itself.

Cache schema changes are deployments

A shared cache stores a representation that old and new application versions may read at the same time. During a rolling or blue-green deployment, changing a field or its interpretation under the same key can make those versions incompatible. Clearing all keys does not necessarily solve it: an old instance may immediately write the old shape again, and a rollback may face values produced by the new shape.

A safer change may require a new key or explicit version so both representations can coexist during the transition. The exact strategy depends on the cache and deployment, but the team must reason about mixed versions rather than assuming a simultaneous switch.

That is only one operational cost. A cache also raises questions about synchronization, invalidation, storage failure, fallback to the database, and how data will be loaded again. Local caches have different details, but they are not free of operational choices either.

Complexity needs a measured reason

Use the database as far as it reasonably goes. Add a cache when measured or well-modeled load shows that reducing database work is worth the new failure modes. Split legacy code when the team can explain the business boundary and verify each move.

Neither caution means “never.” A cache can be exactly right, and a monolith may need to be divided. The restraint is about timing: understand the present system, make one reversible change, and keep the next step open until operation supplies better evidence.