Skip to content

Instantly share code, notes, and snippets.

@darkn3rd
Created August 6, 2026 20:37
Show Gist options
  • Select an option

  • Save darkn3rd/d48a7b8d8fb0dc8973e89124de6da013 to your computer and use it in GitHub Desktop.

Select an option

Save darkn3rd/d48a7b8d8fb0dc8973e89124de6da013 to your computer and use it in GitHub Desktop.
scaling notes

I would first establish the workload characteristics and scaling target: current and expected traffic, read/write ratio, latency objective, data volume, availability requirements, regional requirements, and budget. Then I would scale each tier independently, because the application, cache, and database have different bottlenecks and consistency trade-offs.

1. Start with requirements

Before choosing an architecture, I would ask:

  • Is traffic steady, bursty, or seasonal?
  • Is the application stateless?
  • What are the read/write ratios?
  • What is the current bottleneck?
  • What latency and availability are required?
  • How much data is stored, and how quickly is it growing?
  • Can requests tolerate stale data?
  • Is this single-region or multi-region?
  • What are the recovery-point and recovery-time objectives?
  • Is cost optimization or maximum availability the higher priority?

This prevents jumping directly to “add Kubernetes replicas” when the real bottleneck may be PostgreSQL locks or slow queries.

2. Application tier

I would normally run the application in Kubernetes using a Deployment.

Internet
   |
Cloud Load Balancer
   |
Kubernetes Ingress / Gateway
   |
Application Service
   |
Application Pods

The application pods should be stateless:

  • No session state stored only in memory
  • No important files stored on the container filesystem
  • Configuration injected through environment variables, ConfigMaps, or Secrets
  • User sessions stored in Redis or represented by signed tokens
  • Uploaded files stored in object storage rather than pod disks

I would configure:

  • Multiple replicas across nodes and availability zones
  • Readiness and liveness probes
  • Pod anti-affinity or topology spread constraints
  • Resource requests and limits
  • Horizontal Pod Autoscaling
  • Pod disruption budgets
  • Cluster Autoscaler or a node-provisioning system such as Karpenter
  • Graceful shutdown and connection draining

The Horizontal Pod Autoscaler could use CPU initially, but request rate, latency, or queue depth are often better signals.

Application scaling trade-offs

Adding pods is usually the easiest kind of scaling, but it introduces other pressures:

  • More database connections
  • More Redis connections
  • More network traffic
  • Increased load on downstream APIs
  • More difficult debugging and observability
  • Potentially higher node and load-balancer costs

A common mistake is scaling application pods from 10 to 100 while allowing every pod to open 20 PostgreSQL connections. That can increase database connections from 200 to 2,000 and overwhelm PostgreSQL.

3. PostgreSQL

For production, I would generally use a managed PostgreSQL service rather than run PostgreSQL directly in Kubernetes.

Examples conceptually include:

  • Managed PostgreSQL
  • A managed PostgreSQL-compatible clustered database
  • A managed database with automated backups, failover, monitoring, and read replicas

I would place it in private subnets, separate from the Kubernetes worker nodes, with access restricted to the application security group or equivalent network identity.

Kubernetes application pods
        |
Connection pooler or managed proxy
        |
Managed PostgreSQL primary
        |
Read replicas

First scale PostgreSQL vertically and operationally

Before partitioning or sharding, I would optimize:

  • Slow queries
  • Missing indexes
  • Excessive table scans
  • Lock contention
  • Large transactions
  • Connection management
  • Vacuum and table bloat
  • Inefficient ORM behavior
  • N+1 queries
  • Poor schema design

Then I would consider:

  • Larger CPU and memory
  • Faster storage and higher IOPS
  • Multi-zone high availability
  • Read replicas
  • A connection pooler such as PgBouncer
  • A managed database proxy
  • Table partitioning
  • Archiving old data

Read scaling

If the workload is read-heavy, read replicas can offload:

  • Reports
  • Search-like queries
  • Dashboards
  • Analytics
  • Noncritical reads that tolerate replication lag

The application must distinguish between reads that can use replicas and reads that require the primary.

For example, after a user updates their profile, immediately reading from a lagging replica might return the old data. That workflow may require read-after-write consistency from the primary.

Write scaling

PostgreSQL write scaling is harder.

Options include:

  • Improving indexes and queries
  • Batching writes
  • Moving noncritical work to queues
  • Partitioning large tables
  • Separating workloads into different databases
  • Sharding by tenant, customer, region, or another stable key
  • Moving specialized workloads to a more appropriate datastore

Sharding should come relatively late because it complicates:

  • Transactions
  • Joins
  • Schema migrations
  • Backups
  • Routing
  • Rebalancing
  • Operational support

PostgreSQL cost trade-offs

Managed databases cost more than running a database on a basic virtual machine, but the price includes varying degrees of:

  • Automated backups
  • Point-in-time recovery
  • Failover
  • Patching
  • Monitoring
  • Storage management
  • Read-replica support

Read replicas improve capacity and availability, but each replica adds compute and storage cost. Multi-zone standby instances often improve availability but may not serve application reads.

4. Redis

I would also generally use a managed Redis-compatible service unless there were strong cost, feature, or operational reasons to run it in Kubernetes.

Redis should be in private networking and accessed only by the application.

Its topology depends on how it is used.

Cache-only Redis

If Redis is only a disposable cache:

  • Data loss may be acceptable
  • Persistence may be unnecessary
  • The application must tolerate cache misses
  • PostgreSQL remains the source of truth

The application might use a cache-aside pattern:

1. Read Redis
2. On cache miss, read PostgreSQL
3. Store result in Redis with a TTL
4. Return result

This can reduce database reads substantially.

Important controls include:

  • TTLs
  • Memory limits
  • Eviction policy
  • Cache-key design
  • Protection against cache stampedes
  • Jittered expiration times
  • Negative caching where appropriate

Redis as sessions, queues, or critical state

If Redis stores sessions, queues, locks, or other important state, its availability and persistence requirements are higher.

I would consider:

  • Primary and replicas
  • Automatic failover
  • Multi-zone deployment
  • Persistence
  • Backups
  • Redis Cluster or another sharded topology
  • Clear behavior during failover or data loss

Redis scaling trade-offs

Redis is fast, but it can create new problems:

  • Stale data
  • Cache invalidation complexity
  • Hot keys
  • Memory expense
  • Cache stampedes
  • Serialization overhead
  • Added operational dependency
  • Incorrect distributed locking

Scaling Redis vertically is simple, but memory can become expensive. Sharding improves capacity but complicates multi-key operations and increases operational complexity.

5. Network and placement

I would normally use a topology like this:

Public subnets
  Cloud load balancer

Private application subnets
  Kubernetes worker nodes
  Application pods

Private data subnets
  Managed PostgreSQL
  Managed Redis

The load balancer is the only public-facing component.

PostgreSQL and Redis should not be publicly accessible. Access should be controlled using security groups, firewall rules, network policies, identity-based authentication where supported, and encrypted connections.

I would distribute application instances and managed services across availability zones.

One subtle issue is cross-zone traffic. If application pods frequently communicate with database or cache nodes in another zone, the design may incur additional latency and data-transfer cost. However, aggressively pinning applications to a particular database node can undermine failover and resilience. This is a trade-off between locality and availability.

6. Protecting the database during application scaling

The database often becomes the first shared bottleneck.

I would explicitly address:

  • Connection pooling
  • Maximum connection limits
  • Per-pod pool sizes
  • Request timeouts
  • Retry budgets
  • Exponential backoff and jitter
  • Circuit breakers
  • Load shedding
  • Queueing asynchronous work

For example, instead of allowing every request to write synchronously:

Request
   |
Application
   |
Queue
   |
Worker pods
   |
PostgreSQL

Worker pods can scale based on queue depth. This smooths bursts and protects PostgreSQL.

However, queues introduce eventual consistency and require idempotency, retry handling, dead-letter queues, and operational monitoring.

7. Autoscaling strategy

I would avoid scaling every component based on CPU alone.

Possible signals include:

Component Useful scaling signals
Web application Request rate, CPU, latency, active requests
Background workers Queue depth, oldest message age
Kubernetes nodes Unschedulable pods and resource demand
PostgreSQL CPU, IOPS, lock waits, connections, query latency
Redis Memory, CPU, operations per second, evictions, hot keys

Kubernetes can scale application and worker pods automatically. Managed PostgreSQL and Redis often require scheduled, manual, or service-specific scaling decisions.

Autoscaling the front end faster than the database can absorb traffic is dangerous. I would establish upper limits based on downstream capacity.

8. Observability

Before and during scaling, I would instrument:

  • Request throughput
  • Error rates
  • Latency percentiles
  • Saturation
  • Pod CPU and memory
  • Database query latency
  • Slow queries
  • Connection pool utilization
  • Database locks and replication lag
  • Redis hit ratio
  • Redis evictions and memory
  • Queue depth and processing delay
  • External dependency latency

I would use load testing to determine where saturation begins rather than guessing.

9. Availability levels

The design changes depending on the required availability.

Lower-cost environment

  • Single Kubernetes cluster
  • Several application replicas
  • Single-zone database
  • Single Redis node
  • Daily backups

This is less expensive but permits longer outages.

Typical production environment

  • Application pods distributed across availability zones
  • Multi-zone managed PostgreSQL
  • Managed Redis with replication and automatic failover
  • Automated backups
  • Horizontal Pod Autoscaling
  • Multiple Kubernetes nodes
  • Tested restore and failover procedures

High-availability or business-critical environment

  • Strong zone redundancy
  • Read replicas
  • Cross-region recovery
  • Global traffic management
  • Replicated object storage
  • Defined disaster-recovery procedures
  • Possibly active-passive or active-active regional architecture

Multi-region raises cost and application complexity substantially, particularly for writes and consistency.

10. Major cost drivers

I would explain costs by category rather than just listing services.

Compute

  • Kubernetes worker nodes
  • Application pods
  • Background workers
  • Idle capacity needed for failover
  • Cluster management fees, depending on platform

Database

  • Primary instance
  • Standby instance
  • Read replicas
  • Storage
  • Provisioned IOPS
  • Backup retention
  • Cross-region replication

The database is frequently one of the most expensive components.

Cache

Redis memory is relatively expensive. Costs grow with:

  • Dataset size
  • Replication
  • Sharding
  • Multi-zone operation
  • Backup and persistence requirements

Network

  • Load-balancer charges
  • Cross-zone traffic
  • NAT gateway or equivalent egress
  • Internet egress
  • Cross-region replication

Network charges can be surprisingly large, especially with high-volume cross-zone or NAT traffic.

Operational complexity

Self-hosting PostgreSQL or Redis may reduce the cloud bill on paper but increases engineering cost, on-call burden, patching responsibilities, backup risk, and recovery complexity.

A concise interview answer

I would begin by identifying the bottleneck and defining traffic, latency, availability, consistency, and cost requirements. I would keep the Kubernetes application tier stateless and scale it horizontally behind a cloud load balancer using multiple replicas, topology spreading, resource requests, and autoscaling based on request rate or latency.

I would usually place PostgreSQL and Redis in managed private services rather than inside Kubernetes. For PostgreSQL, I would first optimize queries, indexes, connections, and storage; then scale vertically, add connection pooling, and use read replicas for read-heavy workloads. Write scaling is more difficult and may eventually require partitioning, asynchronous processing, workload separation, or sharding.

For Redis, I would decide whether it is a disposable cache or a critical state store. As a cache, I would use TTLs, an eviction policy, cache-aside behavior, and protection against stampedes. If it stores sessions or queues, I would add replication, failover, and persistence.

I would also prevent the application tier from overwhelming the database by controlling connection pools, retries, concurrency, and queue-backed work. I would instrument the system and load-test it to find actual saturation points. The primary trade-offs are availability versus cost, managed services versus operational control, consistency versus performance, and horizontal application scaling versus increasing pressure on shared database and cache tiers.

The most important thing in this question is not presenting a single “correct” architecture. It is showing that you understand where scaling moves the bottleneck next, and that each improvement introduces costs or consistency trade-offs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment