Scalability ensures the system handles more concurrent users without degrading performance. While performance optimization reduces latency per request through better algorithms and caching, scalability requires architectural patterns that distribute load across multiple nodes.
You cannot always scale away a performance problem. Fix performance issues first, then scale horizontally to handle increased load.
Start with domain-level database federation before sharding — federating by bounded context eliminates the most common write contention with less complexity than sharding.
Many logical shards mapped to fewer physical servers:
// 256 virtual shards → 4 physical serversVirtual 0-63 → Physical Server 1Virtual 64-127 → Physical Server 2Virtual 128-191 → Physical Server 3Virtual 192-255 → Physical Server 4// Add Server 5: remap only virtual shards 192-255// No full data migration required
Benefits:
Smooth resharding by remapping virtuals
Enables incremental scale-out
Reduces blast radius of rebalancing
Use virtual shards (logical → physical) to enable resharding without full data migration. Start with 10-20x more virtual shards than physical nodes.
Introduce redundancy to eliminate joins and improve read performance:
-- Normalized (requires JOIN at read time)SELECT o.id, u.name, SUM(i.price) as totalFROM orders oJOIN users u ON o.user_id = u.idJOIN items i ON i.order_id = o.idGROUP BY o.id, u.name;-- Denormalized materialized view (pre-computed)CREATE MATERIALIZED VIEW order_summary ASSELECT o.id, u.name, SUM(i.price) totalFROM orders o JOIN users u ON o.user_id = u.idJOIN items i ON i.order_id = o.idGROUP BY o.id, u.name;REFRESH MATERIALIZED VIEW CONCURRENTLY order_summary;
Separate Command (write) model from Query (read) model:
// CQRS architectureCommand side (writes): API → Service → PostgreSQL (normalized, ACID) ↓ event Event Bus (Kafka) ↓Query side (reads): Event Handler → Elasticsearch (denormalized, optimized for search)User reads always hit query side (fast, denormalized)User writes always hit command side (consistent, normalized)
When to use:
Read/write ratio > 10:1
Complex query requirements differ from write validation
Need multiple read representations (search, analytics, cache)
CQRS adds significant complexity. Start with read replicas and materialized views; move to CQRS only when those patterns are insufficient.
Problem: Sharding before proving single-DB bottleneckSolution:
Optimize queries and add indexes
Add read replicas
Implement caching
Federation by domain
Shard only if still bottlenecked
Shared Database Across Services
Problem: Multiple services writing to the same tablesImpact: Write contention, deployment coupling, schema migration nightmaresSolution: Database per service (federation), communicate via APIs or events
Cross-Shard Queries in Hot Path
Problem: SELECT without shard key hits all shardsImpact: Latency proportional to shard count, amplifies loadSolution: Include shard key in all queries, or denormalize data for that access pattern