cdp-edge 1.2.0
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 +367 -0
- package/bin/cdp-edge.js +61 -0
- package/contracts/api-versions.json +368 -0
- package/dist/commands/analyze.js +52 -0
- package/dist/commands/infra.js +54 -0
- package/dist/commands/install.js +168 -0
- package/dist/commands/server.js +174 -0
- package/dist/commands/setup.js +123 -0
- package/dist/commands/validate.js +84 -0
- package/dist/index.js +12 -0
- package/docs/CI-CD-SETUP.md +217 -0
- package/docs/PixelBuilder-Documentacao-Completa (2).docx +0 -0
- package/docs/events-reference.md +359 -0
- package/docs/installation.md +155 -0
- package/docs/quick-start.md +185 -0
- package/docs/sdk-reference.md +371 -0
- package/docs/whatsapp-ctwa.md +209 -0
- package/extracted-skill/tracking-events-generator/INDEX.md +94 -0
- package/extracted-skill/tracking-events-generator/INSTALACAO-CDPEDGE.md +58 -0
- package/extracted-skill/tracking-events-generator/INTEGRACAO-COMPLETA.md +594 -0
- package/extracted-skill/tracking-events-generator/MELHORIAS-IMPLEMENTADAS.md +412 -0
- package/extracted-skill/tracking-events-generator/Premium-Tracking-Intelligence-Resumo.md +333 -0
- package/extracted-skill/tracking-events-generator/SKILL.md +257 -0
- package/extracted-skill/tracking-events-generator/advanced-matching.js +364 -0
- package/extracted-skill/tracking-events-generator/agents/ab-testing-agent.md +54 -0
- package/extracted-skill/tracking-events-generator/agents/attribution-agent.md +1304 -0
- package/extracted-skill/tracking-events-generator/agents/bing-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/browser-tracking.md +264 -0
- package/extracted-skill/tracking-events-generator/agents/code-guardian-agent.md +149 -0
- package/extracted-skill/tracking-events-generator/agents/compliance-agent.md +2077 -0
- package/extracted-skill/tracking-events-generator/agents/crm-integration-agent.md +1419 -0
- package/extracted-skill/tracking-events-generator/agents/dashboard-agent.md +456 -0
- package/extracted-skill/tracking-events-generator/agents/database-agent.md +667 -0
- package/extracted-skill/tracking-events-generator/agents/debug-agent.md +1455 -0
- package/extracted-skill/tracking-events-generator/agents/domain-setup-agent.md +224 -0
- package/extracted-skill/tracking-events-generator/agents/email-agent.md +61 -0
- package/extracted-skill/tracking-events-generator/agents/fingerprint-agent.md +52 -0
- package/extracted-skill/tracking-events-generator/agents/google-agent.md +109 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-agent.md +365 -0
- package/extracted-skill/tracking-events-generator/agents/intelligence-scheduling.md +643 -0
- package/extracted-skill/tracking-events-generator/agents/linkedin-agent.md +62 -0
- package/extracted-skill/tracking-events-generator/agents/localization-agent.md +55 -0
- package/extracted-skill/tracking-events-generator/agents/ltv-predictor-agent.md +59 -0
- package/extracted-skill/tracking-events-generator/agents/master-feedback-loop.md +900 -0
- package/extracted-skill/tracking-events-generator/agents/master-orchestrator.md +1922 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.json +109 -0
- package/extracted-skill/tracking-events-generator/agents/memory-agent.md +703 -0
- package/extracted-skill/tracking-events-generator/agents/meta-agent.md +110 -0
- package/extracted-skill/tracking-events-generator/agents/page-analyzer.md +255 -0
- package/extracted-skill/tracking-events-generator/agents/performance-agent.md +1157 -0
- package/extracted-skill/tracking-events-generator/agents/performance-optimization-agent.md +1432 -0
- package/extracted-skill/tracking-events-generator/agents/pinterest-agent.md +310 -0
- package/extracted-skill/tracking-events-generator/agents/premium-tracking-intelligence-agent.md +849 -0
- package/extracted-skill/tracking-events-generator/agents/r2-setup-agent.md +250 -0
- package/extracted-skill/tracking-events-generator/agents/reddit-agent.md +313 -0
- package/extracted-skill/tracking-events-generator/agents/security-enterprise-agent.md +1752 -0
- package/extracted-skill/tracking-events-generator/agents/server-tracking.md +1188 -0
- package/extracted-skill/tracking-events-generator/agents/spotify-agent.md +383 -0
- package/extracted-skill/tracking-events-generator/agents/tiktok-agent.md +111 -0
- package/extracted-skill/tracking-events-generator/agents/tracking-plan-agent.md +364 -0
- package/extracted-skill/tracking-events-generator/agents/validator-agent.md +267 -0
- package/extracted-skill/tracking-events-generator/agents/webhook-agent.md +69 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-agent.md +76 -0
- package/extracted-skill/tracking-events-generator/agents/whatsapp-ctwa-setup-agent.md +699 -0
- package/extracted-skill/tracking-events-generator/agents/youtube-agent.md +422 -0
- package/extracted-skill/tracking-events-generator/anti-blocking.js +285 -0
- package/extracted-skill/tracking-events-generator/cdpTrack.js +641 -0
- package/extracted-skill/tracking-events-generator/contracts/api-versions.json +368 -0
- package/extracted-skill/tracking-events-generator/docs/guia-cloudflare-iniciante.md +107 -0
- package/extracted-skill/tracking-events-generator/engagement-scoring.js +226 -0
- package/extracted-skill/tracking-events-generator/evals/evals.json +235 -0
- package/extracted-skill/tracking-events-generator/integration-test.js +497 -0
- package/extracted-skill/tracking-events-generator/knowledge-base.md +2894 -0
- package/extracted-skill/tracking-events-generator/micro-events.js +992 -0
- package/extracted-skill/tracking-events-generator/models/captura-de-lead.md +78 -0
- package/extracted-skill/tracking-events-generator/models/captura-lead-evento-externo.md +99 -0
- package/extracted-skill/tracking-events-generator/models/checkout-proprio.md +111 -0
- package/extracted-skill/tracking-events-generator/models/multi-step-checkout.md +672 -0
- package/extracted-skill/tracking-events-generator/models/pagina-obrigado.md +55 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/conversions-api-template.js +144 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/event-mappings.json +48 -0
- package/extracted-skill/tracking-events-generator/models/pinterest/tag-template.js +28 -0
- package/extracted-skill/tracking-events-generator/models/quiz-funnel.md +68 -0
- package/extracted-skill/tracking-events-generator/models/reddit/conversions-api-template.js +205 -0
- package/extracted-skill/tracking-events-generator/models/reddit/event-mappings.json +56 -0
- package/extracted-skill/tracking-events-generator/models/reddit/pixel-template.js +19 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/behavior-engine.js +425 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/real-estate-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/scenarios/sales-page-logic.md +50 -0
- package/extracted-skill/tracking-events-generator/models/trafego-direto.md +582 -0
- package/extracted-skill/tracking-events-generator/models/webinar-registration.md +63 -0
- package/extracted-skill/tracking-events-generator/tracking.config.js +46 -0
- package/extracted-skill/tracking-events-generator/walkthrough.md +26 -0
- package/package.json +75 -0
- package/server-edge-tracker/INSTALAR.md +328 -0
- package/server-edge-tracker/migrate-new-db.sql +137 -0
- package/server-edge-tracker/migrate-v2.sql +16 -0
- package/server-edge-tracker/migrate-v3.sql +6 -0
- package/server-edge-tracker/migrate-v4.sql +18 -0
- package/server-edge-tracker/migrate-v5.sql +17 -0
- package/server-edge-tracker/migrate-v6.sql +24 -0
- package/server-edge-tracker/migrate.sql +111 -0
- package/server-edge-tracker/schema.sql +265 -0
- package/server-edge-tracker/worker.js +2574 -0
- package/server-edge-tracker/wrangler.toml +85 -0
- package/templates/afiliado-sem-landing.md +312 -0
- package/templates/captura-de-lead.md +78 -0
- package/templates/captura-lead-evento-externo.md +99 -0
- package/templates/checkout-proprio.md +111 -0
- package/templates/install/.claude/commands/cdp.md +1 -0
- package/templates/install/CLAUDE.md +65 -0
- package/templates/linkedin/tag-template.js +46 -0
- package/templates/multi-step-checkout.md +673 -0
- package/templates/pagina-obrigado.md +55 -0
- package/templates/pinterest/conversions-api-template.js +144 -0
- package/templates/pinterest/event-mappings.json +48 -0
- package/templates/pinterest/tag-template.js +28 -0
- package/templates/quiz-funnel.md +68 -0
- package/templates/reddit/conversions-api-template.js +205 -0
- package/templates/reddit/event-mappings.json +56 -0
- package/templates/reddit/pixel-template.js +46 -0
- package/templates/scenarios/behavior-engine.js +402 -0
- package/templates/scenarios/real-estate-logic.md +50 -0
- package/templates/scenarios/sales-page-logic.md +50 -0
- package/templates/spotify/pixel-template.js +46 -0
- package/templates/trafego-direto.md +582 -0
- package/templates/vsl-page.md +292 -0
- package/templates/webinar-registration.md +63 -0
|
@@ -0,0 +1,1432 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: performance-optimization-agent
|
|
3
|
+
description: Performance Optimization Enterprise Agent - Caching, Query Optimization, Latency Profiling for Cloudflare Workers + D1
|
|
4
|
+
type: agent
|
|
5
|
+
persona: Performance Engineer specializing in edge computing optimization, distributed caching strategies, and database query optimization
|
|
6
|
+
version: "1.0.0"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Performance Optimization Enterprise Agent
|
|
10
|
+
|
|
11
|
+
## 🚀 Visão Geral
|
|
12
|
+
|
|
13
|
+
Agente especializado em otimização de performance para o sistema CDP Edge (Cloudflare Workers + D1 + Queue). Implementa estratégias de caching multi-camada, otimização de queries, processamento em lote e monitoramento de latência em tempo real.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 📊 Arquitetura de Performance
|
|
18
|
+
|
|
19
|
+
### 3-Camadas de Caching
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
23
|
+
│ L1: Memory Cache │
|
|
24
|
+
│ (In-Memory, Request Lifetime) │
|
|
25
|
+
│ - TTL: Request duration │
|
|
26
|
+
│ - Hit Rate: ~85% │
|
|
27
|
+
│ - Use: Session data, config lookup │
|
|
28
|
+
└─────────────────────────────────────────────────────────────┘
|
|
29
|
+
↓
|
|
30
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
31
|
+
│ L2: KV Cache │
|
|
32
|
+
│ (Global, Distributed Edge) │
|
|
33
|
+
│ - TTL: 5-60 minutes │
|
|
34
|
+
│ - Hit Rate: ~12% │
|
|
35
|
+
│ - Use: Attribution data, channel metrics │
|
|
36
|
+
└─────────────────────────────────────────────────────────────┘
|
|
37
|
+
↓
|
|
38
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
39
|
+
│ L3: D1 Database │
|
|
40
|
+
│ (SQLite at Edge, Persistent) │
|
|
41
|
+
│ - TTL: Permanent │
|
|
42
|
+
│ - Use: User journeys, audit logs, security data │
|
|
43
|
+
└─────────────────────────────────────────────────────────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🎯 Métricas de Performance
|
|
49
|
+
|
|
50
|
+
### KPIs Monitorados
|
|
51
|
+
|
|
52
|
+
| Métrica | Target | Alert |
|
|
53
|
+
|---------|--------|-------|
|
|
54
|
+
| **Cache Hit Rate (L1+L2)** | ≥ 95% | < 85% |
|
|
55
|
+
| **P95 Latency (Tracking)** | < 100ms | > 200ms |
|
|
56
|
+
| **P95 Latency (Attribution)** | < 500ms | > 1000ms |
|
|
57
|
+
| **Query Time (D1)** | < 50ms | > 100ms |
|
|
58
|
+
| **Queue Processing Time** | < 500ms | > 2000ms |
|
|
59
|
+
| **Memory Usage (Worker)** | < 128MB | > 128MB |
|
|
60
|
+
| **CPU Time (Worker)** | < 50ms | > 150ms |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 💾 Caching Multi-Layer
|
|
65
|
+
|
|
66
|
+
### L1: Memory Cache (Request-Scoped)
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
/**
|
|
70
|
+
* L1 Memory Cache - In-request cache with automatic expiration
|
|
71
|
+
*/
|
|
72
|
+
class L1Cache {
|
|
73
|
+
constructor() {
|
|
74
|
+
this.cache = new Map();
|
|
75
|
+
this.stats = {
|
|
76
|
+
hits: 0,
|
|
77
|
+
misses: 0,
|
|
78
|
+
sets: 0,
|
|
79
|
+
deletes: 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get value from L1 cache
|
|
85
|
+
* @param {string} key - Cache key
|
|
86
|
+
* @returns {any|undefined} Cached value or undefined if not found/expired
|
|
87
|
+
*/
|
|
88
|
+
get(key) {
|
|
89
|
+
const entry = this.cache.get(key);
|
|
90
|
+
if (!entry) {
|
|
91
|
+
this.stats.misses++;
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check expiration
|
|
96
|
+
if (entry.expiry < Date.now()) {
|
|
97
|
+
this.cache.delete(key);
|
|
98
|
+
this.stats.misses++;
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.stats.hits++;
|
|
103
|
+
return entry.value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Set value in L1 cache with optional TTL (default: request lifetime)
|
|
108
|
+
* @param {string} key - Cache key
|
|
109
|
+
* @param {any} value - Value to cache
|
|
110
|
+
* @param {number} ttlMs - TTL in milliseconds (optional)
|
|
111
|
+
*/
|
|
112
|
+
set(key, value, ttlMs = null) {
|
|
113
|
+
const expiry = ttlMs ? Date.now() + ttlMs : Infinity;
|
|
114
|
+
this.cache.set(key, { value, expiry, createdAt: Date.now() });
|
|
115
|
+
this.stats.sets++;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Delete value from L1 cache
|
|
120
|
+
* @param {string} key - Cache key
|
|
121
|
+
*/
|
|
122
|
+
delete(key) {
|
|
123
|
+
this.cache.delete(key);
|
|
124
|
+
this.stats.deletes++;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clear all L1 cache entries
|
|
129
|
+
*/
|
|
130
|
+
clear() {
|
|
131
|
+
this.cache.clear();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get cache statistics
|
|
136
|
+
* @returns {object} Cache statistics
|
|
137
|
+
*/
|
|
138
|
+
getStats() {
|
|
139
|
+
const totalRequests = this.stats.hits + this.stats.misses;
|
|
140
|
+
const hitRate = totalRequests > 0 ? (this.stats.hits / totalRequests) * 100 : 0;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...this.stats,
|
|
144
|
+
hitRate: hitRate.toFixed(2) + '%',
|
|
145
|
+
cacheSize: this.cache.size
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Global L1 cache instance (request-scoped)
|
|
151
|
+
let l1Cache = new L1Cache();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### L2: KV Cache (Global Edge Cache)
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
/**
|
|
158
|
+
* L2 KV Cache - Global distributed cache with automatic invalidation
|
|
159
|
+
*/
|
|
160
|
+
class L2Cache {
|
|
161
|
+
constructor(env) {
|
|
162
|
+
this.kv = env.CACHE_KV; // Cloudflare KV namespace
|
|
163
|
+
this.stats = {
|
|
164
|
+
hits: 0,
|
|
165
|
+
misses: 0,
|
|
166
|
+
sets: 0,
|
|
167
|
+
deletes: 0
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Cache TTLs (configurable)
|
|
171
|
+
this.ttls = {
|
|
172
|
+
attribution: 3600, // 1 hour
|
|
173
|
+
channelMetrics: 1800, // 30 minutes
|
|
174
|
+
userJourneys: 300, // 5 minutes
|
|
175
|
+
securityConfig: 900, // 15 minutes
|
|
176
|
+
lookupData: 600, // 10 minutes
|
|
177
|
+
default: 600 // 10 minutes
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get value from L2 cache
|
|
183
|
+
* @param {string} key - Cache key
|
|
184
|
+
* @param {string} cacheType - Type of cache (determines TTL)
|
|
185
|
+
* @returns {Promise<any|null>} Cached value or null if not found
|
|
186
|
+
*/
|
|
187
|
+
async get(key, cacheType = 'default') {
|
|
188
|
+
try {
|
|
189
|
+
const cached = await this.kv.get(key, { type: 'json' });
|
|
190
|
+
|
|
191
|
+
if (cached === null) {
|
|
192
|
+
this.stats.misses++;
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.stats.hits++;
|
|
197
|
+
return cached;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`[L2 Cache] Error getting ${key}:`, error);
|
|
200
|
+
this.stats.misses++;
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Set value in L2 cache with type-based TTL
|
|
207
|
+
* @param {string} key - Cache key
|
|
208
|
+
* @param {any} value - Value to cache
|
|
209
|
+
* @param {string} cacheType - Type of cache (determines TTL)
|
|
210
|
+
* @param {number} ttlMs - Custom TTL override (optional)
|
|
211
|
+
* @returns {Promise<boolean>} Success status
|
|
212
|
+
*/
|
|
213
|
+
async set(key, value, cacheType = 'default', ttlMs = null) {
|
|
214
|
+
try {
|
|
215
|
+
const ttl = ttlMs || (this.ttls[cacheType] || this.ttls.default);
|
|
216
|
+
|
|
217
|
+
await this.kv.put(key, JSON.stringify(value), {
|
|
218
|
+
expirationTtl: ttl
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
this.stats.sets++;
|
|
222
|
+
return true;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error(`[L2 Cache] Error setting ${key}:`, error);
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Delete value from L2 cache
|
|
231
|
+
* @param {string} key - Cache key
|
|
232
|
+
* @returns {Promise<boolean>} Success status
|
|
233
|
+
*/
|
|
234
|
+
async delete(key) {
|
|
235
|
+
try {
|
|
236
|
+
await this.kv.delete(key);
|
|
237
|
+
this.stats.deletes++;
|
|
238
|
+
return true;
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error(`[L2 Cache] Error deleting ${key}:`, error);
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Delete multiple keys from L2 cache (batch invalidation)
|
|
247
|
+
* @param {string[]} keys - Array of cache keys
|
|
248
|
+
* @returns {Promise<number>} Number of successfully deleted keys
|
|
249
|
+
*/
|
|
250
|
+
async deleteMany(keys) {
|
|
251
|
+
let deleted = 0;
|
|
252
|
+
for (const key of keys) {
|
|
253
|
+
if (await this.delete(key)) {
|
|
254
|
+
deleted++;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return deleted;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* List keys by prefix (for cache invalidation)
|
|
262
|
+
* @param {string} prefix - Key prefix
|
|
263
|
+
* @param {number} limit - Maximum keys to return
|
|
264
|
+
* @returns {Promise<string[]>} Array of keys
|
|
265
|
+
*/
|
|
266
|
+
async listKeys(prefix, limit = 100) {
|
|
267
|
+
try {
|
|
268
|
+
const list = await this.kv.list({ prefix, limit });
|
|
269
|
+
return list.keys.map(k => k.name);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error(`[L2 Cache] Error listing keys with prefix ${prefix}:`, error);
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Invalidate cache by prefix (bulk invalidation)
|
|
278
|
+
* @param {string} prefix - Key prefix to invalidate
|
|
279
|
+
* @returns {Promise<number>} Number of invalidated keys
|
|
280
|
+
*/
|
|
281
|
+
async invalidateByPrefix(prefix) {
|
|
282
|
+
const keys = await this.listKeys(prefix, 1000);
|
|
283
|
+
return await this.deleteMany(keys);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get cache statistics
|
|
288
|
+
* @returns {object} Cache statistics
|
|
289
|
+
*/
|
|
290
|
+
getStats() {
|
|
291
|
+
const totalRequests = this.stats.hits + this.stats.misses;
|
|
292
|
+
const hitRate = totalRequests > 0 ? (this.stats.hits / totalRequests) * 100 : 0;
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
...this.stats,
|
|
296
|
+
hitRate: hitRate.toFixed(2) + '%'
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// L2 cache instance (environment-scoped)
|
|
302
|
+
let l2Cache = null;
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Cache Strategy Implementation
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
/**
|
|
309
|
+
* Multi-Layer Cache Manager
|
|
310
|
+
* Orchestrates L1 and L2 caches with fallback logic
|
|
311
|
+
*/
|
|
312
|
+
class CacheManager {
|
|
313
|
+
constructor(env) {
|
|
314
|
+
this.l1 = new L1Cache();
|
|
315
|
+
this.l2 = new L2Cache(env);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get value from cache (L1 → L2 → D1 fallback)
|
|
320
|
+
* @param {string} key - Cache key
|
|
321
|
+
* @param {string} cacheType - Type of cache
|
|
322
|
+
* @param {Function} fallback - Fallback function to fetch from D1
|
|
323
|
+
* @returns {Promise<any>} Cached or fetched value
|
|
324
|
+
*/
|
|
325
|
+
async get(key, cacheType = 'default', fallback = null) {
|
|
326
|
+
// Try L1 cache first (fastest)
|
|
327
|
+
const l1Value = this.l1.get(key);
|
|
328
|
+
if (l1Value !== undefined) {
|
|
329
|
+
return l1Value;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Try L2 cache (distributed)
|
|
333
|
+
const l2Value = await this.l2.get(key, cacheType);
|
|
334
|
+
if (l2Value !== null) {
|
|
335
|
+
// Promote to L1 cache
|
|
336
|
+
this.l1.set(key, l2Value);
|
|
337
|
+
return l2Value;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Fallback to source (D1)
|
|
341
|
+
if (fallback) {
|
|
342
|
+
const value = await fallback();
|
|
343
|
+
|
|
344
|
+
// Cache in L2 and L1
|
|
345
|
+
await this.l2.set(key, value, cacheType);
|
|
346
|
+
this.l1.set(key, value);
|
|
347
|
+
|
|
348
|
+
return value;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Set value in cache (L1 + L2)
|
|
356
|
+
* @param {string} key - Cache key
|
|
357
|
+
* @param {any} value - Value to cache
|
|
358
|
+
* @param {string} cacheType - Type of cache
|
|
359
|
+
* @param {number} ttlMs - Custom TTL for L2
|
|
360
|
+
*/
|
|
361
|
+
async set(key, value, cacheType = 'default', ttlMs = null) {
|
|
362
|
+
this.l1.set(key, value);
|
|
363
|
+
await this.l2.set(key, value, cacheType, ttlMs);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Delete value from cache (L1 + L2)
|
|
368
|
+
* @param {string} key - Cache key
|
|
369
|
+
*/
|
|
370
|
+
async delete(key) {
|
|
371
|
+
this.l1.delete(key);
|
|
372
|
+
await this.l2.delete(key);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Invalidate cache by prefix (L2 only - L1 doesn't support prefix)
|
|
377
|
+
* @param {string} prefix - Key prefix
|
|
378
|
+
*/
|
|
379
|
+
async invalidateByPrefix(prefix) {
|
|
380
|
+
await this.l2.invalidateByPrefix(prefix);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get combined cache statistics
|
|
385
|
+
* @returns {object} Combined cache statistics
|
|
386
|
+
*/
|
|
387
|
+
getStats() {
|
|
388
|
+
return {
|
|
389
|
+
l1: this.l1.getStats(),
|
|
390
|
+
l2: this.l2.getStats()
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Global cache manager instance
|
|
396
|
+
let cacheManager = null;
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 🗄️ Otimização de Queries D1
|
|
402
|
+
|
|
403
|
+
### Schema de Índices Otimizados
|
|
404
|
+
|
|
405
|
+
```sql
|
|
406
|
+
-- User Journeys Table (Optimized for attribution queries)
|
|
407
|
+
CREATE TABLE IF NOT EXISTS user_journeys (
|
|
408
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
409
|
+
user_id TEXT NOT NULL,
|
|
410
|
+
event_id TEXT,
|
|
411
|
+
event_name TEXT NOT NULL,
|
|
412
|
+
utm_source TEXT,
|
|
413
|
+
utm_medium TEXT,
|
|
414
|
+
utm_campaign TEXT,
|
|
415
|
+
utm_term TEXT,
|
|
416
|
+
utm_content TEXT,
|
|
417
|
+
event_timestamp DATETIME NOT NULL,
|
|
418
|
+
position INTEGER,
|
|
419
|
+
conversion_id TEXT,
|
|
420
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
-- Compound indexes for common query patterns
|
|
424
|
+
CREATE INDEX IF NOT EXISTS idx_user_journeys_user_timestamp
|
|
425
|
+
ON user_journeys(user_id, event_timestamp DESC);
|
|
426
|
+
|
|
427
|
+
CREATE INDEX IF NOT EXISTS idx_user_journeys_conversion
|
|
428
|
+
ON user_journeys(conversion_id, position);
|
|
429
|
+
|
|
430
|
+
CREATE INDEX IF NOT EXISTS idx_user_journeys_utm_source
|
|
431
|
+
ON user_journeys(utm_source, event_timestamp DESC);
|
|
432
|
+
|
|
433
|
+
CREATE INDEX IF NOT EXISTS idx_user_journeys_event_name
|
|
434
|
+
ON user_journeys(event_name, event_timestamp DESC);
|
|
435
|
+
|
|
436
|
+
-- Multi-Touch Attribution Table
|
|
437
|
+
CREATE TABLE IF NOT EXISTS multi_touch_attribution (
|
|
438
|
+
conversion_id TEXT NOT NULL,
|
|
439
|
+
attribution_model TEXT NOT NULL,
|
|
440
|
+
touchpoint_index INTEGER NOT NULL,
|
|
441
|
+
utm_source TEXT,
|
|
442
|
+
credit_percentage REAL NOT NULL,
|
|
443
|
+
role TEXT,
|
|
444
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
445
|
+
PRIMARY KEY (conversion_id, attribution_model, touchpoint_index)
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
-- Index for attribution queries
|
|
449
|
+
CREATE INDEX IF NOT EXISTS idx_attribution_model_conversion
|
|
450
|
+
ON multi_touch_attribution(attribution_model, conversion_id);
|
|
451
|
+
|
|
452
|
+
CREATE INDEX IF NOT EXISTS idx_attribution_source
|
|
453
|
+
ON multi_touch_attribution(utm_source, attribution_model);
|
|
454
|
+
|
|
455
|
+
-- Channel Performance Table (Materialized for fast aggregation)
|
|
456
|
+
CREATE TABLE IF NOT EXISTS channel_performance (
|
|
457
|
+
utm_source TEXT NOT NULL,
|
|
458
|
+
attribution_model TEXT NOT NULL,
|
|
459
|
+
total_attribution REAL NOT NULL,
|
|
460
|
+
total_conversions INTEGER NOT NULL,
|
|
461
|
+
total_value REAL NOT NULL,
|
|
462
|
+
avg_attribution_per_conversion REAL NOT NULL,
|
|
463
|
+
last_updated DATETIME NOT NULL,
|
|
464
|
+
PRIMARY KEY (utm_source, attribution_model)
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
-- Index for performance queries
|
|
468
|
+
CREATE INDEX IF NOT EXISTS idx_channel_performance_model
|
|
469
|
+
ON channel_performance(attribution_model, total_attribution DESC);
|
|
470
|
+
|
|
471
|
+
-- Index for timestamp-based queries
|
|
472
|
+
CREATE INDEX IF NOT EXISTS idx_channel_performance_updated
|
|
473
|
+
ON channel_performance(last_updated DESC);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Query Optimization Patterns
|
|
477
|
+
|
|
478
|
+
```javascript
|
|
479
|
+
/**
|
|
480
|
+
* Optimized Query Builder for D1
|
|
481
|
+
*/
|
|
482
|
+
class QueryOptimizer {
|
|
483
|
+
constructor(db) {
|
|
484
|
+
this.db = db;
|
|
485
|
+
this.queryCache = new Map();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Execute query with automatic index usage and caching
|
|
490
|
+
* @param {string} query - SQL query with parameter placeholders
|
|
491
|
+
* @param {any[]} params - Query parameters
|
|
492
|
+
* @param {string} cacheKey - Optional cache key
|
|
493
|
+
* @returns {Promise<any>} Query result
|
|
494
|
+
*/
|
|
495
|
+
async execute(query, params = [], cacheKey = null) {
|
|
496
|
+
const startTime = performance.now();
|
|
497
|
+
|
|
498
|
+
try {
|
|
499
|
+
// Check query cache if cache key provided
|
|
500
|
+
if (cacheKey && this.queryCache.has(cacheKey)) {
|
|
501
|
+
const cached = this.queryCache.get(cacheKey);
|
|
502
|
+
if (Date.now() - cached.timestamp < 60000) { // 1 min cache
|
|
503
|
+
return cached.result;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Prepare and execute query
|
|
508
|
+
const statement = this.db.prepare(query);
|
|
509
|
+
const result = await statement.bind(...params).all();
|
|
510
|
+
|
|
511
|
+
// Cache result if cache key provided
|
|
512
|
+
if (cacheKey) {
|
|
513
|
+
this.queryCache.set(cacheKey, {
|
|
514
|
+
result: result,
|
|
515
|
+
timestamp: Date.now()
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const queryTime = performance.now() - startTime;
|
|
520
|
+
|
|
521
|
+
// Log slow queries
|
|
522
|
+
if (queryTime > 100) {
|
|
523
|
+
console.warn(`[Slow Query] ${queryTime.toFixed(2)}ms - ${query.substring(0, 100)}...`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return result;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
const queryTime = performance.now() - startTime;
|
|
529
|
+
console.error(`[Query Error] ${queryTime.toFixed(2)}ms - ${error.message}`);
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Get user journey with optimized index usage
|
|
536
|
+
* @param {string} userId - User ID
|
|
537
|
+
* @param {string} conversionId - Optional conversion ID filter
|
|
538
|
+
* @returns {Promise<Array>} User journey events
|
|
539
|
+
*/
|
|
540
|
+
async getUserJourney(userId, conversionId = null) {
|
|
541
|
+
const cacheKey = `journey:${userId}:${conversionId || 'all'}`;
|
|
542
|
+
|
|
543
|
+
const query = conversionId
|
|
544
|
+
? `
|
|
545
|
+
SELECT * FROM user_journeys
|
|
546
|
+
WHERE user_id = ? AND conversion_id = ?
|
|
547
|
+
ORDER BY event_timestamp ASC
|
|
548
|
+
`
|
|
549
|
+
: `
|
|
550
|
+
SELECT * FROM user_journeys
|
|
551
|
+
WHERE user_id = ?
|
|
552
|
+
ORDER BY event_timestamp DESC
|
|
553
|
+
LIMIT 100
|
|
554
|
+
`;
|
|
555
|
+
|
|
556
|
+
const params = conversionId ? [userId, conversionId] : [userId];
|
|
557
|
+
return await this.execute(query, params, cacheKey);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Get attribution data with optimized joins
|
|
562
|
+
* @param {string} conversionId - Conversion ID
|
|
563
|
+
* @param {string} attributionModel - Attribution model
|
|
564
|
+
* @returns {Promise<Array>} Attribution data
|
|
565
|
+
*/
|
|
566
|
+
async getAttributionData(conversionId, attributionModel) {
|
|
567
|
+
const cacheKey = `attribution:${conversionId}:${attributionModel}`;
|
|
568
|
+
|
|
569
|
+
const query = `
|
|
570
|
+
SELECT
|
|
571
|
+
a.*,
|
|
572
|
+
j.utm_medium,
|
|
573
|
+
j.utm_campaign,
|
|
574
|
+
j.event_name,
|
|
575
|
+
j.event_timestamp
|
|
576
|
+
FROM multi_touch_attribution a
|
|
577
|
+
INNER JOIN user_journeys j ON a.conversion_id = j.conversion_id
|
|
578
|
+
WHERE a.conversion_id = ? AND a.attribution_model = ?
|
|
579
|
+
ORDER BY a.touchpoint_index ASC
|
|
580
|
+
`;
|
|
581
|
+
|
|
582
|
+
return await this.execute(query, [conversionId, attributionModel], cacheKey);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Get channel performance with pre-aggregated data
|
|
587
|
+
* @param {string} attributionModel - Attribution model
|
|
588
|
+
* @param {number} limit - Limit results
|
|
589
|
+
* @returns {Promise<Array>} Channel performance data
|
|
590
|
+
*/
|
|
591
|
+
async getChannelPerformance(attributionModel, limit = 50) {
|
|
592
|
+
const cacheKey = `performance:${attributionModel}:${limit}`;
|
|
593
|
+
|
|
594
|
+
const query = `
|
|
595
|
+
SELECT * FROM channel_performance
|
|
596
|
+
WHERE attribution_model = ?
|
|
597
|
+
ORDER BY total_attribution DESC
|
|
598
|
+
LIMIT ?
|
|
599
|
+
`;
|
|
600
|
+
|
|
601
|
+
return await this.execute(query, [attributionModel, limit], cacheKey);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Batch insert for high-throughput operations
|
|
606
|
+
* @param {string} table - Table name
|
|
607
|
+
* @param {string[]} columns - Column names
|
|
608
|
+
* @param {any[][]} values - Array of value arrays
|
|
609
|
+
* @returns {Promise<number>} Number of inserted rows
|
|
610
|
+
*/
|
|
611
|
+
async batchInsert(table, columns, values) {
|
|
612
|
+
const startTime = performance.now();
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
const placeholders = columns.map(() => '?').join(',');
|
|
616
|
+
const query = `INSERT INTO ${table} (${columns.join(',')}) VALUES (${placeholders})`;
|
|
617
|
+
|
|
618
|
+
let inserted = 0;
|
|
619
|
+
for (const row of values) {
|
|
620
|
+
await this.db.prepare(query).bind(...row).run();
|
|
621
|
+
inserted++;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const queryTime = performance.now() - startTime;
|
|
625
|
+
console.log(`[Batch Insert] ${inserted} rows in ${queryTime.toFixed(2)}ms`);
|
|
626
|
+
|
|
627
|
+
return inserted;
|
|
628
|
+
} catch (error) {
|
|
629
|
+
const queryTime = performance.now() - startTime;
|
|
630
|
+
console.error(`[Batch Insert Error] ${queryTime.toFixed(2)}ms - ${error.message}`);
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Invalidate query cache by pattern
|
|
637
|
+
* @param {string} pattern - Cache key pattern (supports wildcards *)
|
|
638
|
+
*/
|
|
639
|
+
invalidateCache(pattern) {
|
|
640
|
+
for (const key of this.queryCache.keys()) {
|
|
641
|
+
if (this.matchesPattern(key, pattern)) {
|
|
642
|
+
this.queryCache.delete(key);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Match cache key against pattern (supports * wildcard)
|
|
649
|
+
* @param {string} key - Cache key
|
|
650
|
+
* @param {string} pattern - Pattern with * wildcards
|
|
651
|
+
* @returns {boolean} Match result
|
|
652
|
+
*/
|
|
653
|
+
matchesPattern(key, pattern) {
|
|
654
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
|
|
655
|
+
return regex.test(key);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Global query optimizer instance
|
|
660
|
+
let queryOptimizer = null;
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## 🚀 Otimização de Processamento em Lote
|
|
666
|
+
|
|
667
|
+
### Batch Processing for Attribution
|
|
668
|
+
|
|
669
|
+
```javascript
|
|
670
|
+
/**
|
|
671
|
+
* Batch Attribution Processor
|
|
672
|
+
* Processes multiple attributions in parallel with batching
|
|
673
|
+
*/
|
|
674
|
+
class BatchAttributionProcessor {
|
|
675
|
+
constructor(cacheManager, queryOptimizer) {
|
|
676
|
+
this.cache = cacheManager;
|
|
677
|
+
this.optimizer = queryOptimizer;
|
|
678
|
+
this.batchSize = 50; // Process 50 attributions at once
|
|
679
|
+
this.maxParallel = 5; // Max 5 parallel batches
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Process multiple attributions in batches
|
|
684
|
+
* @param {Array} conversions - Array of conversion IDs
|
|
685
|
+
* @param {string} attributionModel - Attribution model
|
|
686
|
+
* @returns {Promise<Array>} Processing results
|
|
687
|
+
*/
|
|
688
|
+
async processBatch(conversions, attributionModel) {
|
|
689
|
+
const results = [];
|
|
690
|
+
const batches = this.chunkArray(conversions, this.batchSize);
|
|
691
|
+
|
|
692
|
+
// Process batches in parallel with concurrency limit
|
|
693
|
+
const batchPromises = batches.map(batch =>
|
|
694
|
+
this.processSingleBatch(batch, attributionModel)
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
const batchResults = await Promise.all(batchPromises);
|
|
698
|
+
|
|
699
|
+
// Flatten results
|
|
700
|
+
for (const batchResult of batchResults) {
|
|
701
|
+
results.push(...batchResult);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return results;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Process a single batch of attributions
|
|
709
|
+
* @param {Array} conversions - Array of conversion IDs
|
|
710
|
+
* @param {string} attributionModel - Attribution model
|
|
711
|
+
* @returns {Promise<Array>} Processing results
|
|
712
|
+
*/
|
|
713
|
+
async processSingleBatch(conversions, attributionModel) {
|
|
714
|
+
const results = [];
|
|
715
|
+
|
|
716
|
+
for (const conversionId of conversions) {
|
|
717
|
+
try {
|
|
718
|
+
// Check cache first
|
|
719
|
+
const cacheKey = `attribution:${conversionId}:${attributionModel}`;
|
|
720
|
+
const cached = await this.cache.get(cacheKey, 'attribution');
|
|
721
|
+
|
|
722
|
+
if (cached) {
|
|
723
|
+
results.push({ conversionId, attributionModel, cached, fromCache: true });
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Fetch user journey
|
|
728
|
+
const journey = await this.optimizer.getUserJourney(conversionId);
|
|
729
|
+
|
|
730
|
+
if (journey.length === 0) {
|
|
731
|
+
results.push({ conversionId, attributionModel, error: 'No journey found' });
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Calculate attribution
|
|
736
|
+
const attribution = await this.calculateAttribution(journey, attributionModel);
|
|
737
|
+
|
|
738
|
+
// Cache result
|
|
739
|
+
await this.cache.set(cacheKey, attribution, 'attribution');
|
|
740
|
+
|
|
741
|
+
results.push({ conversionId, attributionModel, attribution, fromCache: false });
|
|
742
|
+
} catch (error) {
|
|
743
|
+
console.error(`Error processing ${conversionId}:`, error);
|
|
744
|
+
results.push({ conversionId, attributionModel, error: error.message });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return results;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Calculate attribution using specified model
|
|
753
|
+
* @param {Array} journey - User journey events
|
|
754
|
+
* @param {string} model - Attribution model
|
|
755
|
+
* @returns {Array} Attribution result
|
|
756
|
+
*/
|
|
757
|
+
async calculateAttribution(journey, model) {
|
|
758
|
+
// Implementation of attribution models
|
|
759
|
+
// This would call the appropriate attribution function from attribution-agent.md
|
|
760
|
+
const attributionModels = {
|
|
761
|
+
'last_click': this.lastClickAttribution,
|
|
762
|
+
'first_click': this.firstClickAttribution,
|
|
763
|
+
'linear': this.linearAttribution,
|
|
764
|
+
'time_decay': this.timeDecayAttribution,
|
|
765
|
+
'u_shape': this.uShapeAttribution,
|
|
766
|
+
'w_shape': this.wShapeAttribution,
|
|
767
|
+
'data_driven': this.dataDrivenAttribution
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const modelFn = attributionModels[model] || attributionModels['last_click'];
|
|
771
|
+
return await modelFn.call(this, journey);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Attribution model implementations (from attribution-agent.md)
|
|
775
|
+
lastClickAttribution(touchpoints) {
|
|
776
|
+
return touchpoints.map((tp, index) => ({
|
|
777
|
+
...tp,
|
|
778
|
+
credit_percentage: index === touchpoints.length - 1 ? 100 : 0,
|
|
779
|
+
is_last_click: index === touchpoints.length - 1
|
|
780
|
+
}));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
firstClickAttribution(touchpoints) {
|
|
784
|
+
return touchpoints.map((tp, index) => ({
|
|
785
|
+
...tp,
|
|
786
|
+
credit_percentage: index === 0 ? 100 : 0,
|
|
787
|
+
is_first_click: index === 0
|
|
788
|
+
}));
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
linearAttribution(touchpoints) {
|
|
792
|
+
const credit = 100 / touchpoints.length;
|
|
793
|
+
return touchpoints.map(tp => ({
|
|
794
|
+
...tp,
|
|
795
|
+
credit_percentage: credit.toFixed(2)
|
|
796
|
+
}));
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
async timeDecayAttribution(touchpoints, decayFactor = 0.9) {
|
|
800
|
+
const now = Date.now();
|
|
801
|
+
const oneDayMs = 24 * 60 * 60 * 1000;
|
|
802
|
+
|
|
803
|
+
const touchpointsWithDays = touchpoints.map(tp => {
|
|
804
|
+
const daysSinceTouch = (now - tp.event_timestamp) / oneDayMs;
|
|
805
|
+
const decayScore = Math.pow(decayFactor, daysSinceTouch);
|
|
806
|
+
return { ...tp, days_since: daysSinceTouch, decay_score: decayScore };
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
const totalScore = touchpointsWithDays.reduce((sum, tp) => sum + tp.decay_score, 0);
|
|
810
|
+
|
|
811
|
+
return touchpointsWithDays.map(tp => ({
|
|
812
|
+
...tp,
|
|
813
|
+
credit_percentage: ((tp.decay_score / totalScore) * 100).toFixed(2)
|
|
814
|
+
}));
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
uShapeAttribution(touchpoints) {
|
|
818
|
+
if (touchpoints.length === 0) return [];
|
|
819
|
+
|
|
820
|
+
const firstTouchWeight = 0.4;
|
|
821
|
+
const lastTouchWeight = 0.4;
|
|
822
|
+
const middleWeight = touchpoints.length > 2 ? 0.2 / (touchpoints.length - 2) : 0;
|
|
823
|
+
|
|
824
|
+
return touchpoints.map((tp, index) => {
|
|
825
|
+
if (index === 0) {
|
|
826
|
+
return { ...tp, credit_percentage: 40, role: 'first' };
|
|
827
|
+
} else if (index === touchpoints.length - 1) {
|
|
828
|
+
return { ...tp, credit_percentage: 40, role: 'last' };
|
|
829
|
+
} else {
|
|
830
|
+
return { ...tp, credit_percentage: middleWeight.toFixed(2), role: 'middle' };
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
wShapeAttribution(touchpoints) {
|
|
836
|
+
if (touchpoints.length === 0) return [];
|
|
837
|
+
|
|
838
|
+
const firstTouchWeight = 0.3;
|
|
839
|
+
const lastTouchWeight = 0.3;
|
|
840
|
+
const middleWeights = touchpoints.length > 2 ? 0.4 / 2 : 0; // 2 assist points
|
|
841
|
+
|
|
842
|
+
return touchpoints.map((tp, index) => {
|
|
843
|
+
const pos = index + 1;
|
|
844
|
+
const total = touchpoints.length;
|
|
845
|
+
|
|
846
|
+
if (pos === 1) {
|
|
847
|
+
return { ...tp, credit_percentage: 30, role: 'first' };
|
|
848
|
+
} else if (pos === total) {
|
|
849
|
+
return { ...tp, credit_percentage: 30, role: 'last' };
|
|
850
|
+
} else if (pos === Math.floor(total / 2)) {
|
|
851
|
+
return { ...tp, credit_percentage: 20, role: 'middle_assist_1' };
|
|
852
|
+
} else {
|
|
853
|
+
return { ...tp, credit_percentage: middleWeights.toFixed(2), role: 'middle_assist_2' };
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
async dataDrivenAttribution(touchpoints, userJourneyHistory) {
|
|
859
|
+
// Simplified data-driven attribution
|
|
860
|
+
// In production, this would analyze historical conversion data
|
|
861
|
+
|
|
862
|
+
const channelWeights = await this.calculateChannelWeights(touchpoints, userJourneyHistory);
|
|
863
|
+
const positionWeights = await this.calculatePositionWeights(touchpoints, userJourneyHistory);
|
|
864
|
+
|
|
865
|
+
return touchpoints.map((tp, index) => {
|
|
866
|
+
const baseScore = 100 / touchpoints.length;
|
|
867
|
+
const channelWeight = channelWeights[tp.utm_source] || 1;
|
|
868
|
+
const positionWeight = positionWeights[index] || 1;
|
|
869
|
+
|
|
870
|
+
const score = baseScore * channelWeight * positionWeight;
|
|
871
|
+
|
|
872
|
+
return {
|
|
873
|
+
...tp,
|
|
874
|
+
credit_percentage: score.toFixed(2),
|
|
875
|
+
channel_weight: channelWeight,
|
|
876
|
+
position_weight: positionWeight
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async calculateChannelWeights(touchpoints, history) {
|
|
882
|
+
// Simplified - in production, analyze historical data
|
|
883
|
+
const channels = {};
|
|
884
|
+
touchpoints.forEach(tp => {
|
|
885
|
+
channels[tp.utm_source] = (channels[tp.utm_source] || 0) + 1;
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const total = Object.values(channels).reduce((a, b) => a + b, 0);
|
|
889
|
+
const weights = {};
|
|
890
|
+
Object.entries(channels).forEach(([channel, count]) => {
|
|
891
|
+
weights[channel] = (count / total) * touchpoints.length;
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
return weights;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
async calculatePositionWeights(touchpoints, history) {
|
|
898
|
+
// Simplified - in production, analyze historical position conversion rates
|
|
899
|
+
const weights = {};
|
|
900
|
+
for (let i = 0; i < touchpoints.length; i++) {
|
|
901
|
+
weights[i] = 1;
|
|
902
|
+
}
|
|
903
|
+
return weights;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Chunk array into smaller arrays
|
|
908
|
+
* @param {Array} array - Array to chunk
|
|
909
|
+
* @param {number} size - Chunk size
|
|
910
|
+
* @returns {Array} Array of chunks
|
|
911
|
+
*/
|
|
912
|
+
chunkArray(array, size) {
|
|
913
|
+
const chunks = [];
|
|
914
|
+
for (let i = 0; i < array.length; i += size) {
|
|
915
|
+
chunks.push(array.slice(i, i + size));
|
|
916
|
+
}
|
|
917
|
+
return chunks;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Global batch processor instance
|
|
922
|
+
let batchProcessor = null;
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## 📊 Profileamento de Latência
|
|
928
|
+
|
|
929
|
+
### Performance Monitoring
|
|
930
|
+
|
|
931
|
+
```javascript
|
|
932
|
+
/**
|
|
933
|
+
* Latency Profiler
|
|
934
|
+
* Monitors and tracks performance metrics
|
|
935
|
+
*/
|
|
936
|
+
class LatencyProfiler {
|
|
937
|
+
constructor() {
|
|
938
|
+
this.metrics = {
|
|
939
|
+
requests: [],
|
|
940
|
+
queries: [],
|
|
941
|
+
cacheOps: [],
|
|
942
|
+
externalAPIs: []
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
this.thresholds = {
|
|
946
|
+
request: { warning: 100, error: 200 },
|
|
947
|
+
query: { warning: 50, error: 100 },
|
|
948
|
+
cache: { warning: 10, error: 20 },
|
|
949
|
+
external: { warning: 500, error: 2000 }
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Start profiling a request
|
|
955
|
+
* @param {string} requestId - Request ID
|
|
956
|
+
* @param {string} endpoint - Endpoint being accessed
|
|
957
|
+
*/
|
|
958
|
+
startRequest(requestId, endpoint) {
|
|
959
|
+
this.metrics.requests.push({
|
|
960
|
+
id: requestId,
|
|
961
|
+
endpoint: endpoint,
|
|
962
|
+
startTime: performance.now(),
|
|
963
|
+
stages: []
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* End profiling a request
|
|
969
|
+
* @param {string} requestId - Request ID
|
|
970
|
+
* @param {object} metadata - Additional metadata
|
|
971
|
+
*/
|
|
972
|
+
endRequest(requestId, metadata = {}) {
|
|
973
|
+
const request = this.metrics.requests.find(r => r.id === requestId);
|
|
974
|
+
if (!request) return;
|
|
975
|
+
|
|
976
|
+
request.endTime = performance.now();
|
|
977
|
+
request.duration = request.endTime - request.startTime;
|
|
978
|
+
request.metadata = metadata;
|
|
979
|
+
|
|
980
|
+
// Log if over threshold
|
|
981
|
+
if (request.duration > this.thresholds.request.error) {
|
|
982
|
+
console.error(`[Latency] CRITICAL: ${request.duration.toFixed(2)}ms - ${request.endpoint}`);
|
|
983
|
+
} else if (request.duration > this.thresholds.request.warning) {
|
|
984
|
+
console.warn(`[Latency] WARNING: ${request.duration.toFixed(2)}ms - ${request.endpoint}`);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Profile a D1 query
|
|
990
|
+
* @param {string} requestId - Request ID
|
|
991
|
+
* @param {string} query - SQL query
|
|
992
|
+
* @returns {Function} Function to call when query completes
|
|
993
|
+
*/
|
|
994
|
+
profileQuery(requestId, query) {
|
|
995
|
+
const startTime = performance.now();
|
|
996
|
+
|
|
997
|
+
return () => {
|
|
998
|
+
const duration = performance.now() - startTime;
|
|
999
|
+
|
|
1000
|
+
this.metrics.queries.push({
|
|
1001
|
+
requestId: requestId,
|
|
1002
|
+
query: query.substring(0, 100),
|
|
1003
|
+
duration: duration,
|
|
1004
|
+
timestamp: Date.now()
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
if (duration > this.thresholds.query.error) {
|
|
1008
|
+
console.error(`[Query Latency] CRITICAL: ${duration.toFixed(2)}ms - ${query.substring(0, 50)}...`);
|
|
1009
|
+
} else if (duration > this.thresholds.query.warning) {
|
|
1010
|
+
console.warn(`[Query Latency] WARNING: ${duration.toFixed(2)}ms - ${query.substring(0, 50)}...`);
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Profile a cache operation
|
|
1017
|
+
* @param {string} requestId - Request ID
|
|
1018
|
+
* @param {string} operation - Cache operation (get, set, delete)
|
|
1019
|
+
* @param {string} layer - Cache layer (L1, L2)
|
|
1020
|
+
* @returns {Function} Function to call when operation completes
|
|
1021
|
+
*/
|
|
1022
|
+
profileCache(requestId, operation, layer) {
|
|
1023
|
+
const startTime = performance.now();
|
|
1024
|
+
|
|
1025
|
+
return () => {
|
|
1026
|
+
const duration = performance.now() - startTime;
|
|
1027
|
+
|
|
1028
|
+
this.metrics.cacheOps.push({
|
|
1029
|
+
requestId: requestId,
|
|
1030
|
+
operation: operation,
|
|
1031
|
+
layer: layer,
|
|
1032
|
+
duration: duration,
|
|
1033
|
+
timestamp: Date.now()
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
if (duration > this.thresholds.cache.error) {
|
|
1037
|
+
console.error(`[Cache Latency] CRITICAL: ${layer} ${operation} - ${duration.toFixed(2)}ms`);
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Profile an external API call
|
|
1044
|
+
* @param {string} requestId - Request ID
|
|
1045
|
+
* @param {string} api - API name
|
|
1046
|
+
* @returns {Function} Function to call when API call completes
|
|
1047
|
+
*/
|
|
1048
|
+
profileExternalAPI(requestId, api) {
|
|
1049
|
+
const startTime = performance.now();
|
|
1050
|
+
|
|
1051
|
+
return () => {
|
|
1052
|
+
const duration = performance.now() - startTime;
|
|
1053
|
+
|
|
1054
|
+
this.metrics.externalAPIs.push({
|
|
1055
|
+
requestId: requestId,
|
|
1056
|
+
api: api,
|
|
1057
|
+
duration: duration,
|
|
1058
|
+
timestamp: Date.now()
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
if (duration > this.thresholds.external.error) {
|
|
1062
|
+
console.error(`[External API Latency] CRITICAL: ${api} - ${duration.toFixed(2)}ms`);
|
|
1063
|
+
} else if (duration > this.thresholds.external.warning) {
|
|
1064
|
+
console.warn(`[External API Latency] WARNING: ${api} - ${duration.toFixed(2)}ms`);
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Get performance summary
|
|
1071
|
+
* @returns {object} Performance summary
|
|
1072
|
+
*/
|
|
1073
|
+
getSummary() {
|
|
1074
|
+
const summary = {};
|
|
1075
|
+
|
|
1076
|
+
// Request metrics
|
|
1077
|
+
const completedRequests = this.metrics.requests.filter(r => r.duration);
|
|
1078
|
+
summary.requests = {
|
|
1079
|
+
total: completedRequests.length,
|
|
1080
|
+
avg: this.average(completedRequests.map(r => r.duration)),
|
|
1081
|
+
p50: this.percentile(completedRequests.map(r => r.duration), 50),
|
|
1082
|
+
p95: this.percentile(completedRequests.map(r => r.duration), 95),
|
|
1083
|
+
p99: this.percentile(completedRequests.map(r => r.duration), 99),
|
|
1084
|
+
max: Math.max(...completedRequests.map(r => r.duration), 0)
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
// Query metrics
|
|
1088
|
+
summary.queries = {
|
|
1089
|
+
total: this.metrics.queries.length,
|
|
1090
|
+
avg: this.average(this.metrics.queries.map(q => q.duration)),
|
|
1091
|
+
p95: this.percentile(this.metrics.queries.map(q => q.duration), 95),
|
|
1092
|
+
max: Math.max(...this.metrics.queries.map(q => q.duration), 0)
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
// Cache metrics
|
|
1096
|
+
const l1Ops = this.metrics.cacheOps.filter(o => o.layer === 'L1');
|
|
1097
|
+
const l2Ops = this.metrics.cacheOps.filter(o => o.layer === 'L2');
|
|
1098
|
+
|
|
1099
|
+
summary.cache = {
|
|
1100
|
+
l1: {
|
|
1101
|
+
total: l1Ops.length,
|
|
1102
|
+
avg: this.average(l1Ops.map(o => o.duration)),
|
|
1103
|
+
hitRate: this.calculateHitRate(l1Ops)
|
|
1104
|
+
},
|
|
1105
|
+
l2: {
|
|
1106
|
+
total: l2Ops.length,
|
|
1107
|
+
avg: this.average(l2Ops.map(o => o.duration)),
|
|
1108
|
+
hitRate: this.calculateHitRate(l2Ops)
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
// External API metrics
|
|
1113
|
+
summary.external = {
|
|
1114
|
+
total: this.metrics.externalAPIs.length,
|
|
1115
|
+
avg: this.average(this.metrics.externalAPIs.map(a => a.duration)),
|
|
1116
|
+
p95: this.percentile(this.metrics.externalAPIs.map(a => a.duration), 95),
|
|
1117
|
+
byAPI: this.groupByAPI()
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
return summary;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Calculate average of array
|
|
1125
|
+
* @param {number[]} arr - Array of numbers
|
|
1126
|
+
* @returns {number} Average
|
|
1127
|
+
*/
|
|
1128
|
+
average(arr) {
|
|
1129
|
+
if (arr.length === 0) return 0;
|
|
1130
|
+
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Calculate percentile of array
|
|
1135
|
+
* @param {number[]} arr - Array of numbers
|
|
1136
|
+
* @param {number} p - Percentile (0-100)
|
|
1137
|
+
* @returns {number} Percentile value
|
|
1138
|
+
*/
|
|
1139
|
+
percentile(arr, p) {
|
|
1140
|
+
if (arr.length === 0) return 0;
|
|
1141
|
+
const sorted = arr.sort((a, b) => a - b);
|
|
1142
|
+
const index = Math.ceil((p / 100) * sorted.length) - 1;
|
|
1143
|
+
return sorted[index];
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Calculate cache hit rate
|
|
1148
|
+
* @param {Array} ops - Cache operations
|
|
1149
|
+
* @returns {number} Hit rate percentage
|
|
1150
|
+
*/
|
|
1151
|
+
calculateHitRate(ops) {
|
|
1152
|
+
const hits = ops.filter(o => o.operation === 'get').length;
|
|
1153
|
+
const total = ops.length;
|
|
1154
|
+
return total > 0 ? ((hits / total) * 100).toFixed(2) + '%' : '0%';
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Group external API calls by API
|
|
1159
|
+
* @returns {object} Grouped metrics
|
|
1160
|
+
*/
|
|
1161
|
+
groupByAPI() {
|
|
1162
|
+
const grouped = {};
|
|
1163
|
+
this.metrics.externalAPIs.forEach(call => {
|
|
1164
|
+
if (!grouped[call.api]) {
|
|
1165
|
+
grouped[call.api] = { total: 0, durations: [] };
|
|
1166
|
+
}
|
|
1167
|
+
grouped[call.api].total++;
|
|
1168
|
+
grouped[call.api].durations.push(call.duration);
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
Object.keys(grouped).forEach(api => {
|
|
1172
|
+
grouped[api].avg = this.average(grouped[api].durations);
|
|
1173
|
+
grouped[api].p95 = this.percentile(grouped[api].durations, 95);
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
return grouped;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Clear all metrics
|
|
1181
|
+
*/
|
|
1182
|
+
clear() {
|
|
1183
|
+
this.metrics = {
|
|
1184
|
+
requests: [],
|
|
1185
|
+
queries: [],
|
|
1186
|
+
cacheOps: [],
|
|
1187
|
+
externalAPIs: []
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Global latency profiler instance
|
|
1193
|
+
let latencyProfiler = new LatencyProfiler();
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
---
|
|
1197
|
+
|
|
1198
|
+
## 🔧 Endpoints de Monitoramento de Performance
|
|
1199
|
+
|
|
1200
|
+
### Worker API Endpoints
|
|
1201
|
+
|
|
1202
|
+
```javascript
|
|
1203
|
+
/**
|
|
1204
|
+
* Performance Monitoring Endpoints
|
|
1205
|
+
*/
|
|
1206
|
+
export default {
|
|
1207
|
+
async fetch(request, env, ctx) {
|
|
1208
|
+
// Initialize components
|
|
1209
|
+
if (!cacheManager) cacheManager = new CacheManager(env);
|
|
1210
|
+
if (!queryOptimizer) queryOptimizer = new QueryOptimizer(env.DB);
|
|
1211
|
+
if (!batchProcessor) batchProcessor = new BatchAttributionProcessor(cacheManager, queryOptimizer);
|
|
1212
|
+
|
|
1213
|
+
const url = new URL(request.url);
|
|
1214
|
+
const path = url.pathname;
|
|
1215
|
+
|
|
1216
|
+
// Performance monitoring endpoints
|
|
1217
|
+
if (path === '/api/performance/stats') {
|
|
1218
|
+
return handlePerformanceStats(env);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (path === '/api/performance/cache-stats') {
|
|
1222
|
+
return handleCacheStats(env);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
if (path === '/api/performance/query-stats') {
|
|
1226
|
+
return handleQueryStats(env);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if (path === '/api/performance/latency-summary') {
|
|
1230
|
+
return handleLatencySummary(env);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
if (path === '/api/performance/cache-invalidate') {
|
|
1234
|
+
return handleCacheInvalidate(request, env);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// ... other endpoints
|
|
1238
|
+
|
|
1239
|
+
return new Response('Not Found', { status: 404 });
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Handle /api/performance/stats - Overall performance statistics
|
|
1245
|
+
*/
|
|
1246
|
+
async function handlePerformanceStats(env) {
|
|
1247
|
+
const summary = latencyProfiler.getSummary();
|
|
1248
|
+
|
|
1249
|
+
return new Response(JSON.stringify({
|
|
1250
|
+
success: true,
|
|
1251
|
+
data: {
|
|
1252
|
+
performance: summary,
|
|
1253
|
+
timestamp: new Date().toISOString()
|
|
1254
|
+
}
|
|
1255
|
+
}), {
|
|
1256
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Handle /api/performance/cache-stats - Cache statistics
|
|
1262
|
+
*/
|
|
1263
|
+
async function handleCacheStats(env) {
|
|
1264
|
+
const stats = cacheManager ? cacheManager.getStats() : null;
|
|
1265
|
+
|
|
1266
|
+
return new Response(JSON.stringify({
|
|
1267
|
+
success: true,
|
|
1268
|
+
data: {
|
|
1269
|
+
cache: stats,
|
|
1270
|
+
timestamp: new Date().toISOString()
|
|
1271
|
+
}
|
|
1272
|
+
}), {
|
|
1273
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Handle /api/performance/query-stats - Query statistics
|
|
1279
|
+
*/
|
|
1280
|
+
async function handleQueryStats(env) {
|
|
1281
|
+
const queryMetrics = latencyProfiler.metrics.queries;
|
|
1282
|
+
|
|
1283
|
+
return new Response(JSON.stringify({
|
|
1284
|
+
success: true,
|
|
1285
|
+
data: {
|
|
1286
|
+
queries: {
|
|
1287
|
+
total: queryMetrics.length,
|
|
1288
|
+
avg: queryMetrics.length > 0
|
|
1289
|
+
? queryMetrics.reduce((a, b) => a + b.duration, 0) / queryMetrics.length
|
|
1290
|
+
: 0,
|
|
1291
|
+
slowQueries: queryMetrics.filter(q => q.duration > 100),
|
|
1292
|
+
timestamp: new Date().toISOString()
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}), {
|
|
1296
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Handle /api/performance/latency-summary - Latency summary
|
|
1302
|
+
*/
|
|
1303
|
+
async function handleLatencySummary(env) {
|
|
1304
|
+
const summary = latencyProfiler.getSummary();
|
|
1305
|
+
|
|
1306
|
+
return new Response(JSON.stringify({
|
|
1307
|
+
success: true,
|
|
1308
|
+
data: {
|
|
1309
|
+
latency: {
|
|
1310
|
+
requests: summary.requests,
|
|
1311
|
+
queries: summary.queries,
|
|
1312
|
+
cache: summary.cache,
|
|
1313
|
+
externalAPIs: summary.external
|
|
1314
|
+
},
|
|
1315
|
+
timestamp: new Date().toISOString()
|
|
1316
|
+
}
|
|
1317
|
+
}), {
|
|
1318
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Handle /api/performance/cache-invalidate - Cache invalidation
|
|
1324
|
+
*/
|
|
1325
|
+
async function handleCacheInvalidate(request, env) {
|
|
1326
|
+
if (request.method !== 'POST') {
|
|
1327
|
+
return new Response('Method Not Allowed', { status: 405 });
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
try {
|
|
1331
|
+
const body = await request.json();
|
|
1332
|
+
const { prefix, keys } = body;
|
|
1333
|
+
|
|
1334
|
+
if (prefix) {
|
|
1335
|
+
await cacheManager.invalidateByPrefix(prefix);
|
|
1336
|
+
} else if (keys && Array.isArray(keys)) {
|
|
1337
|
+
for (const key of keys) {
|
|
1338
|
+
await cacheManager.delete(key);
|
|
1339
|
+
}
|
|
1340
|
+
} else {
|
|
1341
|
+
return new Response(JSON.stringify({
|
|
1342
|
+
success: false,
|
|
1343
|
+
error: 'Must provide either prefix or keys'
|
|
1344
|
+
}), {
|
|
1345
|
+
status: 400,
|
|
1346
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
return new Response(JSON.stringify({
|
|
1351
|
+
success: true,
|
|
1352
|
+
message: 'Cache invalidated successfully',
|
|
1353
|
+
timestamp: new Date().toISOString()
|
|
1354
|
+
}), {
|
|
1355
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1356
|
+
});
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
return new Response(JSON.stringify({
|
|
1359
|
+
success: false,
|
|
1360
|
+
error: error.message
|
|
1361
|
+
}), {
|
|
1362
|
+
status: 500,
|
|
1363
|
+
headers: { 'Content-Type': 'application/json' }
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
---
|
|
1370
|
+
|
|
1371
|
+
## 📋 Resumo de Implementação
|
|
1372
|
+
|
|
1373
|
+
### Componentes Criados
|
|
1374
|
+
|
|
1375
|
+
1. **CacheManager** - Gerenciador de caching multi-camada (L1 + L2 + L3)
|
|
1376
|
+
2. **QueryOptimizer** - Otimizador de queries com cache e índices
|
|
1377
|
+
3. **BatchAttributionProcessor** - Processamento em lote de atribuições
|
|
1378
|
+
4. **LatencyProfiler** - Monitoramento de latência em tempo real
|
|
1379
|
+
|
|
1380
|
+
### Endpoints de Monitoramento
|
|
1381
|
+
|
|
1382
|
+
| Endpoint | Método | Descrição |
|
|
1383
|
+
|----------|--------|-----------|
|
|
1384
|
+
| `/api/performance/stats` | GET | Estatísticas gerais de performance |
|
|
1385
|
+
| `/api/performance/cache-stats` | GET | Estatísticas de cache (L1/L2) |
|
|
1386
|
+
| `/api/performance/query-stats` | GET | Estatísticas de queries |
|
|
1387
|
+
| `/api/performance/latency-summary` | GET | Resumo de latência (P50, P95, P99) |
|
|
1388
|
+
| `/api/performance/cache-invalidate` | POST | Invalidação de cache |
|
|
1389
|
+
|
|
1390
|
+
### Melhorias de Performance
|
|
1391
|
+
|
|
1392
|
+
- **Cache Hit Rate**: > 95% (L1 + L2)
|
|
1393
|
+
- **P95 Latency**: < 100ms (tracking), < 500ms (attribution)
|
|
1394
|
+
- **Query Time**: < 50ms (queries otimizadas)
|
|
1395
|
+
- **Memory Usage**: < 128MB (workers)
|
|
1396
|
+
- **CPU Time**: < 50ms (workers)
|
|
1397
|
+
|
|
1398
|
+
---
|
|
1399
|
+
|
|
1400
|
+
## 🔗 Integração com Outros Agentes
|
|
1401
|
+
|
|
1402
|
+
- **attribution-agent.md**: Usa `BatchAttributionProcessor` para processar múltiplas atribuições em paralelo
|
|
1403
|
+
- **security-enterprise-agent.md**: Usa `CacheManager` para cache de configurações de segurança
|
|
1404
|
+
- **master-orchestrator.md**: Usa `LatencyProfiler` para monitorar performance de toda a pipeline
|
|
1405
|
+
|
|
1406
|
+
---
|
|
1407
|
+
|
|
1408
|
+
## ✅ Checklist de Implementação
|
|
1409
|
+
|
|
1410
|
+
- [x] Implementar L1 Memory Cache (request-scoped)
|
|
1411
|
+
- [x] Implementar L2 KV Cache (global edge cache)
|
|
1412
|
+
- [x] Implementar CacheManager com fallback L1 → L2 → D1
|
|
1413
|
+
- [x] Criar índices otimizados para D1
|
|
1414
|
+
- [x] Implementar QueryOptimizer com cache de queries
|
|
1415
|
+
- [x] Implementar BatchAttributionProcessor para processamento em lote
|
|
1416
|
+
- [x] Implementar LatencyProfiler com monitoramento em tempo real
|
|
1417
|
+
- [x] Criar endpoints de monitoramento de performance
|
|
1418
|
+
- [x] Implementar invalidação de cache por prefixo
|
|
1419
|
+
- [x] Configurar TTLs específicos por tipo de cache
|
|
1420
|
+
|
|
1421
|
+
---
|
|
1422
|
+
|
|
1423
|
+
## 📚 Referências
|
|
1424
|
+
|
|
1425
|
+
- [Cloudflare Workers Performance Best Practices](https://developers.cloudflare.com/workers/)
|
|
1426
|
+
- [Cloudflare KV Caching](https://developers.cloudflare.com/workers/runtime-apis/kv/)
|
|
1427
|
+
- [Cloudflare D1 Query Optimization](https://developers.cloudflare.com/d1/)
|
|
1428
|
+
- [SQLite Index Optimization](https://www.sqlite.org/queryplanner.html)
|
|
1429
|
+
|
|
1430
|
+
---
|
|
1431
|
+
|
|
1432
|
+
*Performance Optimization Enterprise Agent v1.0.0 - CDP Edge Quantum Tier*
|