Üretim Savaş Hikayeleri: Ölçekte Bildirim Teslimatını Debug Etmek

Yüksek riskli üretim ortamlarında bildirim sistemi hatalarından edinilen gerçek dünya debugging teknikleri, izleme stratejileri ve dersler

Şu sahneyi hayal et: Yılın en büyük ürün lansmanının tam ortasında. Pazarlama bu özelliği aylardır iter, CEO metrik dashboard'unu izliyor ve aniden bildirim sistemin sessizliğe gömülüyor. Hoşgeldin emailleri yok, push bildirimler yok, uygulama içi alertler yok. Sadece... hiçlik.

Bu hipotetik bir senaryo değil. Bu kâbusun versiyonlarını üç farklı şirkette yaşadım, her seferinde bildirim altyapın ateş altındayken gerçekten neyin önemli olduğu hakkında yeni dersler öğrenerek. Blog yazılarında zarif görünen debugging teknikleri, telefonun giderek daha çılgın Slack mesajlarıyla vızıldadığı sırada 500'ler denizine bakarken çoğu zaman çöküyor.

Her şey alev alev yanarken bildirim sistemlerini nasıl debug edeceğimi öğreten üretim savaş hikayelerini ve gerçekten ihtiyaç duyduğunda işe yarayan izleme stratejilerini paylaşayım.

Black Friday Kademeli Hata#

Durum: E-ticaret şirketi, Black Friday sabahı, normal trafiğin 10 katını bekliyor. Bildirim sistemi aylardır sorunsuz çalışıyor, günlük milyonlarca bildirimi email, push ve uygulama içi kanallar üzerinden yönetiyor.

Neler Ters Gitti: Sabah 6:15'te, tam Doğu Kıyısı alışverişçileri uyanırken, bildirim sistemimiz tam çözüme dört saat süren birbiriyle bağlantılı problemlerden oluşan bir kademeli hata ile başarısız olmaya başladı.

İlk Belirtiler#

İlk alert email sağlayıcımızdan geldi: teslimat oranları beş dakikada %99.2'den %60'a düşüyor. Sonra push bildirimler timeout olmaya başladı. Son olarak, WebSocket bağlantıları aşırı yüklenmeye başladı, uygulama içi bildirimlerin birkaç dakika gecikmesine neden oldu.

İlk kritik dakikalardaki izleme şu şekildeydi:

TypeScript
// Alert'lerimizin bize söylediği
const alertTimeline = [
  { time: '06:15', service: 'email', metric: 'delivery_rate', value: 60, threshold: 95 },
  { time: '06:16', service: 'push', metric: 'timeout_rate', value: 25, threshold: 5 },
  { time: '06:18', service: 'websocket', metric: 'connection_count', value: 85000, threshold: 50000 },
  { time: '06:20', service: 'database', metric: 'connection_pool', value: 95, threshold: 80 },
  { time: '06:22', service: 'redis', metric: 'memory_usage', value: 92, threshold: 85 }
];

// Ama kaputun altında gerçekte olan şu
const realityCheck = {
  emailProvider: 'Reputation score düşüşü nedeniyle rate limiting yapıyor',
  pushService: 'Apple APNS template bugından gelen bozuk payload'ları reddediyor',
  websockets: 'Başarısız push kayıtlarını yeniden deneyen mobil app'ten connection storm',
  database: 'Eşzamanlı bildirim tercih güncellemelerinden deadlock'lar',
  redis: 'Sınırsız connection metadata depolamadan bellek tükenmesi'
};

Debugging Süreci#

Adım 1: Kanamayı Durdur

İlk içgüdü servisleri yeniden başlatmaktı, ama deneyim bana yeniden başlatmaların retry storm'larını güçlendirerek kademeli hataları genellikle daha kötü hale getirdiğini öğretmişti. Bunun yerine, acil circuit breaker'lar uyguladık:

TypeScript
class EmergencyCircuitBreaker {
  private isOpen = false;
  private openedAt?: Date;
  private failureCount = 0;
  private readonly failureThreshold = 10;
  private readonly recoveryTimeoutMs = 30000;

  async executeWithBreaker<T>(
    operation: () => Promise<T>,
    fallback?: () => Promise<T>
  ): Promise<T> {
    if (this.isOpen) {
      if (this.shouldAttemptReset()) {
        console.log('Circuit breaker reset deneniyor');
        this.isOpen = false;
        this.failureCount = 0;
      } else {
        if (fallback) {
          return await fallback();
        }
        throw new Error('Circuit breaker açık');
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      if (fallback && this.isOpen) {
        return await fallback();
      }
      throw error;
    }
  }

  private onSuccess(): void {
    this.failureCount = 0;
  }

  private onFailure(): void {
    this.failureCount++;
    if (this.failureCount >= this.failureThreshold) {
      this.isOpen = true;
      this.openedAt = new Date();
      console.warn(`Circuit breaker ${this.failureCount} hatadan sonra açıldı`);
    }
  }
}

// Circuit breaker'lı acil bildirim servisi
class EmergencyNotificationService {
  private emailBreaker = new EmergencyCircuitBreaker();
  private pushBreaker = new EmergencyCircuitBreaker();
  private websocketBreaker = new EmergencyCircuitBreaker();

  async processNotification(event: NotificationEvent): Promise<void> {
    // Circuit breaker'lar ve fallback'lerle birincil kanalları dene
    await Promise.allSettled([
      this.emailBreaker.executeWithBreaker(
        () => this.sendEmail(event),
        () => this.queueForLaterDelivery(event, 'email')
      ),
      this.pushBreaker.executeWithBreaker(
        () => this.sendPush(event),
        () => this.sendWebSocketFallback(event)
      ),
      this.websocketBreaker.executeWithBreaker(
        () => this.sendWebSocket(event),
        () => this.storeForPolling(event)
      )
    ]);
  }
}

Adım 2: Kök Nedeni İzle

Ani hasarı kontrol altına aldıktan sonra, neden her şeyin aynı anda başarısız olduğunu anlamamız gerekiyordu. Ana içgörü, farklı servislerdeki correlation ID'leri analiz etmekten geldi:

TypeScript
// İzlememiz kademeli sırayı ortaya çıkardı
const traceAnalysis = {
  '06:14:45': 'Buggy push token kaydıyla yeni app versiyonu deploy edildi',
  '06:15:00': 'Bozuk push payload'ları APNS'in reddetmesine ve bağlantıları kapatmasına neden oldu',
  '06:15:30': 'Mobil app push kaydını yeniden deniyor, WebSocket connection storm yaratıyor',
  '06:16:00': 'Tercih güncelleme sorgularıyla veritabanı connection pool tükendi',
  '06:16:30': 'Email servisi yedek sağlayıcıya geçiyor, rate limitleri tetikliyor',
  '06:17:00': 'Redis memory öksüz connection metadata ile doluyor',
  '06:17:30': 'Sistem tam kademeli hata moduna giriyor'
};

// Deseni ortaya çıkaran debugging sorgusu
const debugQuery = `
  SELECT 
    ne.correlation_id,
    ne.notification_type,
    nd.channel,
    nd.status,
    nd.error_message,
    nd.created_at
  FROM notification_events ne
  JOIN notification_deliveries nd ON ne.id = nd.event_id
  WHERE ne.created_at > '2024-11-29 06:14:00'
    AND nd.status IN ('failed', 'timeout')
  ORDER BY nd.created_at DESC
  LIMIT 1000;
`;

Gerçek Ders: Observability Hiyerarşileri#

Geleneksel izleme yaklaşımı tüm hataları eşit sayıyor, ama kademeli hatalar bana hiyerarşik observability'ye ihtiyacın olduğunu öğretti:

TypeScript
interface ObservabilityHierarchy {
  // Seviye 1: Kullanıcı Etkisi (Müşterilerin gördüğü)
  userImpact: {
    notificationsReceived: number;
    averageDeliveryTime: number;
    userComplaints: number;
  };
  
  // Seviye 2: Servis Sağlığı (Sistemlerimizin performansı)  
  serviceHealth: {
    deliveryRates: Record<NotificationChannel, number>;
    errorRates: Record<string, number>;
    responseTimes: Record<string, number>;
  };
  
  // Seviye 3: Altyapı (Kaputun altında olan)
  infrastructure: {
    databaseConnections: number;
    redisMemory: number;
    queueDepths: Record<string, number>;
  };
  
  // Seviye 4: Harici Bağımlılıklar (Kontrol etmediğimiz şeyler)
  externalDeps: {
    emailProviderStatus: string;
    pushProviderLatency: number;
    cloudServiceHealth: string;
  };
}

class HierarchicalMonitoring {
  async assessSystemHealth(): Promise<SystemHealthSnapshot> {
    // Kullanıcı etkisiyle başla - bu gerçekten önemli olan
    const userImpact = await this.getUserImpactMetrics();
    
    if (userImpact.isHealthy) {
      return { status: 'healthy', details: userImpact };
    }
    
    // Kullanıcı etkisi kötüyse, hiyerarşi boyunca derine in
    const serviceHealth = await this.getServiceHealthMetrics();
    const infrastructure = await this.getInfrastructureMetrics(); 
    const externalDeps = await this.getExternalDepMetrics();
    
    // Hiyerarşi seviyeleri boyunca sorunları ilişkilendir
    const rootCause = this.correlateIssues({
      userImpact,
      serviceHealth, 
      infrastructure,
      externalDeps
    });
    
    return {
      status: 'degraded',
      rootCause,
      remediationSteps: this.generateRemediationPlan(rootCause)
    };
  }
}

Template Rendering Saatli Bombası#

Durum: 15 ülkede 50.000+ kullanıcılı SaaS platformu. Çok dilli destek, dinamik içerik ve kullanıcı kişiselleştirmesi ile sofistike bir template sistemi implement etmiştik.

Neler Ters Gitti: İş saatleri sırasında masum görünen bir template güncellemesi tüm bildirim sistemini 45 dakika boyunca çökertti.

Sinsi Performans Katili#

Sorun, template tasarımcısının basit görünen bir özellik eklemesiyle başladı: hoşgeldin emaillerinde kullanıcının son aktivitelerini göstermek. Template yeterince masum görünüyordu:

handlebars
{{#each user.recentActivities}}
  <div class="activity-item">
    <span>{{formatDate this.createdAt}}</span>
    <span>{{this.description}}</span>
    {{#if this.projectName}}
      <span>{{getProjectDetails this.projectId}} içinde</span>
    {{/if}}
  </div>
{{/each}}

getProjectDetails helper'ı bir veritabanı sorgusu yapıyordu. Her aktivite için. Her kullanıcı için. Ne yanlış gidebilirdi ki?

Performans Debugging Yolculuğu#

Belirtiler başlangıçta belirsizdi: email teslimatları yavaşlıyor, sonra tamamen timeout oluyor. CPU kullanımı yükseliyor ama memory iyi görünüyor. Veritabanı belirgin darboğazlar göstermiyordu.

Sonunda sorunu ortaya çıkaran debugging aracı şu:

TypeScript
class TemplatePerformanceProfiler {
  private renderTimes: Map<string, number[]> = new Map();
  private queryCount: Map<string, number> = new Map();
  private activeRenders: Map<string, Date> = new Map();

  async profileRender(
    templateId: string,
    templateContent: string,
    data: any
  ): Promise<ProfiledRenderResult> {
    const renderId = `${templateId}-${Date.now()}`;
    this.activeRenders.set(renderId, new Date());
    
    // Template başına sorguları saymak için veritabanı çağrılarını sarala
    const originalQuery = this.db.query;
    let queryCount = 0;
    
    this.db.query = (...args) => {
      queryCount++;
      return originalQuery.apply(this.db, args);
    };
    
    try {
      const startTime = Date.now();
      const result = await this.templateEngine.render(templateContent, data);
      const renderTime = Date.now() - startTime;
      
      // Performans metriklerini sakla
      if (!this.renderTimes.has(templateId)) {
        this.renderTimes.set(templateId, []);
      }
      this.renderTimes.get(templateId)!.push(renderTime);
      this.queryCount.set(renderId, queryCount);
      
      // Şüpheli desenlerde alert
      if (queryCount > 10) {
        console.warn(`Template ${templateId} render sırasında ${queryCount} DB sorgusu yaptı`);
      }
      
      if (renderTime > 1000) {
        console.warn(`Template ${templateId} render için ${renderTime}ms sürdü`);
      }
      
      return {
        content: result,
        renderTime,
        queryCount,
        metrics: this.calculateMetrics(templateId)
      };
      
    } finally {
      // Orijinal query methodunu restore et
      this.db.query = originalQuery;
      this.activeRenders.delete(renderId);
    }
  }

  private calculateMetrics(templateId: string): TemplateMetrics {
    const times = this.renderTimes.get(templateId) || [];
    const recentTimes = times.slice(-100); // Son 100 render
    
    return {
      averageRenderTime: recentTimes.reduce((a, b) => a + b, 0) / recentTimes.length,
      p95RenderTime: this.percentile(recentTimes, 0.95),
      p99RenderTime: this.percentile(recentTimes, 0.99),
      renderCount: recentTimes.length,
      suspiciousPatterns: this.detectPatterns(recentTimes)
    };
  }

  // Performans desenlerine dayalı öneriler üret
  generateOptimizationSuggestions(templateId: string): string[] {
    const metrics = this.calculateMetrics(templateId);
    const suggestions: string[] = [];
    
    if (metrics.averageRenderTime > 500) {
      suggestions.push('Sık erişilen veriyi cache\'lemeyi düşün');
    }
    
    if (metrics.p99RenderTime > 2000) {
      suggestions.push('Template yüksek tail latency\'ye sahip - yavaş yolları araştır');
    }
    
    const avgQueries = Array.from(this.queryCount.values())
      .reduce((a, b) => a + b, 0) / this.queryCount.size;
    
    if (avgQueries > 5) {
      suggestions.push('Çok fazla veritabanı sorgusu - veri ön-yüklemeyi düşün');
    }
    
    return suggestions;
  }
}

Çözüm: Template Performans Korumaları#

Template'lerdeki N+1 sorgu problemini tespit ettikten sonra, çözüm performans limitleri ve veri ön-yükleme kombinasyonuydu:

TypeScript
class SafeTemplateRenderer {
  private readonly MAX_RENDER_TIME = 2000; // 2 saniye
  private readonly MAX_DB_QUERIES = 10;
  private readonly CACHE_TTL = 300; // 5 dakika

  async renderWithGuardrails(
    templateId: string,
    userId: string,
    data: any
  ): Promise<string> {
    // N+1 sorgularını önlemek için yaygın gerekli veriyi ön-yükle
    const enhancedData = await this.preloadTemplateData(userId, data);
    
    // Render kısıtlamaları kur
    const renderPromise = this.templateEngine.render(
      templateId, 
      enhancedData,
      {
        timeout: this.MAX_RENDER_TIME,
        maxQueries: this.MAX_DB_QUERIES,
        enableCache: true
      }
    );
    
    try {
      return await Promise.race([
        renderPromise,
        this.createTimeoutPromise(this.MAX_RENDER_TIME)
      ]);
    } catch (error) {
      if (error instanceof TimeoutError) {
        // Cache'lenmiş versiyona veya basit template'e geri dön
        return await this.renderFallbackTemplate(templateId, userId, data);
      }
      throw error;
    }
  }

  private async preloadTemplateData(userId: string, data: any): Promise<any> {
    // Template'in hangi veriye ihtiyacı olduğunu belirlemek için analiz et
    const requiredData = this.analyzeTemplateDataNeeds(data.templateContent);
    
    // Gerekli tüm veriyi tek sorgularda toplu yükle
    const preloadedData = await Promise.all([
      requiredData.needsProjects ? this.loadUserProjects(userId) : null,
      requiredData.needsActivities ? this.loadUserActivities(userId, 10) : null,
      requiredData.needsTeamInfo ? this.loadUserTeamInfo(userId) : null
    ]);
    
    return {
      ...data,
      projects: preloadedData[0],
      recentActivities: preloadedData[1], 
      teamInfo: preloadedData[2]
    };
  }

  private async renderFallbackTemplate(
    templateId: string, 
    userId: string, 
    data: any
  ): Promise<string> {
    // Karmaşık veri gerektirmeyen basitleştirilmiş template versiyonu kullan
    const fallbackTemplate = await this.getFallbackTemplate(templateId);
    return await this.templateEngine.render(fallbackTemplate, {
      user: data.user,
      basicData: this.extractBasicData(data)
    });
  }
}

WebSocket Bağlantı Fırtınası#

Durum: 20.000 eşzamanlı kullanıcılı gerçek zamanlı işbirliği platformu. WebSocket bağlantıları canlı bildirimleri, doküman güncellemelerini ve presence göstergelerini yönetiyordu.

Neler Ters Gitti: Mobil app güncellemesi, üst kullanım saatleri sırasında WebSocket altyapımızı çökertten üstel backoff hatası yaratan bir bağlantı retry bug'ı tanıttı.

Bağlantı Ölüm Spirali#

Mobil takım sağlam bir retry mekanizması implement ettiğini düşünmüştü:

JavaScript
// Mobil app'in "geliştirilmiş" retry mantığı - bunu yapma
class NotificationConnectionManager {
  connect() {
    this.ws = new WebSocket(this.endpoint);
    
    this.ws.onclose = () => {
      // Üstel backoff... öyle sandılar
      this.retryDelay = Math.min(this.retryDelay * 2, 30000);
      setTimeout(() => this.connect(), this.retryDelay);
    };
    
    this.ws.onerror = () => {
      // Hata durumunda hemen yeniden dene - problem buydu
      this.connect();
    };
  }
}

Sorun: WebSocket serverlarımız aşırı yüklendiğinde bağlantıları reddetmeye başladılar. Mobil app'ler bunu hata olarak yorumladı (close değil) ve herhangi bir backoff olmadan hemen yeniden bağlandı, üstel bir fırtına yaratarak.

Sunucu Tarafı Savunma#

Kendini savunmayı öğrenen WebSocket bağlantı yöneticisi şu:

TypeScript
class DefensiveWebSocketServer {
  private connectionCounts: Map<string, number> = new Map();
  private rateLimiter: Map<string, Date[]> = new Map();
  private readonly MAX_CONNECTIONS_PER_USER = 5;
  private readonly RATE_LIMIT_WINDOW = 60000; // 1 dakika
  private readonly RATE_LIMIT_MAX = 10; // Dakikada 10 bağlantı

  async handleConnection(socket: WebSocket, request: IncomingMessage): Promise<void> {
    const clientId = this.getClientIdentifier(request);
    const userId = await this.authenticateConnection(request);
    
    // Rate limiting kontrolü
    if (!this.checkRateLimit(clientId)) {
      socket.close(1008, 'Rate limit aşıldı');
      this.logSecurityEvent('rate_limit_exceeded', clientId);
      return;
    }
    
    // Kullanıcı başına bağlantı sayısı kontrolü
    const userConnections = this.connectionCounts.get(userId) || 0;
    if (userConnections >= this.MAX_CONNECTIONS_PER_USER) {
      socket.close(1008, 'Çok fazla bağlantı');
      this.logSecurityEvent('connection_limit_exceeded', userId);
      return;
    }
    
    // Sunucu yük koruması
    const serverLoad = await this.getCurrentServerLoad();
    if (serverLoad > 0.9) {
      // Yük altındayken sadece yüksek öncelikli bağlantıları kabul et
      if (!this.isHighPriorityUser(userId)) {
        socket.close(1013, 'Sunucu aşırı yüklü - lütfen sonra tekrar deneyin');
        return;
      }
    }
    
    this.setupConnection(socket, userId, clientId);
  }

  private checkRateLimit(clientId: string): boolean {
    const now = new Date();
    const windowStart = new Date(now.getTime() - this.RATE_LIMIT_WINDOW);
    
    if (!this.rateLimiter.has(clientId)) {
      this.rateLimiter.set(clientId, []);
    }
    
    const connections = this.rateLimiter.get(clientId)!;
    
    // Eski bağlantı denemelerini kaldır
    const recentConnections = connections.filter(date => date > windowStart);
    this.rateLimiter.set(clientId, recentConnections);
    
    // Rate limit altında mı kontrol et
    if (recentConnections.length >= this.RATE_LIMIT_MAX) {
      return false;
    }
    
    // Bu bağlantı denemesini kaydet
    recentConnections.push(now);
    return true;
  }

  private async getCurrentServerLoad(): Promise<number> {
    const metrics = await Promise.all([
      this.getCPUUsage(),
      this.getMemoryUsage(),
      this.getConnectionCount(),
      this.getEventQueueDepth()
    ]);
    
    // Farklı yük göstergelerinin ağırlıklı ortalaması
    return (
      metrics[0] * 0.3 + // CPU
      metrics[1] * 0.2 + // Memory  
      metrics[2] * 0.3 + // Connections
      metrics[3] * 0.2   // Queue depth
    );
  }

  // Yük altında zarifçe bozulma
  private async handleConnectionUnderLoad(
    socket: WebSocket, 
    userId: string
  ): Promise<void> {
    // Kritik olmayan bildirimler için güncelleme sıklığını azalt
    const updateInterval = this.getAdaptiveUpdateInterval();
    
    // Kritik bildirim türlerini önceliklendir
    const allowedNotificationTypes = this.getCriticalNotificationTypes();
    
    socket.send(JSON.stringify({
      type: 'connection_degraded',
      message: 'Yüksek yük nedeniyle azaltılmış servis',
      updateInterval,
      allowedTypes: allowedNotificationTypes
    }));
  }
}

Gerçekten İşe Yarayan Debugging Araç Seti#

Onlarca bildirim sistemi olayını debug ettikten sonra, tutarlı olarak değer sağlayan araçlar ve teknikler şunlar:

Olaylar için Gerçek Zamanlı Dashboard#

TypeScript
class IncidentDashboard {
  async getCurrentSystemState(): Promise<SystemSnapshot> {
    const timestamp = new Date();
    
    // Hız için metrikleri paralel topla
    const [
      deliveryMetrics,
      errorMetrics, 
      performanceMetrics,
      externalServiceStatus
    ] = await Promise.all([
      this.getDeliveryMetrics(),
      this.getErrorMetrics(),
      this.getPerformanceMetrics(),
      this.checkExternalServices()
    ]);
    
    return {
      timestamp,
      overall: this.calculateOverallHealth(deliveryMetrics, errorMetrics),
      deliveryMetrics: {
        email: deliveryMetrics.email,
        push: deliveryMetrics.push,
        websocket: deliveryMetrics.websocket,
        sms: deliveryMetrics.sms
      },
      errors: {
        byChannel: errorMetrics.byChannel,
        byType: errorMetrics.byType,
        trending: errorMetrics.trending
      },
      performance: {
        avgDeliveryTime: performanceMetrics.avgDeliveryTime,
        p95DeliveryTime: performanceMetrics.p95DeliveryTime,
        queueDepths: performanceMetrics.queueDepths
      },
      externalServices: externalServiceStatus,
      recommendations: this.generateRecommendations(deliveryMetrics, errorMetrics)
    };
  }

  private generateRecommendations(
    delivery: any, 
    errors: any
  ): string[] {
    const recommendations: string[] = [];
    
    // Email teslimat sorunları
    if (delivery.email.successRate &lt;0.95) {
      recommendations.push('Email sağlayıcı durumunu ve reputation score\'unu kontrol et');
    }
    
    // Push bildirim problemleri  
    if (delivery.push.successRate &lt;0.9) {
      recommendations.push('Push sertifikalarını ve payload formatını doğrula');
    }
    
    // Yüksek hata oranları
    if (errors.overall.rate > 0.05) {
      recommendations.push('En yaygın hata desenlerini araştır');
    }
    
    return recommendations;
  }
}

Correlation ID İzleme#

Bildirim sistemleri için en değerli debugging aracı kapsamlı correlation ID izlemedir:

TypeScript
class NotificationTracer {
  async traceNotificationJourney(correlationId: string): Promise<NotificationTrace> {
    // Bir bildirimin sistem boyunca tam yolculuğunu al
    const events = await this.db.query(`
      SELECT 
        ne.id as event_id,
        ne.notification_type,
        ne.created_at,
        ne.data,
        nd.channel,
        nd.status,
        nd.attempt_count,
        nd.error_message,
        nd.sent_at,
        nd.delivered_at
      FROM notification_events ne
      LEFT JOIN notification_deliveries nd ON ne.id = nd.event_id  
      WHERE ne.correlation_id = $1
      ORDER BY ne.created_at, nd.created_at
    `, [correlationId]);
    
    // Ayrıca harici servislerden logları al
    const externalLogs = await Promise.all([
      this.getEmailProviderLogs(correlationId),
      this.getPushProviderLogs(correlationId),
      this.getWebSocketLogs(correlationId)
    ]);
    
    return {
      correlationId,
      timeline: this.buildTimeline(events, externalLogs),
      status: this.determineOverallStatus(events),
      failurePoints: this.identifyFailures(events, externalLogs),
      recommendations: this.generateTraceRecommendations(events)
    };
  }

  private buildTimeline(events: any[], externalLogs: any[]): TimelineEvent[] {
    const allEvents = [
      ...events.map(e => ({ 
        timestamp: e.created_at, 
        type: 'internal', 
        details: e 
      })),
      ...externalLogs.flat().map(e => ({ 
        timestamp: e.timestamp, 
        type: 'external', 
        details: e 
      }))
    ];
    
    return allEvents.sort((a, b) => 
      new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
    );
  }
}

Bizi Kurtaran İzleme Stratejisi#

Birden fazla üretim olayından sonra, gerçekten problemleri önleyen izleme yaklaşımı şu:

Öngörücü Alerting#

Mevcut problemlerde alert yerine, gelecekteki problemleri öngören trendlerde alert:

TypeScript
class PredictiveAlerting {
  async checkPredictiveMetrics(): Promise<Alert[]> {
    const alerts: Alert[] = [];
    
    // Teslimat oranı trendlerini kontrol et (sadece mevcut oran değil)
    const deliveryTrend = await this.calculateDeliveryTrend('1h');
    if (deliveryTrend.slope < -0.1) { // Saatte %10+ düşüyor
      alerts.push({
        level: 'warning',
        message: 'Teslimat oranı aşağı doğru trend gösteriyor',
        details: `Oran saatte ${deliveryTrend.slope * 100}% düşüyor`,
        predictedImpact: 'Trend devam ederse ~2 saatte sistem hatası'
      });
    }
    
    // Queue derinliği büyümesini kontrol et
    const queueGrowth = await this.calculateQueueGrowthRate('30m');
    if (queueGrowth > 1000) { // 30 dakikada 1000+ item büyüyor
      alerts.push({
        level: 'critical',
        message: 'Bildirim queue\'su sürdürülemez şekilde büyüyor',
        details: `Queue 30 dakikada ${queueGrowth} item büyüyor`,
        predictedImpact: '~45 dakikada queue overflow'
      });
    }
    
    // Yeni hata deseni tespitini kontrol et
    const errorPatterns = await this.detectEmergingErrorPatterns();
    for (const pattern of errorPatterns) {
      if (pattern.confidence > 0.8) {
        alerts.push({
          level: 'warning',
          message: `Yeni hata deseni tespit edildi: ${pattern.type}`,
          details: pattern.description,
          predictedImpact: `Potansiyel sistem etkisi: ${pattern.impact}`
        });
      }
    }
    
    return alerts;
  }
}

Debugging Savaş Alanından Dersler#

Yüzlerce saat bildirim sistemi hatalarını debug ettikten sonra, tutarlı olarak önemli olan prensipler:

  1. Correlation ID'ler opsiyonel değil: Her bildirim eventi, teslimat denemesi ve harici servis çağrısının bir correlation ID'si olması gerekir. Bu tek karar seni diğer herhangi bir şeyden daha fazla debugging zamanından kurtaracak.

  2. Kullanıcı etkisini izle, sistem metriklerini değil: CPU kullanımına dayalı alertler "kullanıcılar bildirim almıyor" alertlerinden daha az faydalı. Kullanıcı etkisiyle başla ve geriye doğru çalış.

  3. İlk günden circuit breaker'lar inşa et: İlk kademeli hatana kadar circuit breaker implement etmeyi bekleme. Olay sırasında eklemek çok daha zor.

  4. Harici bağımlılıklar başarısız olacak: Email sağlayıcılarının çökmesi, push bildirim servislerinin yavaş olması ve webhook'ların timeout olması için planla. Sistemin zarifçe bozulması gerekir.

  5. Performans limitleri kademeleri önler: Template rendering limitleri, bağlantı rate limiting ve queue derinliği üst limitleri sadece güzel-olsa-iyi özellikler değil - küçük problemlerin büyük problemler haline gelmesini önlerler.

  6. Her şeyi izle: Correlation ID'ler olmayan loglar arkeoloji. Correlation ID'ler olan loglar debugging süper güçleri.

Bu serinin son bölümünde, problemler oluşmadan bildirim sistemini ayarlamana yardımcı olan analitik ve performans optimizasyon tekniklerini keşfedeceğiz. Bildirim stratejilerini A/B testleme, gerçekten metrikleri hareket ettiren optimizasyon desenleri ve kullanıcılar yapmadan sorunları yakalayan performans izlemeyi ele alacağız.

Burada ele aldığımız debugging teknikleri acil müdahale kitin. Ama en iyi olaylar, sistemi onları önleyecek şekilde optimize ettiğin için hiç gerçekleşmeyenler.

Ölçeklenebilir Kullanıcı Bildirim Sistemi Geliştirme

Kurumsal seviye bildirim sistemlerinin tasarımı, implementasyonu ve üretim zorluklarını kapsayan kapsamlı 4-parça serisi. Mimari ve veritabanı tasarımından gerçek zamanlı teslimat, ölçekte debugging ve performans optimizasyonuna kadar.

İlerleme3/4 yazı tamamlandı
Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts