Ölçekte Bildirim Teslimatı Hatalarını Debug Etmek
Yüksek riskli üretim ortamlarında bildirim sistemi hatalarından edinilen gerçek dünya debugging teknikleri, izleme stratejileri ve dersler
Bildirim sistemleri en kötü zamanlarda sessizce başarısız olur: yoğun yükte, bir lansman ya da kampanyanın bağımlı olduğu anda. Hoş geldin e-postaları gelmez, push timeout olur, uygulama içi alert’ler gecikir, ama dashboard’lar yeşil görünmeye devam eder, çünkü her alt-sistem izole bakıldığında sorunsuzdur.
İzleme sinyallerini kullanıcıya olan uzaklığına göre sırala ve kontrol etmediğin her bağımlılığın önüne bir circuit breaker koy, çünkü CPU ve memory alertleri enkazı anlatır ama hangi alt-sistemin başlattığını söylemez.
Yoğun Trafik Altında Kademeli Hata#
Günde milyonlarca mesajı email, push ve uygulama içi kanallardan taşıyan bir hat aylarca sorunsuz çalışabilir, sonra trafik zirvesinden dakikalar sonra her kanalda birden çöker. Hata genelde tek bir bug değildir: her alt-sistemin savunma davranışının bir sonraki alt-sistem için yüke dönüştüğü bir zincirdir.
İlk Belirtiler#
İşe yarayan kısım alertlerin sırasıdır. Önce email teslimat oranı düşer, çünkü sağlayıcı düşen reputation skoru üzerinden throttle uygular. Ardından push timeout vermeye başlar. Sonra WebSocket bağlantıları birikir ve uygulama içi bildirimler dakikalarca gecikir. O penceredeki izleme görüntüsü şuna benzer:
// Alertlerin raporladığı
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 }
];
// Her sayının bir alt katmandaki karşılığı
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
Servisleri yeniden başlatmak ilk içgüdüdür ve çoğunlukla yanlış olandır: yeniden başlatma uçuştaki işi düşürür ve sistemi zaten dolduran retry storm’unu besler. Bunun yerine her kanalın önüne konan circuit breaker’lar hasarı sınırlar.
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
Hasar sınırlandıktan sonra soru şu: neden bütün kanallar aynı anda çöktü? Servisler arasında birleştirilen correlation ID’ler bunu yanıtlar:
// Trace'lerden yeniden kurulan kademeli hata sırası
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 > NOW() - INTERVAL '1 hour'
AND nd.status IN ('failed', 'timeout')
ORDER BY nd.created_at DESC
LIMIT 1000;
`;
Observability Hiyerarşileri#
Aşağıdaki hiyerarşi bu sıralamayı somut bir yapıya döker:
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)
};
}
}
Yük Altında Template Rendering#
Çok dilli, dinamik içerikli ve kullanıcıya göre kişiselleştirilen template’ler, hattın tamamını durduran ikinci yaygın kaynaktır, çünkü template motoru sunum katmanı gibi görünür ve uygulama koduna uygulanan sorgu bütçesini nadiren alır.
Template İçindeki Gizli N+1#
Template yazarı küçük görünen bir özellik ekler: hoş geldin e-postasında kullanıcının son aktiviteleri. Template zararsız görünür:
{{#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’ı, render döngüsünün içinde her kullanıcının her aktivitesi için bir veritabanı sorgusu çalıştırır.
Render Yolunu Profillemek#
Belirtiler başta belirsizdir: email teslimatları yavaşlar, sonra tamamen timeout olur. CPU yükselir ama memory sabit kalır, veritabanı da belirgin bir darboğaz göstermez. Render başına sorgu sayan bir profiler nedeni görünür kılar:
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ı#
N+1 deseni tespit edildikten sonra çözüm, sert limitlerle veri ön-yüklemenin birleşimidir:
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ı#
WebSocket fanout’u genelde canlı bildirimleri, doküman güncellemelerini ve presence bilgisini aynı bağlantı üzerinde taşır; bu yüzden istemci tarafındaki tek bir retry bug’ı üçünü birden vurur.
İstemci Neden Durmadan Yeniden Bağlanıyordu#
Şuna benzeyen istemci retry mantığı sağlam görünür:
// 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 şu: sunucular aşırı yüklendiğinde bağlantıları reddetmeye başlar, istemci de bu reddi close yerine error olarak yorumlayıp hiç backoff uygulamadan yeniden bağlanır.
Sunucu Tarafı Çözüm#
Sunucu, istemcilerin backoff uygulayacağına güvenemez; bu yüzden kabul kontrolü sunucu tarafına aittir: istemci başına bağlantı oranı, kullanıcı başına bağlantı sayısı ve yüke bağlı kabul kontrolü.
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
}));
}
}
Debugging Araç Seti#
Bir bildirim olayında dashboard “şu anda ne bozuk” sorusunu, trace ise “bu tek mesaja ne oldu” sorusunu yanıtlar.
Olaylar için Gerçek Zamanlı Dashboard#
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 < 0.95) {
recommendations.push('Email sağlayıcı durumunu ve reputation score\'unu kontrol et');
}
// Push bildirim problemleri
if (delivery.push.successRate < 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:
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()
);
}
}
İzleme Stratejisi#
Eşik alertleri hasar oluştuktan sonra çalar. Eklemeye değer alertler, hâlâ müdahale alanı varken çalanlardır.
Öngörücü Alerting#
Metriğin gittiği yöne alert kur ve trendin öngördüğü etkiyi de yaz:
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;
}
}
Bir Sonraki Olaydan Önce Eklenecek Ölçüm#
Bu eklemeler şimdi ucuz, olay sırasında zaten pahalıdır:
-
Her adımda correlation ID. Her bildirim eventi, teslimat denemesi ve harici servis çağrısı aynı ID’yi taşır; ID’si olmayan logları sonradan bir araya getirmek zordur.
-
Kullanıcı etkisi alertleri altyapı alertlerinin üstünde. Alert hiyerarşisinin tepesinde “kullanıcılar bildirim almıyor” sinyali durur; CPU, memory ve queue derinliği sinyalleri bunu açıklamak için vardır, tek başlarına page atmak için değil.
-
Sınırsız her yola bir limit. Render süresi, template başına sorgu, kullanıcı başına bağlantı ve queue derinliğinin hepsi bir limite ihtiyaç duyar; böylece kademeli hata başladığı bileşende kalır.
Bu sıralama, teslimat yolunun uçtan uca sahibi olduğun durumlarda geçerlidir. Teslimat bir sağlayıcıya devredilmişse hiyerarşinin 3. ve 4. seviyeleri o sağlayıcının event webhook’larının arkasına geçer: trace’i kendi loglarının yerine onların teslimat eventlerinden kurman gerekir ve circuit breaker kendi queue derinliğini korur.
Serinin son bölümü analitik ve performans ayarını ele alıyor: bildirim stratejilerinin A/B testi, teslimat metriklerini hareket ettiren optimizasyon desenleri ve regresyonları kullanıcılar bildirmeden yakalayan performans izleme.
Kaynaklar#
- Amazon CloudWatch nedir? - AWS Belgeleri (yeni sekmede açılır) - Bildirim sistemi sağlık izleme için metrik toplama, alarm oluşturma ve dashboard’lar için CloudWatch’a genel bakış
- Amazon CloudWatch alarmlarını kullanma - AWS Belgeleri (yeni sekmede açılır) - Bildirim teslimat hatalarını ve kuyruk derinliği anormalliklerini tespit etmek için eşik tabanlı alarmların yapılandırılması
- AWS X-Ray nedir? - AWS Belgeleri (yeni sekmede açılır) - Bildirim pipeline’larında Lambda, SQS ve SNS genelinde uçtan uca istek izleme için dağıtık izleme servisi
- Amazon SNS mobil push bildirimleri için en iyi uygulamalar - AWS Belgeleri (yeni sekmede açılır) - Push teslimat hatalarını hata ayıklamak için SNS teslimat durumu günlüğü, endpoint yönetimi ve olay bildirimleri
- Observability primer - opentelemetry.io (yeni sekmede açılır) - Production hata ayıklamada correlation ID’ler, dağıtık trace’ler ve yapısal günlük kaydı için kavramsal temel
Ö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.
Bu serideki tüm yazılar
İlgili yazılar
Yeşil dashboard'ların gizlediği Fargate arızaları: ENI kotasının tükenmesi, subnet route sorunları, memory leak'ler ve her birini bulan kontroller.
aws · fargate · debugging +4
Yeşil ışıklı dashboard'lardan, dağıtık izleme ile sistem davranışını ve iş etkisini anlatan observability sistemlerine geçişin yolu.
observability · monitoring · opentelemetry +3
Suçlu aramak yerine sistemi düzelten bir suçsuz postmortem modeli, kopyalanabilir bir şablon ve bireysel sorumluluğun hâlâ geçerli olduğu sınır.
engineering-culture · incident-response · psychological-safety +4
RFC tasarımlarının production'la karşılaşınca nerede saptığı, bildirim sistemi örneği üzerinden: faydalı uyarlamayı mimari sürüklenmeden ayırmanın yolu.
rfc · production · debugging +4
AWS Lambda'yı production için enstrümente edin: CloudWatch custom metrics, X-Ray tracing, structured logging ve business etkisini izleyen alert'ler.
lambda · serverless · monitoring +2