# Optimization Patterns Proven performance optimization strategies across the stack. ## Caching Strategies ### Cache Selection Decision Tree ``` What are you caching? │ ├─ Computation result (same input → same output) │ ├─ In-process only │ │ └─ In-memory cache (LRU map, memoization) │ │ Eviction: LRU, LFU, TTL │ │ Tools: lru-cache (Node), functools.lru_cache (Python), sync.Map (Go) │ └─ Shared across processes/servers │ └─ Redis / Memcached │ TTL-based, key-value, sub-millisecond latency │ ├─ Database query result │ ├─ Rarely changes, expensive to compute │ │ └─ Materialized view (database-level cache) │ │ Refresh: on schedule, on trigger, on demand │ ├─ Changes with writes │ │ └─ Cache-aside pattern (read: cache → DB, write: DB → invalidate cache) │ └─ Read-heavy, tolerate slight staleness │ └─ Read replica + cache with TTL │ ├─ API response │ ├─ Same for all users │ │ └─ CDN cache (Cloudflare, CloudFront) │ │ Headers: Cache-Control, ETag, Last-Modified │ ├─ Varies by user but cacheable │ │ └─ Vary header + CDN or reverse proxy cache │ └─ Personalized but expensive │ └─ Server-side cache (Redis) with user-specific keys │ ├─ Static assets (JS, CSS, images) │ └─ CDN + long cache + content-hashed filenames │ Cache-Control: public, max-age=31536000, immutable │ Bust cache by changing filename (app.a1b2c3.js) │ └─ HTML pages ├─ Static content │ └─ Pre-render at build time (SSG) │ Cache-Control: public, max-age=3600 ├─ Mostly static, some dynamic │ └─ Stale-while-revalidate │ Cache-Control: public, max-age=60, stale-while-revalidate=3600 └─ Fully dynamic └─ Short TTL or no-cache + ETag for conditional requests ``` ### Cache Invalidation Patterns ``` Pattern: TTL (Time-To-Live) ├─ Simplest approach: cache expires after N seconds ├─ Pro: no coordination needed, self-healing ├─ Con: stale data for up to TTL duration └─ Best for: session data, config, rate limits Pattern: Cache-Aside (Lazy Loading) ├─ Read: check cache → miss → query DB → populate cache ├─ Write: update DB → delete cache key (not update) ├─ Pro: only caches what's actually requested ├─ Con: first request after invalidation is slow (cache miss) └─ Best for: general purpose, most common pattern Pattern: Write-Through ├─ Write: update cache AND DB in same operation ├─ Pro: cache always consistent with DB ├─ Con: write latency increases, caches unused data └─ Best for: read-heavy data that must be fresh Pattern: Write-Behind (Write-Back) ├─ Write: update cache, async flush to DB ├─ Pro: fast writes, batch DB operations ├─ Con: data loss risk if cache crashes before flush └─ Best for: high-write-throughput, non-critical data (counters, analytics) Pattern: Event-Driven Invalidation ├─ Publish event on data change, subscribers invalidate caches ├─ Pro: low latency invalidation, decoupled ├─ Con: eventual consistency, event delivery guarantees needed └─ Best for: microservices, distributed systems ``` ### Cache Anti-Patterns | Anti-Pattern | Problem | Fix | |-------------|---------|-----| | Cache without eviction | Memory grows unbounded | Set max size + LRU/LFU eviction | | Thundering herd | Cache expires, all requests hit DB simultaneously | Mutex/singleflight, stale-while-revalidate | | Cache stampede | Hot key expires under high load | Background refresh before expiry | | Inconsistent cache + DB | Update DB, crash before invalidating cache | Delete cache first (slightly stale reads), or use distributed transactions | | Caching errors | Error response cached, served to all users | Only cache successful responses, or cache with very short TTL | | Over-caching | Too many cache layers, hard to debug | Cache at one layer, usually closest to consumer | ## Database Optimization ### Indexing Strategy ``` Index selection decision tree: │ ├─ Query uses WHERE clause │ ├─ Single column filter │ │ └─ B-tree index on that column │ ├─ Multiple column filter (AND) │ │ └─ Composite index (most selective column first) │ │ CREATE INDEX idx ON table(col_a, col_b, col_c) │ │ Left-prefix rule: this index covers (a), (a,b), (a,b,c) queries │ └─ Text search (LIKE '%term%') │ └─ Full-text index (not B-tree, which only helps prefix LIKE 'term%') │ ├─ Query uses ORDER BY │ └─ Index matching ORDER BY columns avoids filesort │ Combine with WHERE columns: INDEX(where_col, order_col) │ ├─ Query uses JOIN │ └─ Index on join columns of the inner table │ ON a.id = b.a_id → index on b.a_id │ ├─ Query uses GROUP BY / DISTINCT │ └─ Index matching GROUP BY columns │ └─ High cardinality vs low cardinality ├─ High cardinality (many unique values): good index candidate └─ Low cardinality (few unique values, e.g., boolean): partial index CREATE INDEX idx ON orders(status) WHERE status = 'pending' ``` ### Query Optimization Patterns ```sql -- AVOID: SELECT * (fetches unnecessary columns) SELECT * FROM users WHERE id = 1; -- PREFER: select only needed columns SELECT id, name, email FROM users WHERE id = 1; -- AVOID: N+1 queries -- Python/ORM: for user in users: user.orders (fires query per user) -- PREFER: eager loading -- SELECT * FROM users JOIN orders ON users.id = orders.user_id -- AVOID: OFFSET for pagination on large tables SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 10000; -- PREFER: cursor-based pagination SELECT * FROM posts WHERE created_at < '2025-01-01' ORDER BY created_at DESC LIMIT 20; -- AVOID: functions on indexed columns in WHERE SELECT * FROM users WHERE LOWER(email) = 'user@example.com'; -- PREFER: expression index or store normalized CREATE INDEX idx_email_lower ON users(LOWER(email)); -- AVOID: implicit type conversion SELECT * FROM users WHERE id = '123'; -- string vs integer -- PREFER: correct types SELECT * FROM users WHERE id = 123; -- AVOID: correlated subqueries SELECT *, (SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) FROM users; -- PREFER: JOIN with GROUP BY SELECT users.*, COUNT(orders.id) FROM users LEFT JOIN orders ON users.id = orders.user_id GROUP BY users.id; ``` ### Connection Pooling ``` Connection pool sizing: │ ├─ Too small │ └─ Requests queue waiting for connections │ Symptom: latency spikes, "connection pool exhausted" errors │ ├─ Too large │ └─ Database overwhelmed with connections │ Symptom: high memory usage on DB, mutex contention, slower queries │ └─ Right size └─ Formula: connections = (core_count * 2) + effective_spindle_count For SSD: connections ≈ core_count * 2-3 For cloud DB (e.g., RDS): check instance limits Tools: - PostgreSQL: PgBouncer (transaction pooling, session pooling) - MySQL: ProxySQL - Java: HikariCP (fastest JVM pool) - Python: SQLAlchemy pool (pool_size, max_overflow, pool_timeout) - Node.js: built-in pool in pg, mysql2, knex - Go: database/sql built-in pool (SetMaxOpenConns, SetMaxIdleConns) ``` ## Frontend Optimization ### Code Splitting ``` When to split: │ ├─ Route-based splitting (most impactful) │ └─ Each page/route is a separate chunk │ React: React.lazy(() => import('./Page')) │ Next.js: automatic per-page │ Vue: defineAsyncComponent(() => import('./Page.vue')) │ ├─ Component-based splitting │ └─ Heavy components loaded on demand │ Modal dialogs, rich text editors, charts, maps │ }> │ ├─ Library-based splitting │ └─ Large libraries in separate chunks │ Moment.js, chart libraries, syntax highlighters │ // vite.config.js │ build: { rollupOptions: { output: { │ manualChunks: { vendor: ['react', 'react-dom'] } │ }}} │ └─ Conditional feature splitting └─ Features only some users need (admin panel, A/B tests) const AdminPanel = lazy(() => import('./AdminPanel')) ``` ### Image Optimization ``` Format selection: │ ├─ Photographs │ ├─ Best: AVIF (30-50% smaller than JPEG) │ ├─ Good: WebP (25-35% smaller than JPEG) │ └─ Fallback: JPEG (universal support) │ ├─ Graphics/logos with transparency │ ├─ Best: WebP or AVIF │ ├─ Good: PNG (lossless) │ └─ Simple graphics: SVG (scalable, tiny file size) │ └─ Icons └─ SVG sprites or icon fonts (not individual PNGs) Implementation: Description Responsive images: Description ``` ### Critical Rendering Path ``` Optimization checklist: │ ├─ Critical CSS │ ├─ Inline above-the-fold CSS in │ ├─ Defer non-critical CSS: │ └─ Tools: critical (npm package), Critters (webpack plugin) │ ├─ JavaScript loading │ ├─