When Microservices Look Fine but the Database Is Screaming
When I first read the Dev.to piece, I recognized the pattern from our own curation pipeline: we split fetching, parsing, and delivery into separate services, but they all hit the same Postgres instance for metadata. The article’s title grabbed me because it names the exact illusion we’ve seen in many architecture reviews—services that scale cleanly while the data store groans under hidden pressure.
The Illusion of Independent Scaling
The article walks through a familiar story: teams celebrate smaller services, independent deploys, and clean diagrams that look like Netflix’s architecture. Then production traffic arrives, Kubernetes happily scales pods, and the database melts. What’s interesting is how the author frames the problem not as a failure of microservices but as a failure to treat the database as a shared resource. Each service may add only a few queries, but when you multiply those by dozens of scaled instances, the database sees a sudden surge in connections, locks, and cache misses. It’s a classic case of local optimization creating global congestion.
I’ve seen this same dynamic in our own system when we first added a recommendation service that queried the same article‑metadata table used by the curation worker. At low traffic the extra join was invisible, but as we increased the fetch frequency to stay fresh for subscribers, the query latency crept up. The service metrics looked fine; the database metrics showed rising wait times. The piece’s observation that “the database doesn’t see microservices, it sees chaos” matched what we observed in our logs.
Why Shared State Undermines Microservices
The core issue, as the author points out, is shared ownership of data. When multiple services read and write the same tables, they become tightly coupled through the database, even if they deploy independently. This coupling shows up as contention on rows, bloated connection pools, and the need for distributed transactions that are hard to operate at scale. The article’s suggestion to move toward clear data ownership—each service owning its data and exposing it via APIs or events—resonates because it forces you to confront the trade‑off: you gain operational flexibility but you must invest in contracts and versioning.
What the piece doesn’t explore in depth is how caching often becomes a band‑aid. Adding Redis for read‑through can mask latency spikes, but writes still hammer the primary database, and cache invalidation introduces its own complexity. The author rightly calls caching a painkiller, not a cure. In our pipeline we avoided caching the metadata table altogether; instead we moved to a read‑model updated via change‑data‑capture, which lets the curation service scale reads without adding write pressure to the main Postgres instance.
What We Did Differently in DigestPress
Because we deliver a scheduled digest rather than a real‑time feed, we already limit how often services need to query the database. The curation worker runs once per issue, pulls the selected articles, and then the delivery service formats and ships the EPUB. This design naturally reduces concurrent connections compared to a system that polls for updates every few seconds. It’s a concrete example of designing for database load first: we asked, “What does this do to the database at 10x traffic?” before we added any new service.
We also made sure each microservice owns its data store where possible. The recommendation service, for instance, writes to its own tiny SQLite file that’s shipped with the EPUB, eliminating cross‑service writes entirely. When another service needs that data, it consumes the EPUB itself rather than querying a shared table. This approach mirrors the article’s advice to replace synchronous dependencies with async workflows or event‑driven updates.
Next Step: Ask the Database What It Sees
If you’re reviewing a microservice architecture, make the database’s perspective part of the checklist. Before you celebrate a service that scales from two to twenty pods, ask what that means for open connections, idle transactions, and lock contention on your primary data store. Run a load test that measures database latency, not just service latency, and look for the point where the curve starts to climb. That simple question—“What does this do to the database at 10x traffic?”—has saved us from repeating the crying‑database scenario more than once, and it’s a habit worth adopting for any team that values reliability over the illusion of unlimited scaling.
For more on how DigestPress turns curated articles into a distraction‑free EPUB, see how curation works and why we chose e‑ink for delivery (why we built DigestPress for e‑ink). If you want to try the product yourself, visit DigestPress. And for the original argument that sparked this reflection, read the original piece.