L'Edge Computing révolutionne l'architecture des applications web en rapprochant le traitement des données des utilisateurs finaux. Cette approche distribuée minimise la latence, optimise les performances et améliore drastiquement l'expérience utilisateur globale.
Fondamentaux et architecture Edge Computing L'Edge Computing déplace la logique de traitement traditionnellement centralisée vers des nœuds distribués géographiquement proches des utilisateurs.
Cette décentralisation réduit la distance physique des données, éliminant les goulots d'étranglement réseau et accélérant les temps de réponse. Les Content Delivery Networks (CDN) évoluent vers des plateformes de calcul distribuées capables d'exécuter du code JavaScript, traiter des APIs et gérer des bases de données à la périphérie. Cette transformation architectural permet d'implémenter une logique métier sophistiquée directement sur l'edge. L'approche serverless à la périphérie combine la scalabilité automatique avec une latence ultra-faible. Les fonctions edge s'exécutent au plus près des utilisateurs sans gestion d'infrastructure, optimisant à la fois les coûts et les performances.
Workers et fonctions serverless à la périphérie Les edge workers transforment la façon dont nous
déployons et exécutons la logique applicative en permettant l'exécution de code JavaScript directement sur les points de présence CDN. Cette capacité ouvre des possibilités architecturales révolutionnaires.
// Cloudflare Worker - Exemple d'API edge sophistiquée export default { async fetch(request, env, ctx) { const url = new URL(request.url); const router = new EdgeRouter(); // Configuration du routeur avec middleware intégré router.use(corsMiddleware); router.use(authenticationMiddleware); router.use(rateLimitingMiddleware); router.use(cachingMiddleware); // Routes API avec traitement edge router.get('/api/users/:id', async (request, params) => { return await this.handleUserRequest(request, params, env); }); router.post('/api/analytics', async (request) => { return await this.handleAnalytics(request, env); }); router.get('/api/recommendations', async (request) => { return await this.generateRecommendations(request, env); }); // Proxy intelligent avec optimisations router.all('*', async (request) => { return await this.intelligentProxy(request, env); }); return await router.handle(request); }, // Gestion optimisée des requêtes utilisateur async handleUserRequest(request, params, env) { const userId = params.id; const cacheKey = user:${userId}; const region = request.cf?.colo || 'unknown'; // Tentative de récupération depuis le cache edge let userData = await this.getFromEdgeCache(cacheKey); if (!userData) { // Fallback vers la base de données régionale userData = await this.fetchFromRegionalDB(userId, region, env); if (userData) { // Mise en cache avec TTL adaptatif await this.setEdgeCache(cacheKey, userData, this.calculateTTL(userData)); } } if (!userData) { return new Response(JSON.stringify({ error: 'User not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } }); } // Enrichissement des données avec contexte géographique const enrichedData = await this.enrichWithLocationData(userData, request); return new Response(JSON.stringify(enrichedData), { status: 200, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'public, max-age=300', 'X-Edge-Region': region, 'X-Cache-Status': userData.cached ? 'HIT' : 'MISS' } }); }, // Système d'analytics edge temps réel async handleAnalytics(request, env) { try { const analyticsData = await request.json(); const timestamp = Date.now(); const region = request.cf?.colo || 'unknown'; // Validation des données const validatedData = this.validateAnalyticsData(analyticsData); if (!validatedData.valid) { return new Response(JSON.stringify({ error: 'Invalid data' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } // Enrichissement avec métadonnées edge const enrichedEvent = { ...analyticsData, timestamp, region, country: request.cf?.country, userAgent: request.headers.get('User-Agent'), ip: request.headers.get('CF-Connecting-IP'), edgeProcessingTime: Date.now() - timestamp }; // Agrégation locale temps réel await this.updateLocalMetrics(enrichedEvent, env); // Queue pour traitement asynchrone central ctx.waitUntil(this.queueForCentralProcessing(enrichedEvent, env)); // Mise à jour des compteurs edge await this.incrementEdgeCounters(enrichedEvent, env); return new Response(JSON.stringify({ success: true, processedat: timestamp, region: region }), { status: 200, headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.error('Analytics processing error:', error); return new Response(JSON.stringify({ error: 'Processing failed' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } }, // Génération de recommandations avec ML edge async generateRecommendations(request, env) { const url = new URL(request.url); const userId = url.searchParams.get('user_id'); const category = url.searchParams.get('category') || 'general'; const limit = parseInt(url.searchParams.get('limit')) || 10; if (!userId) { return new Response(JSON.stringify({ error: 'User ID required' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const cacheKey = recommendations:${userId}:${category}; // Vérification du cache edge let recommendations = await this.getFromEdgeCache(cacheKey); if (!recommendations) { // Génération de recommandations via modèle ML edge const userProfile = await this.getUserProfile(userId, env); const contextData = await this.getContextualData(request, env); recommendations = await this.runEdgeMLModel({ userId, userProfile, category, context: contextData, limit }, env); // Mise en cache avec TTL court pour fraîcheur await this.setEdgeCache(cacheKey, recommendations, 600); // 10 minutes } return new Response(JSON.stringify({ recommendations, generatedat: Date.now(), cachestatus: recommendations.cached ? 'hit' : 'miss', modelversion: env.MLMODELVERSION || '1.0' }), { status: 200, headers: { 'Content-Type': 'application/json', 'Cache-Control': 'private, max-age=300' } }); }, // Proxy intelligent avec optimisations automatiques async intelligentProxy(request, env) { const url = new URL(request.url); const startTime = Date.now(); // Détermination du meilleur endpoint backend const backendUrl = await this.selectOptimalBackend(request, env); // Configuration de la requête optimisée const optimizedRequest = new Request(backendUrl, { method: request.method, headers: this.optimizeHeaders(request.headers), body: request.body }); // Ajout de headers de tracing edge optimizedRequest.headers.set('X-Edge-ID', crypto.randomUUID()); optimizedRequest.headers.set('X-Edge-Region', request.cf?.colo || 'unknown'); optimizedRequest.headers.set('X-Edge-Timestamp', startTime.toString()); try { // Tentative de requête avec timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout const response = await fetch(optimizedRequest, { signal: controller.signal }); clearTimeout(timeoutId); // Optimisation de la réponse const optimizedResponse = await this.optimizeResponse(response, request); // Métriques de performance const processingTime = Date.now() - startTime; optimizedResponse.headers.set('X-Edge-Processing-Time', processingTime.toString()); // Logging des métriques ctx.waitUntil(this.logProxyMetrics({ backend: backendUrl, processingTime, status: optimizedResponse.status, region: request.cf?.colo }, env)); return optimizedResponse; } catch (error) { console.error('Proxy error:', error); // Tentative de fallback if (error.name === 'AbortError') { return await this.handleTimeout(request, env); } return await this.handleProxyError(error, request, env); } }, // Sélection intelligente du backend optimal async selectOptimalBackend(request, env) { const region = request.cf?.colo; const country = request.cf?.country; // Récupération des métriques des backends const backendMetrics = await this.getBackendMetrics(env); // Algorithme de sélection basé sur la latence et la charge const candidates = env.BACKENDURLS.split(',').map(url => ({ url: url.trim(), metrics: backendMetrics[url] || { latency: 1000, load: 0.5 } })); // Score composite : latence + charge + proximité géographique const scored = candidates.map(backend => ({ ...backend, score: this.calculateBackendScore(backend, region, country) })); // Sélection du meilleur candidat const selected = scored.reduce((best, current) => current.score > best.score ? current : best ); return selected.url; }, // Optimisation automatique des headers optimizeHeaders(originalHeaders) { const optimized = new Headers(); // Conservation des headers essentiels const essentialHeaders = [ 'authorization', 'content-type', 'user-agent', 'accept', 'accept-language', 'accept-encoding' ]; essentialHeaders.forEach(header => { const value = originalHeaders.get(header); if (value) { optimized.set(header, value); } }); // Ajout de headers d'optimisation optimized.set('Accept-Encoding', 'gzip, deflate, br'); optimized.set('Connection', 'keep-alive'); return optimized; }, // Machine Learning à la périphérie async runEdgeMLModel(input, env) { // Chargement du modèle depuis le cache edge let model = await this.getFromEdgeCache('mlmodelv1'); if (!model) { // Chargement du modèle depuis le stockage model = await this.loadMLModel(env.MLMODELURL); await this.setEdgeCache('mlmodel_v1', model, 3600); // 1 heure } // Préparation des features const features = this.extractFeatures(input); // Inférence rapide const predictions = await this.runInference(model, features); // Post-traitement des résultats const recommendations = this.processMLOutput(predictions, input); return recommendations; }, // Gestion du cache edge avec stratégies avancées async getFromEdgeCache(key) { try { const cached = await caches.default.match(https://cache/${key}); if (cached) { const data = await cached.json(); data._cached = true; return data; } } catch (error) { console.error('Cache read error:', error); } return null; }, async setEdgeCache(key, data, ttl = 300) { try { const response = new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json', 'Cache-Control': public, max-age=${ttl}, 'X-Cached-At': Date.now().toString() } }); await caches.default.put(https://cache/${key}, response); } catch (error) { console.error('Cache write error:', error); } } }; // Classe de routeur edge optimisé class EdgeRouter { constructor() { this.routes = []; this.middleware = []; } use(middleware) { this.middleware.push(middleware); } addRoute(method, pattern, handler) { const routeRegex = this.patternToRegex(pattern); this.routes.push({ method, pattern, regex: routeRegex, handler }); } get(pattern, handler) { this.addRoute('GET', pattern, handler); } post(pattern, handler) { this.addRoute('POST', pattern, handler); } put(pattern, handler) { this.addRoute('PUT', pattern, handler); } delete(pattern, handler) { this.addRoute('DELETE', pattern, handler); } all(pattern, handler) { this.addRoute('', pattern, handler); } async handle(request) { const url = new URL(request.url); const method = request.method; // Application des middlewares for (const middleware of this.middleware) { const result = await middleware(request); if (result instanceof Response) { return result; } } // Recherche de route correspondante for (const route of this.routes) { if ((route.method === '' || route.method === method)) { const match = url.pathname.match(route.regex); if (match) { const params = this.extractParams(route.pattern, match); return await route.handler(request, params); } } } return new Response('Not Found', { status: 404 }); } patternToRegex(pattern) { return new RegExp('^' + pattern.replace(/:\w+/g, '([^/]+)').replace(/\/g, '.') + '$'); } extractParams(pattern, match) { const paramNames = pattern.match(/:\w+/g) || []; const params = {}; paramNames.forEach((param, index) => { params[param.slice(1)] = match[index + 1]; }); return params; } } // Middlewares edge spécialisés const corsMiddleware = async (request) => { if (request.method === 'OPTIONS') { return new Response(null, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Max-Age': '86400' } }); } }; const rateLimitingMiddleware = async (request) => { const ip = request.headers.get('CF-Connecting-IP'); const key = rate_limit:${ip}; // Implémentation simple de rate limiting avec cache edge const current = await caches.default.match(https://ratelimit/${key}); let count = 0; if (current) { const data = await current.json(); count = data.count; } if (count > 100) { // 100 requêtes par minute return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json' } }); } // Mise à jour du compteur const response = new Response(JSON.stringify({ count: count + 1 }), { headers: { 'Cache-Control': 'public, max-age=60' // 1 minute } }); await caches.default.put(https://ratelimit/${key}, response); };
Optimisations de cache distribué et CDN intelligent L'évolution des CDN vers des plateformes de ca
lcul distribuées permet d'implémenter des stratégies de cache sophistiquées avec invalidation intelligente et prédiction de contenu. Cette approche optimise dramatiquement les performances globales.
// Système de cache distribué intelligent class IntelligentEdgeCache { constructor() { this.cacheStrategies = new Map(); this.hitRates = new Map(); this.predictiveEngine = new CachePredictionEngine(); this.invalidationEngine = new SmartInvalidationEngine(); } // Configuration des stratégies de cache par type de contenu configureStrategies() { this.cacheStrategies.set('apidata', { ttl: 300, // 5 minutes staleWhileRevalidate: 600, // 10 minutes tags: ['api', 'userdata'], conditions: { method: 'GET', contentType: 'application/json' }, varyBy: ['userid', 'region', 'devicetype'] }); this.cacheStrategies.set('staticcontent', { ttl: 86400, // 24 heures staleWhileRevalidate: 604800, // 7 jours tags: ['static', 'assets'], conditions: { extensions: ['.js', '.css', '.png', '.jpg', '.svg'] }, varyBy: ['acceptencoding'] }); this.cacheStrategies.set('usercontent', { ttl: 1800, // 30 minutes staleWhileRevalidate: 3600, // 1 heure tags: ['usergenerated', 'personalized'], conditions: { authenticated: true }, varyBy: ['userid', 'preferences'] }); this.cacheStrategies.set('mlpredictions', { ttl: 600, // 10 minutes staleWhileRevalidate: 1200, // 20 minutes tags: ['ml', 'predictions'], conditions: { path: '/api/recommendations' }, varyBy: ['userprofile', 'context'] }); } // Mise en cache intelligente avec stratégie adaptative async intelligentCache(request, response, context = {}) { const cacheKey = this.generateCacheKey(request, context); const strategy = this.selectOptimalStrategy(request, context); if (!strategy) { return response; // Pas de mise en cache } // Analyse de la performance de cache historique const historicalPerformance = await this.getHistoricalPerformance(cacheKey); // Ajustement adaptatif du TTL const adaptiveTTL = this.calculateAdaptiveTTL( strategy.ttl, historicalPerformance, context ); // Enrichissement des headers de cache const enrichedResponse = new Response(response.body, { status: response.status, statusText: response.statusText, headers: { ...response.headers, 'Cache-Control': this.buildCacheControlHeader(strategy, adaptiveTTL), 'X-Cache-Strategy': strategy.name, 'X-Cache-TTL': adaptiveTTL.toString(), 'X-Cache-Tags': strategy.tags.join(','), 'X-Cache-Key': cacheKey } }); // Stockage avec métadonnées const cacheMetadata = { strategy: strategy.name, ttl: adaptiveTTL, tags: strategy.tags, createdAt: Date.now(), varyBy: strategy.varyBy, region: request.cf?.colo || 'unknown' }; await this.storeCacheEntry(cacheKey, enrichedResponse, cacheMetadata); // Mise à jour des statistiques de performance this.updateCacheStats(strategy.name, 'store', cacheKey); // Déclenchement de la prédiction de cache this.predictiveEngine.analyzePattern(request, context); return enrichedResponse; } // Récupération intelligente avec fallback strategies async intelligentRetrieve(request, context = {}) { const cacheKey = this.generateCacheKey(request, context); // Tentative de récupération primaire let cachedResponse = await this.getCacheEntry(cacheKey); if (cachedResponse) { const metadata = await this.getCacheMetadata(cacheKey); // Vérification de la fraîcheur if (this.isStale(metadata)) { // Stale-while-revalidate strategy if (this.canServeStale(metadata)) { // Déclenchement de la revalidation en arrière-plan this.scheduleRevalidation(request, context); // Retour du contenu périmé avec headers appropriés cachedResponse.headers.set('X-Cache-Status', 'STALE'); cachedResponse.headers.set('X-Cache-Age', this.calculateAge(metadata)); this.updateCacheStats(metadata.strategy, 'stalehit', cacheKey); return cachedResponse; } // Contenu trop périmé, suppression du cache await this.deleteCacheEntry(cacheKey); cachedResponse = null; } else { // Cache frais, succès cachedResponse.headers.set('X-Cache-Status', 'HIT'); cachedResponse.headers.set('X-Cache-Age', this.calculateAge(metadata)); this.updateCacheStats(metadata.strategy, 'hit', cacheKey); return cachedResponse; } } // Tentative de cache de second niveau (regional) const regionalCache = await this.getRegionalCache(request, context); if (regionalCache) { // Promotion vers le cache local await this.promoteToLocalCache(cacheKey, regionalCache); regionalCache.headers.set('X-Cache-Status', 'REGIONAL_HIT'); this.updateCacheStats('regional', 'hit', cacheKey); return regionalCache; } // Aucun cache disponible this.updateCacheStats('unknown', 'miss', cacheKey); return null; } // Génération de clé de cache contextuelle generateCacheKey(request, context) { const url = new URL(request.url); const baseKey = ${request.method}:${url.pathname}; // Facteurs de variation const varyFactors = []; // Headers de variation standard if (request.headers.get('Accept-Encoding')) { varyFactors.push(enc:${request.headers.get('Accept-Encoding')}); } if (request.headers.get('Accept-Language')) { varyFactors.push(lang:${request.headers.get('Accept-Language')}); } // Contexte utilisateur if (context.userId) { varyFactors.push(user:${context.userId}); } if (context.deviceType) { varyFactors.push(device:${context.deviceType}); } // Contexte géographique if (request.cf?.country) { varyFactors.push(country:${request.cf.country}); } // Query parameters pertinents const relevantParams = ['category', 'limit', 'filter', 'sort']; relevantParams.forEach(param => { const value = url.searchParams.get(param); if (value) { varyFactors.push(${param}:${value}); } }); // Construction de la clé finale const varyString = varyFactors.sort().join('|'); return ${baseKey}${varyString ? '?' + this.hashString(varyString) : ''}; } // Sélection de stratégie optimale basée sur l'analyse selectOptimalStrategy(request, context) { const url = new URL(request.url); const method = request.method; const contentType = request.headers.get('Content-Type'); for (const [name, strategy] of this.cacheStrategies) { // Vérification des conditions de base if (strategy.conditions.method && strategy.conditions.method !== method) { continue; } if (strategy.conditions.contentType && !contentType?.includes(strategy.conditions.contentType)) { continue; } // Vérification des extensions if (strategy.conditions.extensions) { const hasMatchingExtension = strategy.conditions.extensions.some(ext => url.pathname.endsWith(ext) ); if (!hasMatchingExtension) { continue; } } // Vérification de l'authentification if (strategy.conditions.authenticated && !context.authenticated) { continue; } // Vérification du chemin if (strategy.conditions.path && !url.pathname.includes(strategy.conditions.path)) { continue; } return { ...strategy, name }; } return null; } // TTL adaptatif basé sur les patterns d'utilisation calculateAdaptiveTTL(baseTTL, historicalPerformance, context) { let adjustedTTL = baseTTL; // Ajustement basé sur le taux de hit if (historicalPerformance?.hitRate) { if (historicalPerformance.hitRate > 0.8) { adjustedTTL = 1.5; // Augmentation pour contenu populaire } else if (historicalPerformance.hitRate < 0.3) { adjustedTTL = 0.5; // Réduction pour contenu peu utilisé } } // Ajustement basé sur la fréquence de mise à jour if (historicalPerformance?.updateFrequency) { const avgUpdateInterval = historicalPerformance.updateFrequency; adjustedTTL = Math.min(adjustedTTL, avgUpdateInterval 0.8); } // Ajustement géographique if (context.region === 'high_latency') { adjustedTTL = 1.2; // Augmentation pour régions à latence élevée } // Ajustement selon l'heure de pointe const hour = new Date().getHours(); if (hour >= 8 && hour <= 20) { // Heures de pointe adjustedTTL *= 0.8; // Réduction pour fraîcheur } return Math.max(60, Math.min(adjustedTTL, 86400)); // Entre 1 minute et 24 heures } // Prédiction de contenu et préchargement async predictivePreloading(request, context) { const predictions = await this.predictiveEngine.generatePredictions( request, context ); const preloadPromises = predictions .filter(prediction => prediction.confidence > 0.7) .slice(0, 5) // Limite à 5 prédictions .map(prediction => this.preloadContent(prediction)); // Exécution en arrière-plan return Promise.allSettled(preloadPromises); } async preloadContent(prediction) { try { const preloadRequest = new Request(prediction.url, { method: 'GET', headers: prediction.headers || {} }); const response = await fetch(preloadRequest); if (response.ok) { await this.intelligentCache(preloadRequest, response, prediction.context); console.log(Preloaded: ${prediction.url}); } } catch (error) { console.warn(Preload failed for ${prediction.url}:, error); } } // Invalidation intelligente de cache async smartInvalidation(tags, strategy = 'immediate') { const affectedKeys = await this.findCacheKeysByTags(tags); switch (strategy) { case 'immediate': await Promise.all( affectedKeys.map(key => this.deleteCacheEntry(key)) ); break; case 'lazy': await Promise.all( affectedKeys.map(key => this.markCacheStale(key)) ); break; case 'gradual': await this.scheduleGradualInvalidation(affectedKeys); break; default: throw new Error(Unknown invalidation strategy: ${strategy}); } console.log(Invalidated ${affectedKeys.length} cache entries for tags: ${tags.join(', ')}); } // Statistiques et monitoring generateCacheReport() { const strategies = Array.from(this.cacheStrategies.keys()); const report = { timestamp: Date.now(), strategies: {} }; strategies.forEach(strategy => { const stats = this.getCacheStats(strategy); report.strategies[strategy] = { hitRate: stats.hits / (stats.hits + stats.misses || 1), totalRequests: stats.hits + stats.misses, staleHits: stats.staleHits || 0, averageSize: stats.totalSize / (stats.entries || 1), efficiency: this.calculateEfficiency(stats) }; }); return report; } calculateEfficiency(stats) { const hitRate = stats.hits / (stats.hits + stats.misses || 1); const freshness = 1 - (stats.staleHits / (stats.hits || 1)); return (hitRate 0.7) + (freshness 0.3); } hashString(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash).toString(36); } } // Moteur de prédiction de cache utilisant l'apprentissage class CachePredictionEngine { constructor() { this.patterns = new Map(); this.userBehaviors = new Map(); this.globalTrends = new Map(); } analyzePattern(request, context) { const url = new URL(request.url); const pattern = this.extractPattern(url, context); if (!this.patterns.has(pattern)) { this.patterns.set(pattern, { requests: [], followUps: new Map(), timing: [] }); } const patternData = this.patterns.get(pattern); patternData.requests.push({ timestamp: Date.now(), url: request.url, context }); // Analyse des requêtes suivantes (dans une fenêtre de 5 minutes) setTimeout(() => { this.analyzeFollowUpRequests(pattern, request.url); }, 5000); } async generatePredictions(request, context) { const url = new URL(request.url); const pattern = this.extractPattern(url, context); const predictions = []; // Prédictions basées sur les patterns historiques const patternPredictions = this.getPredictionsFromPattern(pattern, context); predictions.push(...patternPredictions); // Prédictions basées sur le comportement utilisateur if (context.userId) { const userPredictions = this.getPredictionsFromUserBehavior( context.userId, request ); predictions.push(...userPredictions); } // Prédictions basées sur les tendances globales const trendPredictions = this.getPredictionsFromTrends(request, context); predictions.push(...trendPredictions); // Scoring et filtrage des prédictions return this.scorePredictions(predictions) .filter(pred => pred.confidence > 0.5) .sort((a, b) => b.confidence - a.confidence); } scorePredictions(predictions) { return predictions.map(prediction => { let confidence = prediction.baseConfidence || 0.5; // Facteurs d'ajustement if (prediction.source === 'pattern' && prediction.frequency > 5) { confidence = 1.3; } if (prediction.source === 'user_behavior' && prediction.consistency > 0.8) { confidence = 1.2; } if (prediction.recentActivity) { confidence *= 1.1; } return { ...prediction, confidence: Math.min(confidence, 1.0) }; }); } }
Bases de données distribuées et synchronisation edge La gestion des données à la périphérie n�
�cessite des stratégies de synchronisation sophistiquées permettant la cohérence éventuelle tout en maintenant la performance locale. Les bases de données edge évoluent vers des solutions hybrides combinant cache et persistance.
// Système de base de données distribuée edge class EdgeDatabase { constructor(config = {}) { this.localCache = new Map(); this.syncQueue = []; this.conflictResolver = new ConflictResolver(); this.replicationManager = new ReplicationManager(); this.consistencyLevel = config.consistencyLevel || 'eventual'; this.syncStrategy = config.syncStrategy || 'optimistic'; this.initializeDatabase(); } async initializeDatabase() { // Configuration des indexes locaux this.indexes = { primary: new Map(), secondary: new Map(), fullText: new Map() }; // Chargement des données critiques await this.loadCriticalData(); // Démarrage de la synchronisation this.startSyncProcess(); } // Lecture optimisée avec fallback hiérarchique async get(key, options = {}) { const startTime = Date.now(); let result = null; let source = 'unknown'; try { // 1. Cache local (plus rapide) result = this.localCache.get(key); if (result && this.isValid(result, options)) { source = 'localcache'; result.metadata = { source, latency: Date.now() - startTime }; return result; } // 2. Stockage local edge result = await this.getFromLocalStorage(key); if (result && this.isValid(result, options)) { // Promotion vers le cache this.localCache.set(key, result); source = 'localstorage'; result.metadata = { source, latency: Date.now() - startTime }; return result; } // 3. Cache régional if (options.allowRegional !== false) { result = await this.getFromRegionalCache(key); if (result && this.isValid(result, options)) { // Promotion vers les niveaux locaux await this.promoteData(key, result); source = 'regionalcache'; result.metadata = { source, latency: Date.now() - startTime }; return result; } } // 4. Base de données centrale (dernier recours) if (options.allowCentral !== false) { result = await this.getFromCentralDB(key); if (result) { // Stockage avec réplication vers les niveaux inférieurs await this.storeWithReplication(key, result); source = 'centraldb'; result.metadata = { source, latency: Date.now() - startTime }; return result; } } return null; } catch (error) { console.error(Database get error for key ${key}:, error); // Tentative avec données périmées en cas d'erreur if (options.allowStale) { const staleData = await this.getStaleData(key); if (staleData) { staleData.metadata = { source: 'stale', error: error.message }; return staleData; } } throw error; } } // Écriture avec résolution de conflits async set(key, value, options = {}) { const timestamp = Date.now(); const writeId = crypto.randomUUID(); const dataEntry = { key, value, timestamp, writeId, version: options.version || this.generateVersion(), metadata: { region: options.region || 'unknown', source: 'edgewrite', ...options.metadata } }; try { // Écriture locale immédiate await this.writeToLocalStorage(key, dataEntry); this.localCache.set(key, dataEntry); // Ajout à la queue de synchronisation this.syncQueue.push({ operation: 'set', key, data: dataEntry, timestamp, writeId, priority: options.priority || 'normal' }); // Synchronisation selon la stratégie if (this.syncStrategy === 'immediate' || options.immediate) { await this.syncImmediate(writeId); } else if (this.syncStrategy === 'batched') { this.scheduleBatchSync(); } return { success: true, writeId, timestamp, synchronized: this.syncStrategy === 'immediate' }; } catch (error) { console.error(Database set error for key ${key}:, error); throw error; } } // Requêtes complexes avec optimisation edge async query(conditions, options = {}) { const startTime = Date.now(); const queryId = crypto.randomUUID(); try { // Analyse de la requête pour optimisation const queryPlan = this.analyzeQuery(conditions, options); // Exécution locale si les données sont disponibles if (queryPlan.canExecuteLocally) { const localResults = await this.executeLocalQuery(conditions, options); if (localResults.length > 0 || queryPlan.localOnly) { return { results: localResults, metadata: { source: 'local', executionTime: Date.now() - startTime, queryId } }; } } // Requête hybride (local + distant) if (queryPlan.useHybrid) { const hybridResults = await this.executeHybridQuery(conditions, options); return { results: hybridResults, metadata: { source: 'hybrid', executionTime: Date.now() - startTime, queryId } }; } // Requête distante avec cache des résultats const remoteResults = await this.executeRemoteQuery(conditions, options); // Cache des résultats pour futures requêtes similaires await this.cacheQueryResults(conditions, remoteResults); return { results: remoteResults, metadata: { source: 'remote', executionTime: Date.now() - startTime, queryId } }; } catch (error) { console.error(Query execution error:, error); throw error; } } // Synchronisation intelligente avec résolution de conflits async startSyncProcess() { // Synchronisation périodique setInterval(() => { this.processSyncQueue(); }, 5000); // Toutes les 5 secondes // Synchronisation en temps réel pour les opérations critiques setInterval(() => { this.syncCriticalData(); }, 1000); // Toutes les secondes // Nettoyage périodique setInterval(() => { this.cleanupStaleData(); }, 300000); // Toutes les 5 minutes } async processSyncQueue() { if (this.syncQueue.length === 0) return; // Tri par priorité et timestamp this.syncQueue.sort((a, b) => { if (a.priority !== b.priority) { return a.priority === 'high' ? -1 : 1; } return a.timestamp - b.timestamp; }); // Traitement par batches const batchSize = 10; const batch = this.syncQueue.splice(0, batchSize); try { await this.syncBatch(batch); } catch (error) { console.error('Batch sync error:', error); // Remise en queue avec compteur de retry batch.forEach(item => { item.retryCount = (item.retryCount || 0) + 1; if (item.retryCount < 3) { this.syncQueue.push(item); } }); } } async syncBatch(batch) { const syncData = { batch_id: crypto.randomUUID(), timestamp: Date.now(), region: this.region, operations: batch }; // Envoi vers le service de synchronisation central const response = await fetch('/api/sync/batch', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Edge-Region': this.region }, body: JSON.stringify(syncData) }); if (!response.ok) { throw new Error(Sync failed: ${response.status} ${response.statusText}); } const result = await response.json(); // Traitement des conflits détectés if (result.conflicts && result.conflicts.length > 0) { await this.resolveConflicts(result.conflicts); } // Mise à jour des métadonnées de synchronisation batch.forEach(item => { this.updateSyncMetadata(item.key, { lastSync: Date.now(), syncId: result.syncid }); }); } // Résolution automatique de conflits async resolveConflicts(conflicts) { for (const conflict of conflicts) { try { const resolution = await this.conflictResolver.resolve(conflict); if (resolution.action === 'merge') { const mergedValue = this.conflictResolver.merge( conflict.localValue, conflict.remoteValue, conflict.schema ); await this.set(conflict.key, mergedValue, { version: resolution.newVersion, immediate: true }); } else if (resolution.action === 'useremote') { await this.set(conflict.key, conflict.remoteValue, { version: conflict.remoteVersion, immediate: false }); } else if (resolution.action === 'uselocal') { // Marquer pour re-synchronisation forcée this.syncQueue.push({ operation: 'forcesync', key: conflict.key, data: conflict.localValue, priority: 'high' }); } console.log(Conflict resolved for key ${conflict.key}: ${resolution.action}); } catch (error) { console.error(Conflict resolution failed for ${conflict.key}:, error); } } } // Gestion des transactions distribuées async transaction(operations) { const transactionId = crypto.randomUUID(); const rollbackData = []; try { // Phase 1: Préparation - validation et verrouillage for (const operation of operations) { const currentValue = await this.get(operation.key); rollbackData.push({ key: operation.key, value: currentValue }); // Validation des contraintes if (!this.validateOperation(operation, currentValue)) { throw new Error(Validation failed for operation on key ${operation.key}); } // Verrouillage optimiste await this.lockKey(operation.key, transactionId); } // Phase 2: Commit - application des opérations const results = []; for (const operation of operations) { const result = await this.applyOperation(operation, transactionId); results.push(result); } // Phase 3: Déverrouillage et synchronisation for (const operation of operations) { await this.unlockKey(operation.key, transactionId); } // Synchronisation de la transaction await this.syncTransaction(transactionId, operations); return { success: true, transactionId, results }; } catch (error) { console.error(Transaction ${transactionId} failed:, error); // Rollback try { await this.rollbackTransaction(transactionId, rollbackData); } catch (rollbackError) { console.error(Rollback failed for transaction ${transactionId}:, rollbackError); } throw error; } } // Métriques et monitoring de performance getPerformanceMetrics() { return { cacheHitRate: this.calculateCacheHitRate(), averageLatency: this.calculateAverageLatency(), syncQueueSize: this.syncQueue.length, localStorageSize: this.calculateLocalStorageSize(), conflictResolutionRate: this.conflictResolver.getResolutionRate(), dataFreshness: this.calculateDataFreshness() }; } // Optimisation automatique basée sur les métriques async optimizePerformance() { const metrics = this.getPerformanceMetrics(); // Ajustement de la taille du cache local if (metrics.cacheHitRate < 0.8) { this.increaseCacheSize(); } else if (metrics.cacheHitRate > 0.95 && this.localCache.size > 1000) { this.decreaseCacheSize(); } // Ajustement de la stratégie de synchronisation if (metrics.syncQueueSize > 100) { this.increaseSyncFrequency(); } // Nettoyage proactif des données anciennes if (metrics.dataFreshness < 0.7) { await this.cleanupOldData(); } } } // Résolveur de conflits intelligent class ConflictResolver { constructor() { this.resolutionStrategies = new Map(); this.configureDefaultStrategies(); } configureDefaultStrategies() { // Stratégie Last-Writer-Wins this.resolutionStrategies.set('lww', (local, remote) => { return local.timestamp > remote.timestamp ? { action: 'uselocal', value: local } : { action: 'useremote', value: remote }; }); // Stratégie de fusion automatique this.resolutionStrategies.set('merge', (local, remote, schema) => { const merged = this.deepMerge(local.value, remote.value, schema); return { action: 'merge', value: { ...local, value: merged, version: this.generateMergeVersion(local.version, remote.version) } }; }); // Stratégie de priorité par région this.resolutionStrategies.set('regionpriority', (local, remote) => { const localRegion = local.metadata?.region || 'unknown'; const remoteRegion = remote.metadata?.region || 'unknown'; const regionPriority = { 'us-east': 3, 'eu-west': 2, 'ap-southeast': 1 }; const localPriority = regionPriority[localRegion] || 0; const remotePriority = regionPriority[remoteRegion] || 0; return localPriority >= remotePriority ? { action: 'uselocal', value: local } : { action: 'use_remote', value: remote }; }); } async resolve(conflict) { const strategy = conflict.resolutionStrategy || 'lww'; const resolver = this.resolutionStrategies.get(strategy); if (!resolver) { throw new Error(Unknown conflict resolution strategy: ${strategy}); } return resolver(conflict.localValue, conflict.remoteValue, conflict.schema); } deepMerge(local, remote, schema = {}) { if (typeof local !== 'object' || typeof remote !== 'object') { return remote; // Fallback to remote value } const merged = { ...local }; for (const [key, remoteValue] of Object.entries(remote)) { const localValue = local[key]; const fieldSchema = schema[key] || {}; if (localValue === undefined) { merged[key] = remoteValue; } else if (fieldSchema.mergeStrategy === 'append' && Array.isArray(localValue)) { merged[key] = [...localValue, ...remoteValue]; } else if (fieldSchema.mergeStrategy === 'max') { merged[key] = Math.max(localValue, remoteValue); } else if (fieldSchema.mergeStrategy === 'concat') { merged[key] = localValue + remoteValue; } else if (typeof localValue === 'object' && typeof remoteValue === 'object') { merged[key] = this.deepMerge(localValue, remoteValue, fieldSchema); } else { merged[key] = remoteValue; // Default to remote value } } return merged; } }
Monitoring et analytics distribuées La surveillance d'une infrastructure edge nécessite une approche holistique collectant métriques, traces et logs à travers tous les points de présence.
L'agrégation intelligente des données fournit une visibilité complète des performances globales.
// Système de monitoring edge distribué class EdgeMonitoringSystem { constructor(config = {}) { this.metricsCollector = new MetricsCollector(); this.traceCollector = new TraceCollector(); this.logAggregator = new LogAggregator(); this.alertingSystem = new EdgeAlertingSystem(); this.dashboardGenerator = new RealTimeDashboard(); this.region = config.region || 'unknown'; this.nodeId = config.nodeId || crypto.randomUUID(); this.initializeMonitoring(); } initializeMonitoring() { // Configuration des collecteurs de métriques this.setupMetricsCollection(); // Initialisation du tracing distribué this.setupDistributedTracing(); // Configuration de l'agrégation de logs this.setupLogAggregation(); // Démarrage des health checks this.startHealthChecks(); // Configuration des alertes automatiques this.setupAutomaticAlerting(); } setupMetricsCollection() { // Métriques de performance edge this.metricsCollector.defineMetric('edgerequestlatency', { type: 'histogram', buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000], labels: ['region', 'statuscode', 'cachestatus'] }); this.metricsCollector.defineMetric('edgecachehitrate', { type: 'gauge', labels: ['region', 'cachetype'] }); this.metricsCollector.defineMetric('edgefunctionduration', { type: 'histogram', buckets: [1, 10, 50, 100, 200, 500, 1000, 2000], labels: ['functionname', 'region', 'success'] }); this.metricsCollector.defineMetric('edgebandwidthusage', { type: 'counter', labels: ['region', 'contenttype', 'direction'] }); // Collection automatique toutes les 10 secondes setInterval(() => { this.collectSystemMetrics(); }, 10000); } setupDistributedTracing() { // Configuration OpenTelemetry pour edge const tracer = this.traceCollector.getTracer('edge-system', '1.0.0'); // Middleware de tracing pour les requêtes this.traceMiddleware = (request) => { const span = tracer.startSpan('edgerequest', { attributes: { 'edge.region': this.region, 'edge.nodeid': this.nodeId, 'http.method': request.method, 'http.url': request.url, 'useragent.original': request.headers.get('User-Agent') } }); return { span, addEvent: (name, attributes) => span.addEvent(name, attributes), setStatus: (status) => span.setStatus(status), end: () => span.end() }; }; } setupLogAggregation() { // Configuration des niveaux de log this.logAggregator.configure({ levels: ['error', 'warn', 'info', 'debug'], defaultLevel: 'info', format: 'structured', transport: 'edgestream' }); // Contexte global pour tous les logs this.logAggregator.setGlobalContext({ region: this.region, nodeId: this.nodeId, version: '1.0.0' }); // Rotation automatique des logs setInterval(() => { this.logAggregator.rotate(); }, 3600000); // Toutes les heures } // Monitoring en temps réel des requêtes edge async monitorRequest(request, response, processingTime) { const url = new URL(request.url); const status = response.status; const cacheStatus = response.headers.get('X-Cache-Status') || 'unknown'; // Métriques de latence this.metricsCollector.observe('edgerequestlatency', processingTime, { region: this.region, statuscode: status.toString(), cachestatus: cacheStatus }); // Métriques de bande passante const responseSize = this.getResponseSize(response); this.metricsCollector.increment('edgebandwidthusage', responseSize, { region: this.region, contenttype: response.headers.get('Content-Type') || 'unknown', direction: 'outbound' }); // Log structuré pour analyse this.logAggregator.info('edgerequestprocessed', { method: request.method, path: url.pathname, status, processingtime: processingTime, cachestatus: cacheStatus, responsesize: responseSize, usercountry: request.cf?.country, userasn: request.cf?.asn }); // Détection d'anomalies await this.detectAnomalies({ latency: processingTime, status, cacheStatus, path: url.pathname }); } // Détection automatique d'anomalies async detectAnomalies(requestData) { const { latency, status, cacheStatus, path } = requestData; // Seuils adaptatifs basés sur l'historique const thresholds = await this.getAdaptiveThresholds(path); // Détection de latence anormale if (latency > thresholds.latency.p95 2) { await this.alertingSystem.triggerAlert({ type: 'high_latency', severity: latency > thresholds.latency.p99 2 ? 'critical' : 'warning', region: this.region, value: latency, threshold: thresholds.latency.p95, path }); } // Détection de taux d'erreur anormal if (status >= 500) { const recentErrorRate = await this.calculateRecentErrorRate(path); if (recentErrorRate > thresholds.errorRate.normal * 3) { await this.alertingSystem.triggerAlert({ type: 'higherrorrate', severity: 'critical', region: this.region, value: recentErrorRate, threshold: thresholds.errorRate.normal, path }); } } // Détection de problème de cache if (cacheStatus === 'MISS' && thresholds.cacheHitRate > 0.8) { const recentCacheHitRate = await this.calculateRecentCacheHitRate(path); if (recentCacheHitRate < 0.5) { await this.alertingSystem.triggerAlert({ type: 'cacheperformance', severity: 'warning', region: this.region, value: recentCacheHitRate, path }); } } } // Health checks complets du nœud edge async performHealthCheck() { const healthData = { timestamp: Date.now(), region: this.region, nodeId: this.nodeId, status: 'healthy', checks: {} }; try { // Vérification des fonctions edge healthData.checks.functions = await this.checkEdgeFunctions(); // Vérification du cache healthData.checks.cache = await this.checkCacheHealth(); // Vérification de la connectivité healthData.checks.connectivity = await this.checkConnectivity(); // Vérification des ressources système healthData.checks.resources = await this.checkSystemResources(); // Vérification de la synchronisation de données healthData.checks.dataSync = await this.checkDataSynchronization(); // Détermination du statut global const allChecksHealthy = Object.values(healthData.checks) .every(check => check.status === 'healthy'); healthData.status = allChecksHealthy ? 'healthy' : 'degraded'; // Log du health check this.logAggregator.info('healthcheckcompleted', healthData); return healthData; } catch (error) { healthData.status = 'unhealthy'; healthData.error = error.message; this.logAggregator.error('healthcheckfailed', { error: error.message, stack: error.stack }); return healthData; } } // Génération de rapports analytiques async generateAnalyticsReport(timeRange = '1h') { const endTime = Date.now(); const startTime = endTime - this.parseTimeRange(timeRange); const report = { timeRange: { start: startTime, end: endTime }, region: this.region, summary: {}, details: {} }; // Métriques de performance const performanceMetrics = await this.metricsCollector.query({ metrics: ['edgerequestlatency', 'edgefunctionduration'], timeRange: { start: startTime, end: endTime } }); report.summary.performance = { averageLatency: this.calculateAverage(performanceMetrics.edgerequestlatency), p95Latency: this.calculatePercentile(performanceMetrics.edgerequestlatency, 0.95), totalRequests: performanceMetrics.edgerequestlatency.length }; // Métriques de cache const cacheMetrics = await this.metricsCollector.query({ metrics: ['edgecachehitrate'], timeRange: { start: startTime, end: endTime } }); report.summary.cache = { hitRate: this.calculateAverage(cacheMetrics.edgecachehitrate), efficiency: this.calculateCacheEfficiency(cacheMetrics) }; // Analyse des erreurs const errorLogs = await this.logAggregator.query({ level: 'error', timeRange: { start: startTime, end: endTime } }); report.summary.errors = { totalErrors: errorLogs.length, errorsByType: this.groupErrorsByType(errorLogs), errorRate: errorLogs.length / report.summary.performance.totalRequests }; // Top des pages/endpoints const requestLogs = await this.logAggregator.query({ event: 'edgerequestprocessed', timeRange: { start: startTime, end: endTime } }); report.details.topEndpoints = this.analyzeTopEndpoints(requestLogs); report.details.geographicDistribution = this.analyzeGeographicDistribution(requestLogs); report.details.deviceTypes = this.analyzeDeviceTypes(requestLogs); return report; } // Dashboard temps réel createRealTimeDashboard() { return { getCurrentMetrics: () => ({ requestsPerSecond: this.metricsCollector.getRate('edgerequestlatency'), averageLatency: this.metricsCollector.getAverage('edgerequestlatency'), cacheHitRate: this.metricsCollector.getCurrent('edgecachehitrate'), activeConnections: this.getActiveConnections(), systemLoad: this.getSystemLoad() }), getRecentAlerts: () => this.alertingSystem.getRecentAlerts(300000), // 5 minutes getHealthStatus: () => this.getLastHealthCheck(), getPerformanceTrends: (duration = 3600000) => { const endTime = Date.now(); const startTime = endTime - duration; return this.metricsCollector.getTrends({ metrics: ['edgerequestlatency', 'edgecachehit_rate'], timeRange: { start: startTime, end: endTime }, granularity: '1m' }); } }; } // Export de données pour analyse externe async exportMonitoringData(format = 'json', timeRange = '24h') { const endTime = Date.now(); const startTime = endTime - this.parseTimeRange(timeRange); const data = { exportedAt: new Date().toISOString(), region: this.region, nodeId: this.nodeId, timeRange: { start: startTime, end: endTime } }; // Export des métriques data.metrics = await this.metricsCollector.export({ timeRange: { start: startTime, end: endTime }, format: 'prometheus' }); // Export des traces data.traces = await this.traceCollector.export({ timeRange: { start: startTime, end: endTime }, format: 'jaeger' }); // Export des logs data.logs = await this.logAggregator.export({ timeRange: { start: startTime, end: endTime }, format: 'elk' }); // Formatage selon le format demandé switch (format) { case 'json': return JSON.stringify(data, null, 2); case 'csv': return this.convertToCSV(data); case 'prometheus': return this.convertToPrometheus(data); default: throw new Error(Unsupported export format: ${format}); } } }
Stratégies de déploiement et infrastructure Le déploiement d'applications edge nécessite une orc
hestration sophistiquée gérant la distribution géographique, les mises à jour progressives et la gestion des versions à travers multiples points de présence.
# Configuration Terraform pour infrastructure edge terraform { requiredproviders { cloudflare = { source = "cloudflare/cloudflare" version = "~> 4.0" } aws = { source = "hashicorp/aws" version = "~> 5.0" } } } # Zones Cloudflare pour distribution edge resource "cloudflarezone" "edgeapp" { accountid = var.cloudflareaccountid zone = var.domainname } # Workers edge avec routing intelligent resource "cloudflareworkerscript" "edgeapp" { accountid = var.cloudflareaccountid name = "edge-app-${var.environment}" content = file("${path.module}/dist/worker.js") # Configuration des variables d'environnement plaintextbinding { name = "ENVIRONMENT" text = var.environment } secrettextbinding { name = "APITOKEN" text = var.apitoken } kvnamespacebinding { name = "EDGEKV" namespaceid = cloudflareworkerskvnamespace.edgestorage.id } r2bucketbinding { name = "EDGESTORAGE" bucketname = cloudflarer2bucket.edgeassets.name } # Configuration des limites de ressources compatibilitydate = "2024-01-01" compatibilityflags = ["nodejscompat"] # Scripts de health check logpush = true } # Stockage KV distribué pour l'edge resource "cloudflareworkerskvnamespace" "edgestorage" { accountid = var.cloudflareaccountid title = "edge-storage-${var.environment}" } # Stockage R2 pour les assets statiques resource "cloudflarer2bucket" "edgeassets" { accountid = var.cloudflareaccountid name = "edge-assets-${var.environment}" location = "auto" } # Distribution des workers par région resource "cloudflareworkerroute" "edgeroutes" { foreach = var.edgeregions zoneid = cloudflarezone.edgeapp.id pattern = "${each.key}.${var.domainname}/*" scriptname = cloudflareworkerscript.edgeapp.name } # Configuration DNS avec géolocalisation resource "cloudflarerecord" "edgedns" { foreach = var.edgeregions zoneid = cloudflarezone.edgeapp.id name = each.key value = each.value.endpoint type = "CNAME" # Géolocalisation intelligente geolatencyrouting { country = each.value.country region = each.value.region } # Health checks automatiques healthcheck { enabled = true type = "HTTPS" path = "/health" interval = 60 retries = 3 timeout = 10 } } # Configuration des alertes et monitoring resource "cloudflarenotificationpolicy" "edgealerts" { name = "edge-app-alerts-${var.environment}" description = "Edge application monitoring alerts" enabled = true # Alertes de performance alerttype = "workersalert" filters = { "workername" = [cloudflareworkerscript.edgeapp.name] "status" = ["exceedslimit", "exceedsduration"] } # Destinations des alertes emailintegration { id = var.alertemailintegrationid } webhookintegration { id = var.slackwebhookintegrationid } } # Variables de configuration variable "edgeregions" { description = "Configuration des régions edge" type = map(object({ endpoint = string country = string region = string capacity = number })) default = { "us-east" = { endpoint = "us-east.edge.example.com" country = "US" region = "eastern" capacity = 1000 } "eu-west" = { endpoint = "eu-west.edge.example.com" country = "GB" region = "western" capacity = 800 } "ap-southeast" = { endpoint = "ap-southeast.edge.example.com" country = "SG" region = "southeastern" capacity = 600 } } } # Outputs pour intégration output "edgeendpoints" { description = "Endpoints des nœuds edge déployés" value = { for region, config in var.edgeregions : region => { endpoint = "${region}.${var.domainname}" workerid = cloudflareworkerscript.edgeapp.id health_check = "/health" } } }
# Pipeline de déploiement edge avec GitHub Actions name: Edge Application Deployment on: push: branches: [main, staging] pullrequest: branches: [main] env: NODEVERSION: '20' CLOUDFLAREACCOUNTID: ${{ secrets.CLOUDFLAREACCOUNTID }} CLOUDFLAREAPITOKEN: ${{ secrets.CLOUDFLAREAPITOKEN }} jobs: # Tests et validation test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODEVERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run unit tests run: npm run test - name: Run integration tests run: npm run test:integration - name: Validate edge worker syntax run: npm run validate:worker # Build pour edge deployment build: runs-on: ubuntu-latest needs: test outputs: build-hash: ${{ steps.build.outputs.hash }} steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODEVERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Build edge worker id: build run: | npm run build:edge echo "hash=$(sha256sum dist/worker.js | cut -d' ' -f1)" >> $GITHUBOUTPUT env: NODEENV: production BUILDVERSION: ${{ github.sha }} - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: edge-build-${{ steps.build.outputs.hash }} path: dist/ retention-days: 30 # Déploiement staging pour validation deploy-staging: runs-on: ubuntu-latest needs: [test, build] if: github.ref == 'refs/heads/staging' environment: staging steps: - uses: actions/checkout@v4 - name: Download build artifacts uses: actions/download-artifact@v3 with: name: edge-build-${{ needs.build.outputs.build-hash }} path: dist/ - name: Deploy to staging workers uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLAREAPITOKEN }} accountId: ${{ secrets.CLOUDFLAREACCOUNTID }} command: deploy --env staging workingDirectory: '.' - name: Wait for deployment propagation run: sleep 60 - name: Run smoke tests run: | npm run test:smoke -- --endpoint https://staging.example.com npm run test:performance -- --endpoint https://staging.example.com # Déploiement production avec stratégie canary deploy-production: runs-on: ubuntu-latest needs: [test, build] if: github.ref == 'refs/heads/main' environment: production steps: - uses: actions/checkout@v4 - name: Download build artifacts uses: actions/download-artifact@v3 with: name: edge-build-${{ needs.build.outputs.build-hash }} path: dist/ # Phase 1: Déploiement canary (5% du trafic) - name: Deploy canary (5% traffic) uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLAREAPITOKEN }} accountId: ${{ secrets.CLOUDFLAREACCOUNTID }} command: deploy --env production --compatibility-date 2024-01-01 workingDirectory: '.' env: CANARYPERCENTAGE: 5 - name: Monitor canary deployment run: | npm run monitor:canary -- --duration 300 --error-threshold 1% # Phase 2: Augmentation progressive du trafic canary - name: Increase canary to 25% if: success() uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLAREAPITOKEN }} accountId: ${{ secrets.CLOUDFLAREACCOUNTID }} command: route update --percentage 25 - name: Monitor extended canary run: | npm run monitor:canary -- --duration 600 --error-threshold 0.5% # Phase 3: Déploiement complet - name: Complete deployment (100% traffic) if: success() uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLAREAPITOKEN }} accountId: ${{ secrets.CLOUDFLAREACCOUNTID }} command: route update --percentage 100 - name: Final health check run: | npm run test:health -- --all-regions # Rollback en cas d'échec - name: Rollback on failure if: failure() uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLAREAPITOKEN }} accountId: ${{ secrets.CLOUDFLAREACCOUNTID }} command: rollback --env production - name: Update deployment status if: always() run: | status=${{ job.status }} curl -X POST ${{ secrets.DEPLOYMENTWEBHOOK }} \ -H "Content-Type: application/json" \ -d "{ \"status\": \"$status\", \"version\": \"${{ github.sha }}\", \"environment\": \"production\", \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" }" # Tests post-déploiement multi-régions post-deployment-tests: runs-on: ubuntu-latest needs: deploy-production if: always() && needs.deploy-production.result == 'success' strategy: matrix: region: [us-east, eu-west, ap-southeast] steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.NODEVERSION }} cache: 'npm' - name: Install dependencies run: npm ci - name: Test region ${{ matrix.region }} run: | npm run test:region -- --region ${{ matrix.region }} npm run test:latency -- --region ${{ matrix.region }} --max-latency 200 - name: Test cross-region sync run: | npm run test:sync -- --source ${{ matrix.region }} --targets us-east,eu-west,ap-southeast
Outils et écosystème recommandés L'écosystème Edge Computing s'enrichit rapidement d'outils sp�
�cialisés optimisant le développement, déploiement et monitoring des applications distribuées à la périphérie. - Cloudflare Workers : Plateforme serverless edge avec runtime JavaScript V8 - AWS Lambda@Edge : Fonctions Lambda distribuées sur CloudFront - Vercel Edge Functions : Runtime edge intégré à la plateforme Vercel - Deno Deploy : Plateforme edge native avec runtime Deno - Fastly Compute@Edge : Environnement d'exécution WebAssembly à la périphérie L'Edge Computing transforme fondamentalement l'architecture des applications web en rapprochant le traitement des utilisateurs finaux. Cette approche révolutionnaire optimise les performances, réduit la latence et améliore l'expérience utilisateur globale. L'investissement dans les technologies edge constitue un avantage concurrentiel majeur pour les applications modernes exigeantes en performance.