Route monolith reads to replicas without code edits

Drain safe reads from an overloaded primary by running a parallel monolith and routing selected endpoints to replicas.

Parallel monolith read drain

A legacy monolith can overload its primary database with reads simply because its default connection points there. Many of those queries may tolerate a replica's delay, but changing every old call site could take months. Moving a few suitable routes can buy time for that cleanup.

The useful boundary is the running application: give a second copy a different database configuration, then control which requests reach it. First choose eligible routes, then measure whether moving them actually relieves the primary.

Same code, different default database

Keep the existing fleet pointed at the primary. Run another fleet from the same application code with its default connection pointed at a replica. An API gateway sends selected read routes to the second fleet; writes and all other routes stay on the existing path.

Application fleet Default database --- --- Existing monolith Primary Read-drain monolith Replica, with read-only credentials

This avoids editing individual queries only when they use that configurable default. Audit explicit connection overrides, startup migrations, background consumers and scheduled jobs. Disable work that should not run in the second fleet. A second HTTP deployment should not accidentally become a second job scheduler.

Route by both path and method. Gateway support for GET /catalog does not imply that POST /catalog should reach the same backend. KrakenD, for example, lets endpoint definitions select a method and backend. That supplies a routing mechanism; it cannot tell you whether the application behind a route is safe to move.

Choose by behavior and freshness

“GET” is not enough. A handler might update a last-seen timestamp, create a missing row, refresh a token or publish an external event. Read-only database credentials help reject accidental database writes, including after a replica is promoted. They do not prevent external side effects.

A route also needs a freshness contract. An asynchronous replica can return a state from before an accepted write. Decide whether that is acceptable for the particular answer:

Possible candidates, after inspection Keep on the primary unless stronger guarantees exist --- --- Catalog descriptions with an accepted delay Checkout confirmation immediately after payment Profile display where a delayed edit is acceptable Balance or entitlement decisions requiring current state Dashboards that show their data freshness Read-modify-write and idempotency endpoints

These are starting points, not permanent labels. A catalog page that promises current inventory has a different requirement from one displaying descriptions. A profile response that includes current access permissions needs a separate authorization decision.

For read-after-write flows, retaining the primary route is often the simplest first choice. The wiki/replication replication lesson examines other freshness strategies. Do not silently weaken the product's behavior merely to move more queries.

Move one route and watch both databases

Start with primary read work attributable to the route: request volume, query volume and expensive queries. Establish enough replica capacity, then route a small population through the second fleet. Watch the primary's work, replica replay lag, endpoint errors and p95/p99 latency together.