@rune-kit/rune 2.3.2 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Mesh: A ↔ B ↔ C (B fails = A reaches C via D→E)
|
|
|
32
32
|
D ↔ E ↔ F
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
## What's New (v2.3.
|
|
35
|
+
## What's New (v2.3.3)
|
|
36
36
|
|
|
37
37
|
- **UI/UX Pro Max Integration** — 161 palettes, 84 styles, 73 font pairings, 161 reasoning rules from [UI/UX Pro Max](https://github.com/nextlevelbuilder/ui-ux-pro-max-skill) (MIT, 42.8k★) meshed into `design` skill + `@rune/ui` pack
|
|
38
38
|
- **11 Core Skill Enrichments** — deviation rules, repair operators, evidence quality gate, decision classification, debug knowledge base, CSO discipline, YAGNI pushback, formal pause/resume
|
package/docs/index.html
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<canvas id="mesh-canvas"></canvas>
|
|
63
63
|
</div>
|
|
64
64
|
<div class="hero-content">
|
|
65
|
-
<p class="hero-badge">v2.3.
|
|
65
|
+
<p class="hero-badge">v2.3.3 — 59 skills • 8 platforms • MIT</p>
|
|
66
66
|
<h1>Less skills.<br><span class="accent">Deeper connections.</span></h1>
|
|
67
67
|
<p class="hero-sub">A mesh ecosystem for AI coding assistants. Skills call each other bidirectionally, forming resilient workflows that adapt when things go wrong.</p>
|
|
68
68
|
<div class="hero-actions">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rune-kit/rune",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "59-skill mesh for AI coding assistants — 5-layer architecture, 200+ connections, 8 platforms (Claude Code, Cursor, Windsurf, Antigravity, Codex, OpenCode, OpenClaw, Generic)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skills/perf/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: perf
|
|
|
3
3
|
description: Performance regression gate. Detects N+1 queries, sync-in-async, missing indexes, memory leaks, and bundle bloat before they reach production.
|
|
4
4
|
metadata:
|
|
5
5
|
author: runedev
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.2.0"
|
|
7
7
|
layer: L2
|
|
8
8
|
model: sonnet
|
|
9
9
|
group: quality
|
|
@@ -38,6 +38,11 @@ Performance regression gate. Analyzes code changes for patterns that cause measu
|
|
|
38
38
|
- `review` (L2): performance patterns detected in diff
|
|
39
39
|
- `deploy` (L2): pre-deploy perf regression check
|
|
40
40
|
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
- `references/cost-reference.md` — Cost priority hierarchy, quick wins checklist, instance right-sizing, data transfer traps, serverless optimization, observability cost control, managed vs self-hosted matrix, unit economics tracking. Load when cost analysis or FinOps context detected.
|
|
44
|
+
- `references/scalability-reference.md` — Bottleneck identification flow, performance thresholds, API patterns (cursor pagination, rate limiting, circuit breaker, graceful shutdown), caching strategies, queue-based load leveling, concurrency patterns, K8s HPA, CDN headers, load testing. Load when scaling or infrastructure optimization context detected.
|
|
45
|
+
|
|
41
46
|
## Executable Steps
|
|
42
47
|
|
|
43
48
|
### Step 1 — Scope
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Cost Optimization Reference
|
|
2
|
+
|
|
3
|
+
> Loaded by `perf` skill when cost analysis, cloud spending, or FinOps context is detected.
|
|
4
|
+
> Patterns: production-proven cost reduction strategies with dollar-impact estimates.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Cost Priority Hierarchy
|
|
9
|
+
|
|
10
|
+
Optimize in this order — higher tiers save 10x more than lower:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
1. Architecture choices (10x impact — monolith vs micro, serverless vs containers)
|
|
14
|
+
2. Data transfer (NAT Gateway, cross-region, CDN vs origin)
|
|
15
|
+
3. Compute right-sizing (instance types, spot/reserved, autoscaling)
|
|
16
|
+
4. Database optimization (query tuning, connection pooling, read replicas)
|
|
17
|
+
5. Caching layer (Redis, CDN, in-memory — ROI depends on hit rate)
|
|
18
|
+
6. Storage tiering (S3 lifecycle, cold storage, cleanup)
|
|
19
|
+
7. Bundle/asset size (tree-shaking, image optimization, compression)
|
|
20
|
+
8. Observability costs (log sampling, trace sampling, retention)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quick Wins Checklist
|
|
26
|
+
|
|
27
|
+
| Fix | Typical Savings | Effort |
|
|
28
|
+
|-----|----------------|--------|
|
|
29
|
+
| S3 Intelligent-Tiering on infrequent buckets | 40-70% storage | 5 min |
|
|
30
|
+
| Fix N+1 queries (Prisma `include`) | 10-100x query reduction | 30 min |
|
|
31
|
+
| WebP/AVIF images (Sharp pipeline) | 25-80% bandwidth | 1 hr |
|
|
32
|
+
| Add CDN for static assets | 50-90% origin traffic | 1 hr |
|
|
33
|
+
| NAT Gateway → VPC endpoints for AWS services | $30-100/mo per service | 30 min |
|
|
34
|
+
| Log sampling (10% INFO, 100% ERROR) | 50-80% observability bill | 30 min |
|
|
35
|
+
| Gzip/Brotli response compression | 70-90% transfer size | 15 min |
|
|
36
|
+
| Lambda memory right-sizing (power tuning) | 10-40% Lambda cost | 1 hr |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Instance Right-Sizing
|
|
41
|
+
|
|
42
|
+
### AWS EC2 Strategy
|
|
43
|
+
|
|
44
|
+
| Strategy | Savings | Commitment |
|
|
45
|
+
|----------|---------|-----------|
|
|
46
|
+
| On-Demand | Baseline | None |
|
|
47
|
+
| Spot Instances | 60-90% | Can be interrupted |
|
|
48
|
+
| Reserved (1-yr) | ~40% | 1 year |
|
|
49
|
+
| Reserved (3-yr) | ~60% | 3 years |
|
|
50
|
+
| Savings Plans | ~30-40% | Flexible commitment |
|
|
51
|
+
|
|
52
|
+
### Kubernetes Right-Sizing
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
# Set requests = actual P95 usage, limits = 2x requests
|
|
56
|
+
resources:
|
|
57
|
+
requests:
|
|
58
|
+
cpu: "250m" # Based on actual P95 CPU usage
|
|
59
|
+
memory: "256Mi" # Based on actual P95 memory
|
|
60
|
+
limits:
|
|
61
|
+
cpu: "500m"
|
|
62
|
+
memory: "512Mi"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Check actual usage: `kubectl top pods --sort-by=cpu`
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Data Transfer Cost Traps
|
|
70
|
+
|
|
71
|
+
### NAT Gateway Problem
|
|
72
|
+
|
|
73
|
+
NAT Gateway: **$0.045/GB + $0.045/hr** — one of the most expensive AWS surprises.
|
|
74
|
+
|
|
75
|
+
**Fix:** Replace with VPC endpoints for AWS services:
|
|
76
|
+
```
|
|
77
|
+
S3 Gateway Endpoint → Free (gateway type)
|
|
78
|
+
DynamoDB Gateway Endpoint → Free (gateway type)
|
|
79
|
+
SQS/SNS Interface Endpoint → $0.01/hr (still 4x cheaper than NAT)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Cross-Region Transfer
|
|
83
|
+
|
|
84
|
+
- Same region, same AZ: **Free**
|
|
85
|
+
- Same region, different AZ: **$0.01/GB**
|
|
86
|
+
- Cross-region: **$0.02/GB**
|
|
87
|
+
- Internet egress: **$0.09/GB** (first 10TB)
|
|
88
|
+
|
|
89
|
+
**Rule:** Keep services in the same AZ when possible. Use CloudFront for global distribution.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Serverless Optimization
|
|
94
|
+
|
|
95
|
+
### Lambda Memory Tuning
|
|
96
|
+
|
|
97
|
+
Lambda CPU scales linearly with memory. Sometimes MORE memory = CHEAPER (faster execution):
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
128MB, 3000ms = 375,000 GB-ms = $0.00000625
|
|
101
|
+
512MB, 800ms = 400,000 GB-ms = $0.00000667 (similar cost, 4x faster)
|
|
102
|
+
1024MB, 400ms = 400,000 GB-ms = $0.00000667 (same cost, 7.5x faster)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use [AWS Lambda Power Tuning](https://github.com/alexcasalboni/aws-lambda-power-tuning) to find optimal memory.
|
|
106
|
+
|
|
107
|
+
### Cold Start Reduction
|
|
108
|
+
|
|
109
|
+
| Technique | Impact |
|
|
110
|
+
|-----------|--------|
|
|
111
|
+
| Smaller deployment package | -100-500ms |
|
|
112
|
+
| Use Graviton (arm64) | -200ms + 20% cheaper |
|
|
113
|
+
| Provisioned concurrency | Eliminates cold starts |
|
|
114
|
+
| Lazy-load heavy SDKs | -200-1000ms |
|
|
115
|
+
| Use ESM instead of CJS | -100-300ms (Node.js) |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Observability Cost Control
|
|
120
|
+
|
|
121
|
+
### Log Sampling
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Sample 10% of INFO logs, keep 100% of errors
|
|
125
|
+
const shouldLog = (level: string): boolean => {
|
|
126
|
+
if (level === 'error' || level === 'warn') return true;
|
|
127
|
+
return Math.random() < 0.1; // 10% sampling
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Trace Sampling
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// 100% errors and slow requests, 10% normal
|
|
135
|
+
const shouldTrace = (duration: number, isError: boolean): boolean => {
|
|
136
|
+
if (isError) return true;
|
|
137
|
+
if (duration > 1000) return true; // Slow requests
|
|
138
|
+
return Math.random() < 0.1;
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### High-Cardinality Metrics — NEVER DO
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// BAD: Creates millions of unique time series
|
|
146
|
+
metrics.counter('requests', { userId: req.userId }); // ← kills Datadog bill
|
|
147
|
+
|
|
148
|
+
// GOOD: Low-cardinality labels only
|
|
149
|
+
metrics.counter('requests', { endpoint: '/api/users', method: 'GET', status: '200' });
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Retention Policies
|
|
153
|
+
|
|
154
|
+
| Data Type | Recommended Retention |
|
|
155
|
+
|-----------|----------------------|
|
|
156
|
+
| Raw logs | 7-14 days |
|
|
157
|
+
| Aggregated metrics | 90 days |
|
|
158
|
+
| Error logs | 30-90 days |
|
|
159
|
+
| Traces | 7 days |
|
|
160
|
+
| Audit logs | 1-7 years (compliance) |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Managed vs Self-Hosted Decision Matrix
|
|
165
|
+
|
|
166
|
+
| Service | Self-Host When | Stay Managed When |
|
|
167
|
+
|---------|---------------|-------------------|
|
|
168
|
+
| Auth | >200K MAU ($4K+/mo managed) | <200K MAU or need compliance |
|
|
169
|
+
| Search | >500K records or >$500/mo | <500K records, need instant setup |
|
|
170
|
+
| Database | >$500/mo RDS bill | Need HA, backups, patching handled |
|
|
171
|
+
| Email | Almost never | Always (deliverability is hard) |
|
|
172
|
+
| Monitoring | >$1K/mo Datadog | Need APM + distributed tracing |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Unit Economics Tracking
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Track cost per unit to detect inefficiency trends
|
|
180
|
+
interface UnitEconomics {
|
|
181
|
+
costPerRequest: number; // Total infra / total requests
|
|
182
|
+
costPerUser: number; // Total infra / MAU
|
|
183
|
+
costPerTransaction: number; // Total infra / transactions
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Alert if unit cost increases >20% month-over-month
|
|
187
|
+
function checkCostTrend(current: number, previous: number): void {
|
|
188
|
+
const increase = (current - previous) / previous;
|
|
189
|
+
if (increase > 0.2) {
|
|
190
|
+
alert(`Unit cost increased ${(increase * 100).toFixed(0)}% — investigate`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Cost Optimization Priority Matrix
|
|
198
|
+
|
|
199
|
+
| Effort | Low Savings (<$100/mo) | Med Savings ($100-500) | High Savings (>$500) |
|
|
200
|
+
|--------|----------------------|----------------------|---------------------|
|
|
201
|
+
| **Low** | Log retention | Image optimization | S3 tiering |
|
|
202
|
+
| **Medium** | Bundle analysis | Caching layer | Instance right-sizing |
|
|
203
|
+
| **High** | — | Query optimization | Architecture redesign |
|
|
204
|
+
|
|
205
|
+
**Rule:** Start top-right (high savings, low effort), work diagonally down-left.
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Scalability Reference
|
|
2
|
+
|
|
3
|
+
> Loaded by `perf` skill when scaling, load handling, or infrastructure optimization context is detected.
|
|
4
|
+
> Patterns: production-proven scalability strategies for Node.js/TypeScript applications.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Bottleneck Identification Flow
|
|
9
|
+
|
|
10
|
+
Profile BEFORE optimizing. Follow this decision tree:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Is database the bottleneck (>50% of response time)?
|
|
14
|
+
├── YES → Index optimization, connection pooling, read replicas, query caching
|
|
15
|
+
│ See db/references/scaling-reference.md
|
|
16
|
+
└── NO
|
|
17
|
+
Is external API the bottleneck?
|
|
18
|
+
├── YES → Circuit breaker, caching, parallel requests, queue background
|
|
19
|
+
└── NO
|
|
20
|
+
Is it CPU-bound?
|
|
21
|
+
├── YES → Worker threads, horizontal scaling, algorithm optimization
|
|
22
|
+
└── NO
|
|
23
|
+
Is it memory pressure?
|
|
24
|
+
├── YES → Leak detection, streaming, pagination, bounded caches
|
|
25
|
+
└── NO → Measure again with better instrumentation
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Performance Thresholds
|
|
29
|
+
|
|
30
|
+
| Metric | Healthy | Warning | Critical |
|
|
31
|
+
|--------|---------|---------|----------|
|
|
32
|
+
| p99 latency | <200ms | 200-500ms | >500ms |
|
|
33
|
+
| DB cache hit ratio | >95% | 90-95% | <90% |
|
|
34
|
+
| Connection pool utilization | <70% | 70-85% | >85% |
|
|
35
|
+
| CPU utilization | <60% | 60-80% | >80% |
|
|
36
|
+
| Memory utilization | <70% | 70-85% | >85% |
|
|
37
|
+
| Error rate | <0.1% | 0.1-1% | >1% |
|
|
38
|
+
| Queue depth | <100 | 100-1000 | >1000 |
|
|
39
|
+
| Queue wait time | <1s | 1-10s | >10s |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## API Scalability Patterns
|
|
44
|
+
|
|
45
|
+
### Cursor-Based Pagination
|
|
46
|
+
|
|
47
|
+
Offset pagination breaks at scale (`OFFSET 100000` = scan 100K rows). Use cursor-based:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Cursor-based — consistent performance at any depth
|
|
51
|
+
async function listOrders(cursor?: string, limit = 20) {
|
|
52
|
+
const where = cursor ? { id: { gt: cursor } } : {};
|
|
53
|
+
const items = await prisma.order.findMany({
|
|
54
|
+
where,
|
|
55
|
+
take: limit + 1, // Fetch one extra to detect hasMore
|
|
56
|
+
orderBy: { id: 'asc' },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const hasMore = items.length > limit;
|
|
60
|
+
const data = hasMore ? items.slice(0, -1) : items;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
data,
|
|
64
|
+
cursor: data.at(-1)?.id ?? null,
|
|
65
|
+
hasMore,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Rate Limiting (Tiered)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const rateLimits = {
|
|
74
|
+
free: { requests: 100, window: '15m' },
|
|
75
|
+
pro: { requests: 1000, window: '15m' },
|
|
76
|
+
enterprise: { requests: 10000, window: '15m' },
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Response headers (always include)
|
|
80
|
+
// X-RateLimit-Limit: 1000
|
|
81
|
+
// X-RateLimit-Remaining: 847
|
|
82
|
+
// X-RateLimit-Reset: 1699234567
|
|
83
|
+
// Retry-After: 30 (only on 429)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Circuit Breaker
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import CircuitBreaker from 'opossum';
|
|
90
|
+
|
|
91
|
+
const breaker = new CircuitBreaker(callExternalAPI, {
|
|
92
|
+
timeout: 3000, // Fail if function takes > 3s
|
|
93
|
+
errorThresholdPercentage: 50, // Open if 50% fail
|
|
94
|
+
resetTimeout: 30000, // Try again after 30s
|
|
95
|
+
volumeThreshold: 10, // Min calls before tripping
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
breaker.fallback(() => cachedResponse);
|
|
99
|
+
breaker.on('open', () => logger.warn('Circuit breaker OPEN'));
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Graceful Shutdown
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
async function shutdown(signal: string) {
|
|
106
|
+
logger.info(`${signal} received — starting graceful shutdown`);
|
|
107
|
+
|
|
108
|
+
// 1. Stop accepting new connections
|
|
109
|
+
server.close();
|
|
110
|
+
|
|
111
|
+
// 2. Finish in-flight requests (with timeout)
|
|
112
|
+
await Promise.race([
|
|
113
|
+
finishInflightRequests(),
|
|
114
|
+
new Promise(resolve => setTimeout(resolve, 30000)),
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
// 3. Close DB connections
|
|
118
|
+
await prisma.$disconnect();
|
|
119
|
+
await redis.quit();
|
|
120
|
+
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
125
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Caching Strategies
|
|
131
|
+
|
|
132
|
+
### Strategy Selection
|
|
133
|
+
|
|
134
|
+
| Strategy | When to Use | Trade-off |
|
|
135
|
+
|----------|-------------|-----------|
|
|
136
|
+
| **Cache-aside** | Most common. App checks cache, falls back to DB | May serve stale data |
|
|
137
|
+
| **Write-through** | Cache updated on every write | Higher write latency |
|
|
138
|
+
| **Write-behind** | Cache updated async after write | Risk of data loss |
|
|
139
|
+
| **Read-through** | Cache auto-fetches on miss | Cache library dependency |
|
|
140
|
+
|
|
141
|
+
### TTL Guidelines
|
|
142
|
+
|
|
143
|
+
| Data Type | TTL | Rationale |
|
|
144
|
+
|-----------|-----|-----------|
|
|
145
|
+
| Static config | 1 hour | Rarely changes |
|
|
146
|
+
| User profile | 5 min | Changes occasionally |
|
|
147
|
+
| Product catalog | 15 min | Balance freshness vs load |
|
|
148
|
+
| Session data | 30 min sliding | Security + UX |
|
|
149
|
+
| Rate limit counters | Match window | Exact timing matters |
|
|
150
|
+
| Search results | 60s | High-read, low-freshness need |
|
|
151
|
+
|
|
152
|
+
### Cache Invalidation Patterns
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// 1. TTL-based (simplest, eventual consistency)
|
|
156
|
+
await redis.setex(`user:${id}`, 300, JSON.stringify(user));
|
|
157
|
+
|
|
158
|
+
// 2. Explicit invalidation (strongest consistency)
|
|
159
|
+
await prisma.user.update({ where: { id }, data });
|
|
160
|
+
await redis.del(`user:${id}`);
|
|
161
|
+
|
|
162
|
+
// 3. Tag-based (invalidate groups)
|
|
163
|
+
await redis.sadd('tag:users', `user:${id}`, `user:${id}:posts`);
|
|
164
|
+
// On invalidate:
|
|
165
|
+
const keys = await redis.smembers('tag:users');
|
|
166
|
+
await redis.del(...keys);
|
|
167
|
+
|
|
168
|
+
// 4. Versioned keys (zero-downtime cache migration)
|
|
169
|
+
const version = 'v2';
|
|
170
|
+
await redis.setex(`user:${version}:${id}`, 300, data);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Queue-Based Load Leveling
|
|
176
|
+
|
|
177
|
+
### When to Use Queues
|
|
178
|
+
|
|
179
|
+
- Request takes > 500ms to process
|
|
180
|
+
- External API with rate limits
|
|
181
|
+
- Spiky traffic patterns
|
|
182
|
+
- Fire-and-forget operations (email, webhooks, analytics)
|
|
183
|
+
|
|
184
|
+
### BullMQ Pattern
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { Queue, Worker } from 'bullmq';
|
|
188
|
+
|
|
189
|
+
const emailQueue = new Queue('emails', { connection: redis });
|
|
190
|
+
|
|
191
|
+
// Producer — returns immediately
|
|
192
|
+
await emailQueue.add('welcome', { userId, email }, {
|
|
193
|
+
attempts: 3,
|
|
194
|
+
backoff: { type: 'exponential', delay: 1000 },
|
|
195
|
+
removeOnComplete: 1000, // Keep last 1000 completed
|
|
196
|
+
removeOnFail: 5000, // Keep last 5000 failed
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Consumer — processes in background
|
|
200
|
+
const worker = new Worker('emails', async (job) => {
|
|
201
|
+
await sendEmail(job.data.email, 'Welcome!');
|
|
202
|
+
}, {
|
|
203
|
+
connection: redis,
|
|
204
|
+
concurrency: 5, // Process 5 emails in parallel
|
|
205
|
+
limiter: { max: 10, duration: 1000 }, // Rate limit: 10/sec
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Concurrency Patterns
|
|
212
|
+
|
|
213
|
+
### Worker Threads (CPU-intensive)
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
|
|
217
|
+
|
|
218
|
+
if (isMainThread) {
|
|
219
|
+
const worker = new Worker(__filename, { workerData: { input } });
|
|
220
|
+
worker.on('message', (result) => resolve(result));
|
|
221
|
+
worker.on('error', reject);
|
|
222
|
+
} else {
|
|
223
|
+
const result = heavyComputation(workerData.input);
|
|
224
|
+
parentPort?.postMessage(result);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Backpressure (prevent OOM)
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// Bounded queue — reject when full instead of consuming infinite memory
|
|
232
|
+
class BoundedQueue<T> {
|
|
233
|
+
private queue: T[] = [];
|
|
234
|
+
constructor(private maxSize: number) {}
|
|
235
|
+
|
|
236
|
+
enqueue(item: T): boolean {
|
|
237
|
+
if (this.queue.length >= this.maxSize) return false; // Apply backpressure
|
|
238
|
+
this.queue.push(item);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Semaphore (limit concurrent operations)
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
class Semaphore {
|
|
248
|
+
private current = 0;
|
|
249
|
+
private queue: (() => void)[] = [];
|
|
250
|
+
|
|
251
|
+
constructor(private max: number) {}
|
|
252
|
+
|
|
253
|
+
async acquire(): Promise<void> {
|
|
254
|
+
if (this.current < this.max) {
|
|
255
|
+
this.current++;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
return new Promise((resolve) => this.queue.push(resolve));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
release(): void {
|
|
262
|
+
this.current--;
|
|
263
|
+
const next = this.queue.shift();
|
|
264
|
+
if (next) { this.current++; next(); }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Usage: limit to 10 concurrent DB connections
|
|
269
|
+
const sem = new Semaphore(10);
|
|
270
|
+
await sem.acquire();
|
|
271
|
+
try { await db.query(sql); }
|
|
272
|
+
finally { sem.release(); }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Deployment Scalability
|
|
278
|
+
|
|
279
|
+
### Kubernetes HPA
|
|
280
|
+
|
|
281
|
+
```yaml
|
|
282
|
+
apiVersion: autoscaling/v2
|
|
283
|
+
kind: HorizontalPodAutoscaler
|
|
284
|
+
metadata:
|
|
285
|
+
name: api-hpa
|
|
286
|
+
spec:
|
|
287
|
+
scaleTargetRef:
|
|
288
|
+
apiVersion: apps/v1
|
|
289
|
+
kind: Deployment
|
|
290
|
+
name: api
|
|
291
|
+
minReplicas: 2
|
|
292
|
+
maxReplicas: 20
|
|
293
|
+
metrics:
|
|
294
|
+
- type: Resource
|
|
295
|
+
resource:
|
|
296
|
+
name: cpu
|
|
297
|
+
target:
|
|
298
|
+
type: Utilization
|
|
299
|
+
averageUtilization: 70
|
|
300
|
+
behavior:
|
|
301
|
+
scaleUp:
|
|
302
|
+
stabilizationWindowSeconds: 60
|
|
303
|
+
scaleDown:
|
|
304
|
+
stabilizationWindowSeconds: 300 # Slow scale-down prevents flapping
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### CDN Cache-Control Headers
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
# Immutable assets (hashed filenames)
|
|
311
|
+
Cache-Control: public, max-age=31536000, immutable
|
|
312
|
+
|
|
313
|
+
# API responses (short cache + revalidation)
|
|
314
|
+
Cache-Control: public, max-age=60, stale-while-revalidate=30
|
|
315
|
+
|
|
316
|
+
# Private user data
|
|
317
|
+
Cache-Control: private, no-store
|
|
318
|
+
|
|
319
|
+
# HTML pages (revalidate every time)
|
|
320
|
+
Cache-Control: no-cache
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Zero-Downtime Database Migrations
|
|
324
|
+
|
|
325
|
+
3-phase safe migration pattern:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
Phase 1: ADD new column (nullable) → deploy
|
|
329
|
+
Phase 2: Deploy code that writes to BOTH old + new columns → backfill
|
|
330
|
+
Phase 3: Drop old column → deploy
|
|
331
|
+
|
|
332
|
+
NEVER in one step:
|
|
333
|
+
- Rename column (breaks running code)
|
|
334
|
+
- Change type (data loss risk)
|
|
335
|
+
- Add NOT NULL without default (fails on existing rows)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Load Testing
|
|
341
|
+
|
|
342
|
+
### k6 Quick Start
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
import http from 'k6/http';
|
|
346
|
+
import { check, sleep } from 'k6';
|
|
347
|
+
|
|
348
|
+
export const options = {
|
|
349
|
+
stages: [
|
|
350
|
+
{ duration: '1m', target: 50 }, // Ramp up
|
|
351
|
+
{ duration: '3m', target: 50 }, // Steady state
|
|
352
|
+
{ duration: '1m', target: 0 }, // Ramp down
|
|
353
|
+
],
|
|
354
|
+
thresholds: {
|
|
355
|
+
http_req_duration: ['p(99)<500'], // 99th percentile < 500ms
|
|
356
|
+
http_req_failed: ['rate<0.01'], // Error rate < 1%
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
export default function () {
|
|
361
|
+
const res = http.get('https://api.example.com/orders');
|
|
362
|
+
check(res, { 'status 200': (r) => r.status === 200 });
|
|
363
|
+
sleep(1);
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### What to Measure
|
|
368
|
+
|
|
369
|
+
| Metric | Target | Why |
|
|
370
|
+
|--------|--------|-----|
|
|
371
|
+
| p50 latency | <100ms | Typical user experience |
|
|
372
|
+
| p99 latency | <500ms | Worst-case user experience |
|
|
373
|
+
| Throughput (RPS) | >baseline | Capacity headroom |
|
|
374
|
+
| Error rate | <0.1% | Reliability |
|
|
375
|
+
| CPU during load | <80% | Scaling headroom |
|
|
376
|
+
| Memory during load | <70% | Leak detection |
|
|
377
|
+
|
|
378
|
+
**Rule:** Always observe p99, not p50. Median latency hides tail latency that kills user experience.
|