koladev aka kolawole
software engineer focused on backend systems, developer tooling, and ai infrastructure

scaling to millions of users

architecture databases backend payments fintech

If you're scaling a system to millions of users, congratulations, that's usually a sign the business is working, not a problem to panic over. But before you optimize anything, you need to answer one question: where is your actual bottleneck? This article walks through how to find that bottleneck, how your workload type (reads versus writes) shapes your architecture, and why the right operational tooling often matters more than your choice of programming language.

what is the bottleneck in my system?

Software systems differ in how they use data, so the bottleneck depends on the system you're building.

Take a payment system with wallets, handling over 10 million transactions per day. A transfer from one wallet to another involves a credit and a debit, so each transaction generates two writes and two reads. At that volume, you get 40 million database operations per day, or about 463 read and write operations per second.

Now take a subscription provider similar to RevenueCat, where customers check their subscription status and entitlements. This system sees more reads than writes. Users subscribe once, rarely, but every time they open the application (perhaps 10 times a day) the app fetches their entitlements. At 20 million monthly active users, that's 200 million requests per day, most of them reads.

Write-heavy versus read-heavy workload comparison

Knowing your system's workload determines what you optimize for. Most operations, regardless of the domain, are still CRUD. Does your system need to optimize for writes, or for reads?

technology matters

People often debate whether Go, Rust, Java, Python, or Node.js can scale to millions of users.

In practice, architecture, data access, and operations constrain that scale, not the programming language itself. A language can improve CPU efficiency or concurrency, but it rarely fixes the underlying bottleneck.

specialized infrastructure beats generic infrastructure

Technology choice matters less at the start. As you scale, it matters more, but still less than most people assume.

Take a primary/replica database setup, used because you have heavy read traffic and want to scale to millions of requests. If a replica or a shard goes down, you could spin up a new one using Kubernetes. This works, but it's not the best choice.

When a replica goes down, Kubernetes only sees that the pod is inactive. It restarts the pod, or creates a new one if needed, schedules it on another node, and reattaches the persistent volume if one exists.

Kubernetes doesn't understand PostgreSQL-specific replication state, such as:

A PostgreSQL-aware orchestrator like Patroni handles these problems directly, because it understands PostgreSQL's replication behavior. Tools like Patroni, or PostgreSQL operators built on similar concepts, monitor replication health, perform leader election, promote replicas safely, and reconnect nodes to the cluster correctly after failures.

Kubernetes generic failover versus Patroni PostgreSQL-aware failover

This illustrates a broader lesson about scaling. General-purpose platforms like Kubernetes excel at orchestrating infrastructure. Specialized tools understand the behavior of the systems they manage. As your system grows, choosing the right operational tooling matters more than switching programming languages.

final thoughts

When people ask how to scale to millions of users, they're often asking the wrong question. The better questions are: