Phase 1: Query Optimization
Before adding infrastructure, optimize what you have. Most performance issues are solved by better indexes and queries.
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status, created_at DESC);
CREATE INDEX idx_products_category_price ON products(category_id, price);Phase 2: Read Replicas
For read-heavy workloads, add read replicas to distribute the load.
class DatabaseService {
async getOrder(id) {
return this.readDb.order.findUnique({ where: { id } });
}
async createOrder(data) {
return this.writeDb.order.create({ data });
}
}Phase 3: Caching Layer
Cache hot data to reduce database load. Use Redis with appropriate TTL and invalidation strategies.
async getUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.user.findUnique({ where: { id } });
await redis.setex(`user:${id}`, 3600, JSON.stringify(user));
return user;
}Phase 4: Sharding
When a single server can't handle the write load, split data across multiple servers.
| Strategy | Best For | Trade-offs |
|---|---|---|
| Range-based | Time-series data | Hot spots on latest data |
| Hash-based | Even distribution | Range queries are hard |
| Geographic | Latency-sensitive apps | Complex rebalancing |
Decision Framework
- Optimize queries and add indexes first
- Add read replicas for read-heavy workloads
- Cache hot data with Redis
- Shard only when nothing else works