@ryuenn3123/agentic-senior-core 1.8.2 → 1.9.1
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/.agent-context/blueprints/mobile-app.md +21 -21
- package/.agent-context/policies/llm-judge-threshold.json +19 -19
- package/.agent-context/profiles/platform.md +13 -13
- package/.agent-context/profiles/regulated.md +13 -13
- package/.agent-context/profiles/startup.md +13 -13
- package/.agent-context/review-checklists/frontend-skill-parity.md +28 -28
- package/.agent-context/review-checklists/frontend-usability.md +33 -33
- package/.agent-context/review-checklists/release-operations.md +29 -29
- package/.agent-context/skills/README.md +62 -62
- package/.agent-context/skills/backend/README.md +67 -67
- package/.agent-context/skills/backend/architecture.md +360 -360
- package/.agent-context/skills/backend/data-access.md +230 -230
- package/.agent-context/skills/backend/errors.md +137 -137
- package/.agent-context/skills/backend/validation.md +116 -116
- package/.agent-context/skills/backend.md +28 -28
- package/.agent-context/skills/cli/README.md +49 -49
- package/.agent-context/skills/cli/init.md +37 -37
- package/.agent-context/skills/cli/output.md +35 -35
- package/.agent-context/skills/cli/upgrade.md +37 -37
- package/.agent-context/skills/cli.md +28 -28
- package/.agent-context/skills/distribution/README.md +18 -18
- package/.agent-context/skills/distribution/compatibility.md +31 -31
- package/.agent-context/skills/distribution/publish.md +36 -36
- package/.agent-context/skills/distribution/rollback.md +31 -31
- package/.agent-context/skills/distribution.md +28 -28
- package/.agent-context/skills/frontend/README.md +35 -35
- package/.agent-context/skills/frontend/accessibility.md +107 -107
- package/.agent-context/skills/frontend/motion.md +66 -66
- package/.agent-context/skills/frontend/performance.md +62 -62
- package/.agent-context/skills/frontend/ui-architecture.md +128 -128
- package/.agent-context/skills/frontend.md +29 -29
- package/.agent-context/skills/fullstack/README.md +18 -18
- package/.agent-context/skills/fullstack/contracts.md +52 -52
- package/.agent-context/skills/fullstack/end-to-end.md +41 -41
- package/.agent-context/skills/fullstack/feature-slicing.md +64 -64
- package/.agent-context/skills/fullstack.md +26 -26
- package/.agent-context/skills/index.json +107 -107
- package/.agent-context/skills/review-quality/README.md +18 -18
- package/.agent-context/skills/review-quality/benchmark.md +29 -29
- package/.agent-context/skills/review-quality/planning.md +37 -37
- package/.agent-context/skills/review-quality/security.md +33 -33
- package/.agent-context/skills/review-quality.md +27 -27
- package/.agent-context/stacks/flutter.md +16 -16
- package/.agent-context/stacks/react-native.md +16 -16
- package/.agent-context/state/architecture-map.md +25 -25
- package/.agent-context/state/benchmark-analysis.json +431 -431
- package/.agent-context/state/benchmark-thresholds.json +10 -10
- package/.agent-context/state/benchmark-watchlist.json +19 -19
- package/.agent-context/state/dependency-map.md +32 -32
- package/.agent-context/state/skill-platform.json +38 -38
- package/.agent-override.md +36 -36
- package/.cursorrules +140 -140
- package/.github/ISSUE_TEMPLATE/v1.7-frontend-work-item.yml +54 -54
- package/.github/copilot-instructions.md +62 -0
- package/.github/workflows/benchmark-detection.yml +38 -38
- package/.github/workflows/benchmark-intelligence.yml +50 -50
- package/.github/workflows/frontend-usability-gate.yml +36 -36
- package/.github/workflows/publish.yml +32 -0
- package/.github/workflows/release-gate.yml +32 -32
- package/.github/workflows/sbom-compliance.yml +32 -32
- package/.windsurfrules +106 -106
- package/AGENTS.md +181 -131
- package/README.md +318 -318
- package/bin/agentic-senior-core.js +1556 -1556
- package/mcp.json +92 -29
- package/package.json +1 -1
- package/scripts/benchmark-gate.mjs +121 -121
- package/scripts/benchmark-intelligence.mjs +140 -140
- package/scripts/detection-benchmark.mjs +138 -138
- package/scripts/frontend-usability-audit.mjs +87 -87
- package/scripts/generate-sbom.mjs +61 -61
- package/scripts/init-project.ps1 +104 -104
- package/scripts/llm-judge.mjs +664 -664
- package/scripts/release-gate.mjs +116 -116
- package/scripts/skill-tier-policy.mjs +75 -75
- package/scripts/validate.mjs +636 -636
|
@@ -1,231 +1,231 @@
|
|
|
1
|
-
# Database Patterns & Query Optimization
|
|
2
|
-
|
|
3
|
-
**Tier:** EXPERT | **Source:** awesome-copilot (schema design) + antigravity (N+1 patterns) + minimax (Django ORM)
|
|
4
|
-
|
|
5
|
-
## Part 1: Schema Design (3NF Normalization)
|
|
6
|
-
|
|
7
|
-
### The Problem: Data Redundancy
|
|
8
|
-
|
|
9
|
-
**Denormalized (redundant):**
|
|
10
|
-
```sql
|
|
11
|
-
CREATE TABLE orders (
|
|
12
|
-
id UUID PRIMARY KEY,
|
|
13
|
-
customer_name VARCHAR(255), -- Redundant: can look up from customers table
|
|
14
|
-
customer_email VARCHAR(255), -- Redundant
|
|
15
|
-
customer_tier VARCHAR(50), -- Redundant
|
|
16
|
-
amount DECIMAL(10,2),
|
|
17
|
-
created_at TIMESTAMP
|
|
18
|
-
);
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Problem: Update customer name -> must update in `orders` table too (data inconsistency risk).
|
|
22
|
-
|
|
23
|
-
### **3NF (Normalized):**
|
|
24
|
-
|
|
25
|
-
```sql
|
|
26
|
-
CREATE TABLE customers (
|
|
27
|
-
id UUID PRIMARY KEY,
|
|
28
|
-
name VARCHAR(255),
|
|
29
|
-
email VARCHAR(255),
|
|
30
|
-
tier VARCHAR(50),
|
|
31
|
-
created_at TIMESTAMP
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
CREATE TABLE orders (
|
|
35
|
-
id UUID PRIMARY KEY,
|
|
36
|
-
customer_id UUID NOT NULL REFERENCES customers(id),
|
|
37
|
-
amount DECIMAL(10,2),
|
|
38
|
-
created_at TIMESTAMP
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
-- To get order with customer name:
|
|
42
|
-
SELECT o.id, o.amount, c.name
|
|
43
|
-
FROM orders o
|
|
44
|
-
JOIN customers c ON o.customer_id = c.id;
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Benefits:**
|
|
48
|
-
- Single source of truth (customer name in one place)
|
|
49
|
-
- Updates atomic (change name, not duplicated)
|
|
50
|
-
- Consistent joins
|
|
51
|
-
|
|
52
|
-
### When to Break 3NF (Denormalization)
|
|
53
|
-
|
|
54
|
-
Denormalize **only if** profiling shows JOIN is bottleneck:
|
|
55
|
-
|
|
56
|
-
```sql
|
|
57
|
-
-- ONLY if "SELECT ... JOIN customers" is slow:
|
|
58
|
-
ALTER TABLE orders ADD COLUMN customer_name VARCHAR(255);
|
|
59
|
-
|
|
60
|
-
-- Keep redundancy in sync with trigger:
|
|
61
|
-
CREATE TRIGGER sync_customer_name
|
|
62
|
-
AFTER UPDATE ON customers
|
|
63
|
-
FOR EACH ROW
|
|
64
|
-
UPDATE orders SET customer_name = NEW.name WHERE customer_id = NEW.id;
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Part 2: Indexes (FK + Query Optimization)
|
|
70
|
-
|
|
71
|
-
### Rule: Always Index Foreign Keys
|
|
72
|
-
|
|
73
|
-
```sql
|
|
74
|
-
CREATE TABLE orders (
|
|
75
|
-
id UUID PRIMARY KEY,
|
|
76
|
-
customer_id UUID NOT NULL REFERENCES customers(id),
|
|
77
|
-
created_at TIMESTAMP,
|
|
78
|
-
|
|
79
|
-
-- Foreign key queries need fast lookup
|
|
80
|
-
INDEX idx_customer_id (customer_id),
|
|
81
|
-
-- Query by date also common
|
|
82
|
-
INDEX idx_created_at (created_at)
|
|
83
|
-
);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Without index on `customer_id`, query `SELECT * FROM orders WHERE customer_id = ?` scans entire table (O(n)).
|
|
87
|
-
|
|
88
|
-
With index, it's O(log n) via B-tree lookup.
|
|
89
|
-
|
|
90
|
-
### Composite Indexes (When Queries Filter by Multiple Columns)
|
|
91
|
-
|
|
92
|
-
```sql
|
|
93
|
-
-- If queries often filter by (customer_id, created_at > date):
|
|
94
|
-
INDEX idx_customer_date (customer_id, created_at DESC);
|
|
95
|
-
|
|
96
|
-
-- Good for: SELECT * FROM orders WHERE customer_id = ? AND created_at > ?
|
|
97
|
-
-- Bad for: SELECT * FROM orders WHERE created_at > ? (first column must match)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Part 3: Query Patterns (Anti-Patterns)
|
|
103
|
-
|
|
104
|
-
### Anti-Pattern #1: N+1 Query Problem
|
|
105
|
-
|
|
106
|
-
**WRONG:**
|
|
107
|
-
```javascript
|
|
108
|
-
const customers = await db.query('SELECT * FROM customers');
|
|
109
|
-
for (const customer of customers) {
|
|
110
|
-
customer.orders = await db.query(
|
|
111
|
-
'SELECT * FROM orders WHERE customer_id = $1',
|
|
112
|
-
[customer.id] // <- Executes once per customer (N+1 queries!)
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Cost:** 1 query (customers) + N queries (orders per customer) = **N+1 total**. If 100 customers, 101 queries.
|
|
118
|
-
|
|
119
|
-
**CORRECT (JOIN):**
|
|
120
|
-
```javascript
|
|
121
|
-
const results = await db.query(`
|
|
122
|
-
SELECT
|
|
123
|
-
c.id as customer_id,
|
|
124
|
-
c.name,
|
|
125
|
-
o.id as order_id,
|
|
126
|
-
o.amount
|
|
127
|
-
FROM customers c
|
|
128
|
-
LEFT JOIN orders o ON c.id = o.customer_id
|
|
129
|
-
`);
|
|
130
|
-
|
|
131
|
-
// Post-process to group
|
|
132
|
-
const customersWithOrders = {};
|
|
133
|
-
for (const row of results) {
|
|
134
|
-
if (!customersWithOrders[row.customer_id]) {
|
|
135
|
-
customersWithOrders[row.customer_id] = {
|
|
136
|
-
id: row.customer_id,
|
|
137
|
-
name: row.name,
|
|
138
|
-
orders: []
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
if (row.order_id) {
|
|
142
|
-
customersWithOrders[row.customer_id].orders.push({ id: row.order_id, amount: row.amount });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
**Cost:** 1 JOIN query (fast).
|
|
148
|
-
|
|
149
|
-
### Anti-Pattern #2: SELECT * (Load Unnecessary Columns)
|
|
150
|
-
|
|
151
|
-
**WRONG:**
|
|
152
|
-
```javascript
|
|
153
|
-
const customer = await db.one('SELECT * FROM customers WHERE id = $1', [id]);
|
|
154
|
-
console.log(customer.name); // But loaded email, phone, address too
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**CORRECT (Project columns):**
|
|
158
|
-
```javascript
|
|
159
|
-
const customer = await db.one(
|
|
160
|
-
'SELECT id, name, email FROM customers WHERE id = $1',
|
|
161
|
-
[id] // Only load what's needed
|
|
162
|
-
);
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
## Part 4: Safe Zero-Downtime Migrations
|
|
168
|
-
|
|
169
|
-
### The Problem: Data Migrations Block Requests
|
|
170
|
-
|
|
171
|
-
```sql
|
|
172
|
-
ALTER TABLE customers ADD COLUMN phone VARCHAR(20);
|
|
173
|
-
-- ^ Blocks all INSERT/UPDATE on customers table during migration (can be seconds~minutes for large tables)
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### Strategy: Backward-Compatible Migrations
|
|
177
|
-
|
|
178
|
-
**Step 1: Add column (no default = already nullable):**
|
|
179
|
-
```sql
|
|
180
|
-
ALTER TABLE customers ADD COLUMN phone VARCHAR(20);
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**Step 2: Backfill data (in batches to avoid lock):**
|
|
184
|
-
```javascript
|
|
185
|
-
// Run offline / in background job
|
|
186
|
-
const BATCH_SIZE = 10000;
|
|
187
|
-
let offset = 0;
|
|
188
|
-
while (true) {
|
|
189
|
-
const updated = await db.query(`
|
|
190
|
-
UPDATE customers
|
|
191
|
-
SET phone = ''
|
|
192
|
-
WHERE id IN (
|
|
193
|
-
SELECT id FROM customers
|
|
194
|
-
WHERE phone IS NULL
|
|
195
|
-
LIMIT $1
|
|
196
|
-
)
|
|
197
|
-
`, [BATCH_SIZE]);
|
|
198
|
-
if (updated.rowCount === 0) break;
|
|
199
|
-
offset += BATCH_SIZE;
|
|
200
|
-
await sleep(1000); // Rate limit to avoid overwhelming DB
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
**Step 3: Code deployment (app handles both old + new):**
|
|
205
|
-
```javascript
|
|
206
|
-
// Service layer reads/writes phone
|
|
207
|
-
if (newCustomer.phone) {
|
|
208
|
-
await customerRepo.update(customerId, { phone: newCustomer.phone });
|
|
209
|
-
}
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Old code still works (ignores phone), new code uses it.
|
|
213
|
-
|
|
214
|
-
**Step 4: After migration successful, remove the column (or rename old):**
|
|
215
|
-
```sql
|
|
216
|
-
-- Keep old column renamed for rollback safety
|
|
217
|
-
ALTER TABLE customers RENAME COLUMN phone_old TO phone_deprecated;
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Checklist
|
|
223
|
-
|
|
224
|
-
- [ ] All tables in 3NF
|
|
225
|
-
- [ ] Foreign keys indexed
|
|
226
|
-
- [ ] JOIN queries avoid N+1 (load related data in one query)
|
|
227
|
-
- [ ] SELECT projects specific columns (not *)
|
|
228
|
-
- [ ] Indexes match WHERE + ORDER BY + JOIN ON patterns
|
|
229
|
-
- [ ] Migrations backward-compatible + batched
|
|
230
|
-
- [ ] Query performance profiled (EXPLAIN ANALYZE)
|
|
1
|
+
# Database Patterns & Query Optimization
|
|
2
|
+
|
|
3
|
+
**Tier:** EXPERT | **Source:** awesome-copilot (schema design) + antigravity (N+1 patterns) + minimax (Django ORM)
|
|
4
|
+
|
|
5
|
+
## Part 1: Schema Design (3NF Normalization)
|
|
6
|
+
|
|
7
|
+
### The Problem: Data Redundancy
|
|
8
|
+
|
|
9
|
+
**Denormalized (redundant):**
|
|
10
|
+
```sql
|
|
11
|
+
CREATE TABLE orders (
|
|
12
|
+
id UUID PRIMARY KEY,
|
|
13
|
+
customer_name VARCHAR(255), -- Redundant: can look up from customers table
|
|
14
|
+
customer_email VARCHAR(255), -- Redundant
|
|
15
|
+
customer_tier VARCHAR(50), -- Redundant
|
|
16
|
+
amount DECIMAL(10,2),
|
|
17
|
+
created_at TIMESTAMP
|
|
18
|
+
);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Problem: Update customer name -> must update in `orders` table too (data inconsistency risk).
|
|
22
|
+
|
|
23
|
+
### **3NF (Normalized):**
|
|
24
|
+
|
|
25
|
+
```sql
|
|
26
|
+
CREATE TABLE customers (
|
|
27
|
+
id UUID PRIMARY KEY,
|
|
28
|
+
name VARCHAR(255),
|
|
29
|
+
email VARCHAR(255),
|
|
30
|
+
tier VARCHAR(50),
|
|
31
|
+
created_at TIMESTAMP
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE orders (
|
|
35
|
+
id UUID PRIMARY KEY,
|
|
36
|
+
customer_id UUID NOT NULL REFERENCES customers(id),
|
|
37
|
+
amount DECIMAL(10,2),
|
|
38
|
+
created_at TIMESTAMP
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
-- To get order with customer name:
|
|
42
|
+
SELECT o.id, o.amount, c.name
|
|
43
|
+
FROM orders o
|
|
44
|
+
JOIN customers c ON o.customer_id = c.id;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Benefits:**
|
|
48
|
+
- Single source of truth (customer name in one place)
|
|
49
|
+
- Updates atomic (change name, not duplicated)
|
|
50
|
+
- Consistent joins
|
|
51
|
+
|
|
52
|
+
### When to Break 3NF (Denormalization)
|
|
53
|
+
|
|
54
|
+
Denormalize **only if** profiling shows JOIN is bottleneck:
|
|
55
|
+
|
|
56
|
+
```sql
|
|
57
|
+
-- ONLY if "SELECT ... JOIN customers" is slow:
|
|
58
|
+
ALTER TABLE orders ADD COLUMN customer_name VARCHAR(255);
|
|
59
|
+
|
|
60
|
+
-- Keep redundancy in sync with trigger:
|
|
61
|
+
CREATE TRIGGER sync_customer_name
|
|
62
|
+
AFTER UPDATE ON customers
|
|
63
|
+
FOR EACH ROW
|
|
64
|
+
UPDATE orders SET customer_name = NEW.name WHERE customer_id = NEW.id;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Part 2: Indexes (FK + Query Optimization)
|
|
70
|
+
|
|
71
|
+
### Rule: Always Index Foreign Keys
|
|
72
|
+
|
|
73
|
+
```sql
|
|
74
|
+
CREATE TABLE orders (
|
|
75
|
+
id UUID PRIMARY KEY,
|
|
76
|
+
customer_id UUID NOT NULL REFERENCES customers(id),
|
|
77
|
+
created_at TIMESTAMP,
|
|
78
|
+
|
|
79
|
+
-- Foreign key queries need fast lookup
|
|
80
|
+
INDEX idx_customer_id (customer_id),
|
|
81
|
+
-- Query by date also common
|
|
82
|
+
INDEX idx_created_at (created_at)
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Without index on `customer_id`, query `SELECT * FROM orders WHERE customer_id = ?` scans entire table (O(n)).
|
|
87
|
+
|
|
88
|
+
With index, it's O(log n) via B-tree lookup.
|
|
89
|
+
|
|
90
|
+
### Composite Indexes (When Queries Filter by Multiple Columns)
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
-- If queries often filter by (customer_id, created_at > date):
|
|
94
|
+
INDEX idx_customer_date (customer_id, created_at DESC);
|
|
95
|
+
|
|
96
|
+
-- Good for: SELECT * FROM orders WHERE customer_id = ? AND created_at > ?
|
|
97
|
+
-- Bad for: SELECT * FROM orders WHERE created_at > ? (first column must match)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Part 3: Query Patterns (Anti-Patterns)
|
|
103
|
+
|
|
104
|
+
### Anti-Pattern #1: N+1 Query Problem
|
|
105
|
+
|
|
106
|
+
**WRONG:**
|
|
107
|
+
```javascript
|
|
108
|
+
const customers = await db.query('SELECT * FROM customers');
|
|
109
|
+
for (const customer of customers) {
|
|
110
|
+
customer.orders = await db.query(
|
|
111
|
+
'SELECT * FROM orders WHERE customer_id = $1',
|
|
112
|
+
[customer.id] // <- Executes once per customer (N+1 queries!)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Cost:** 1 query (customers) + N queries (orders per customer) = **N+1 total**. If 100 customers, 101 queries.
|
|
118
|
+
|
|
119
|
+
**CORRECT (JOIN):**
|
|
120
|
+
```javascript
|
|
121
|
+
const results = await db.query(`
|
|
122
|
+
SELECT
|
|
123
|
+
c.id as customer_id,
|
|
124
|
+
c.name,
|
|
125
|
+
o.id as order_id,
|
|
126
|
+
o.amount
|
|
127
|
+
FROM customers c
|
|
128
|
+
LEFT JOIN orders o ON c.id = o.customer_id
|
|
129
|
+
`);
|
|
130
|
+
|
|
131
|
+
// Post-process to group
|
|
132
|
+
const customersWithOrders = {};
|
|
133
|
+
for (const row of results) {
|
|
134
|
+
if (!customersWithOrders[row.customer_id]) {
|
|
135
|
+
customersWithOrders[row.customer_id] = {
|
|
136
|
+
id: row.customer_id,
|
|
137
|
+
name: row.name,
|
|
138
|
+
orders: []
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (row.order_id) {
|
|
142
|
+
customersWithOrders[row.customer_id].orders.push({ id: row.order_id, amount: row.amount });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Cost:** 1 JOIN query (fast).
|
|
148
|
+
|
|
149
|
+
### Anti-Pattern #2: SELECT * (Load Unnecessary Columns)
|
|
150
|
+
|
|
151
|
+
**WRONG:**
|
|
152
|
+
```javascript
|
|
153
|
+
const customer = await db.one('SELECT * FROM customers WHERE id = $1', [id]);
|
|
154
|
+
console.log(customer.name); // But loaded email, phone, address too
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**CORRECT (Project columns):**
|
|
158
|
+
```javascript
|
|
159
|
+
const customer = await db.one(
|
|
160
|
+
'SELECT id, name, email FROM customers WHERE id = $1',
|
|
161
|
+
[id] // Only load what's needed
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Part 4: Safe Zero-Downtime Migrations
|
|
168
|
+
|
|
169
|
+
### The Problem: Data Migrations Block Requests
|
|
170
|
+
|
|
171
|
+
```sql
|
|
172
|
+
ALTER TABLE customers ADD COLUMN phone VARCHAR(20);
|
|
173
|
+
-- ^ Blocks all INSERT/UPDATE on customers table during migration (can be seconds~minutes for large tables)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Strategy: Backward-Compatible Migrations
|
|
177
|
+
|
|
178
|
+
**Step 1: Add column (no default = already nullable):**
|
|
179
|
+
```sql
|
|
180
|
+
ALTER TABLE customers ADD COLUMN phone VARCHAR(20);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Step 2: Backfill data (in batches to avoid lock):**
|
|
184
|
+
```javascript
|
|
185
|
+
// Run offline / in background job
|
|
186
|
+
const BATCH_SIZE = 10000;
|
|
187
|
+
let offset = 0;
|
|
188
|
+
while (true) {
|
|
189
|
+
const updated = await db.query(`
|
|
190
|
+
UPDATE customers
|
|
191
|
+
SET phone = ''
|
|
192
|
+
WHERE id IN (
|
|
193
|
+
SELECT id FROM customers
|
|
194
|
+
WHERE phone IS NULL
|
|
195
|
+
LIMIT $1
|
|
196
|
+
)
|
|
197
|
+
`, [BATCH_SIZE]);
|
|
198
|
+
if (updated.rowCount === 0) break;
|
|
199
|
+
offset += BATCH_SIZE;
|
|
200
|
+
await sleep(1000); // Rate limit to avoid overwhelming DB
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Step 3: Code deployment (app handles both old + new):**
|
|
205
|
+
```javascript
|
|
206
|
+
// Service layer reads/writes phone
|
|
207
|
+
if (newCustomer.phone) {
|
|
208
|
+
await customerRepo.update(customerId, { phone: newCustomer.phone });
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Old code still works (ignores phone), new code uses it.
|
|
213
|
+
|
|
214
|
+
**Step 4: After migration successful, remove the column (or rename old):**
|
|
215
|
+
```sql
|
|
216
|
+
-- Keep old column renamed for rollback safety
|
|
217
|
+
ALTER TABLE customers RENAME COLUMN phone_old TO phone_deprecated;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Checklist
|
|
223
|
+
|
|
224
|
+
- [ ] All tables in 3NF
|
|
225
|
+
- [ ] Foreign keys indexed
|
|
226
|
+
- [ ] JOIN queries avoid N+1 (load related data in one query)
|
|
227
|
+
- [ ] SELECT projects specific columns (not *)
|
|
228
|
+
- [ ] Indexes match WHERE + ORDER BY + JOIN ON patterns
|
|
229
|
+
- [ ] Migrations backward-compatible + batched
|
|
230
|
+
- [ ] Query performance profiled (EXPLAIN ANALYZE)
|
|
231
231
|
- Watch for N+1 and hidden coupling.
|