There are two ways to end up building systems at scale. The first is the growth story: you start small, hit bottlenecks, fix them, repeat. The architecture reflects the journey — you can read the decisions in the scar tissue.
The second is less discussed: you join an organization that's already at scale and you ship new things into that environment. There's no gradual ramp. Your first production deploy is live for 1M+ users.
That's the context at Simplilearn. The platform has 1M+ organic monthly visitors. It was at that scale when I joined. Every new app I build — Next.js frontends in the frontend monorepo, backend services in the backend monorepo — goes into production on a platform where traffic is already massive and user expectations are already formed.
The discipline this requires is different from the scaling journey. It's not reactive. You can't ship first and optimize later, because there's no "later" where traffic ramps up slowly and gives you time. You have to make architectural decisions at design time that you'd normally make under pressure six months down the road.
The Mindset Shift
In a typical scaling journey, CDN strategy, caching layers, and read replicas are things you add when you need them. There's a natural sequence: something breaks or slows down, you add the fix, you move on.
When you're building into an existing 1M+ scale platform, those decisions move to the design phase. Not because you're being overcautious — because the cost of retrofitting is genuinely higher. A new app that goes live without proper CDN configuration will immediately generate avoidable load on the origin. A service that connects directly to the primary database without connection pooling will compete with production traffic from the moment it ships.
The questions I now ask at design time that used to feel premature:
- What percentage of this app's traffic is cacheable at the CDN edge?
- What's the read/write ratio, and does it warrant a read replica from day one?
- How will this service behave if a downstream dependency is slow or unavailable?
- What's the connection pooling strategy before we write the first query?
These aren't academic questions. They're decisions with real consequences that are cheap to make correctly upfront and expensive to retrofit later.
CDN Strategy as a First-Class Architectural Decision
For public-facing Next.js apps in the frontend monorepo, CDN cacheability is part of the page design, not an afterthought.
The pattern: every page is modeled as a cacheable shell plus personalized fragments. The shell — content, structure, metadata — is designed to be cacheable at the edge with appropriate TTLs. Personalized data (enrollment status, progress, user-specific pricing) is either fetched client-side after the shell loads or served via a separate API endpoint that's explicitly excluded from edge caching.
This split has to happen at design time. If you build the page assuming full server-side rendering on every request, retrofitting CDN cacheability means rearchitecting the page — separating concerns that were built together, adding client-side fetching that wasn't in the original design.
The discipline is treating CDN cache-hit rate as a design requirement, not a performance metric you measure after launch.
Caching Layers: Explicit, Not Emergent
At Simplilearn's scale, three distinct caching layers are necessary and each serves a distinct purpose.
CDN edge cache: full or partial HTML responses for public routes. TTL in minutes to hours. Invalidated on content publish. This layer exists to protect the origin from request volume it doesn't need to handle.
Application cache (Redis): computed aggregates — enrollment counts, completion percentages, course ratings. TTL in seconds to minutes. These are expensive to compute and queried on almost every page load.
Database query cache: for read-heavy, rarely-changing reference data — course categories, skill taxonomies, configuration — Postgres's query cache handles this tier without adding application complexity.
The discipline is being explicit about which layer each piece of data lives in from the start. The failure mode at scale isn't missing a cache layer — it's having duplicate caches with different TTLs that produce consistency bugs. Emergent caching (adding cache where things are slow) leads to this. Designed caching (deciding upfront what lives where) prevents it.
When I'm designing a new service, the caching architecture is documented before the first line of code. Which data is cached at which layer, what the TTL rationale is, and how invalidation works.
Read Replicas and Connection Pooling From Day One
New services at Simplilearn connect to read replicas for read-heavy operations and use connection pooling from initial deployment. These aren't optimizations applied after observing load — they're defaults.
The reasoning is simple: the primary database is already under production load from the full platform. A new service that routes all reads to the primary is adding unnecessary load from day one. The configuration cost of pointing reads at a replica is minimal. The potential impact of not doing it is immediate.
Connection pooling follows the same logic. Without it, every new service deployment adds unbounded connections to the database under the traffic patterns of a 1M+ user platform. Connection pool configuration is part of the service template, not something added when connections start getting exhausted.
Circuit Breakers and Failure Isolation
A new service on an existing platform inherits the platform's dependencies. If your new app calls three downstream services, each of those services has its own failure rate. Without failure isolation, a slow or unavailable downstream service makes your new app slow or unavailable.
Circuit breakers are part of the default service design, not a later addition. The pattern: any external service call is wrapped with a circuit breaker that fails fast when the downstream is degraded, rather than allowing the failure to propagate and exhaust thread pools or request queues.
This is a decision that is trivial to make at service design time and genuinely difficult to retrofit correctly during an incident. The cost of getting it wrong is proportional to the scale of the platform you're serving — at 1M+ users, a cascading failure from a missing circuit breaker is a major incident.
The Practical Difference
The difference between "we scaled to this" and "we designed for this" isn't primarily technical. Both paths can arrive at similar infrastructure. The difference is when the decisions get made.
Reactive scaling decisions get made under pressure, during incidents, with imperfect information and time constraints. Proactive design decisions get made with full information, in review, with time to consider tradeoffs.
Building at Simplilearn's scale has made me permanently impatient with the "we'll optimize it later" approach. Later, in this environment, doesn't exist as a quiet window of opportunity. Later is a production incident.
The patterns that work — CDN-first design, explicit caching layers, read replicas, connection pooling, circuit breakers — are not complicated. The discipline is making them the starting point rather than the finish line.