Webhook Dağıtım Sistemi Oluşturma: 2026'da Güvenilir Postback'ler

Kuyruklar, titreşimli yeniden denemeler, devre kesiciler, idempotansiyellik, imzalar ve uç nokta başına gecikme telemetrisi ile dayanıklı webhook ve affiliate-postback teslimatı için pratik bir 2026 kılavuzu.
Webhook Dağıtım Sistemi - Webhook Dağıtım Sistemi Oluşturma: 2026'da Güvenilir Postback'ler

Son Güncelleme: Haziran 24, 2026 Sezar Fikson

Doğrudan cevap: Güvenilir bir webhook veya ortaklık geri bildirim teslimat hizmeti, dayanıklı bir kuyruğa, gecikmeli sınırlı yeniden denemelere, uç nokta başına devre kesme özelliğine, yinelenen kayıt korumasına, imza doğrulamasına ve teslimatın başarısız olmadan önce yavaşlayıp yavaşlamadığını gösteren telemetriye ihtiyaç duyar.

Bu uygulama, yürütme modelini kasıtlı olarak küçük tutar: Python çalışanları ve kuyruk, teslimat geçmişi ve uç nokta sağlığı için PostgreSQL. B2B SaaS, iGaming ortaklık operasyonları ve tek bir HTTP zaman aşımını kayıp bir dönüşüm olarak değerlendirmeden giden olayları iletmesi gereken herhangi bir ürün için pratik bir başlangıç ​​noktasıdır.

Teslimat sözleşmesinin garanti etmesi gerekenler:

Control Neden önemli operasyonel kontrol
Kalıcı etkinlik kaydı Olaylar, çalışanların yeniden başlatılmasından sonra da devam eder. Kabul edilen her teslimatın sabit bir kimliği ve durumu vardır.
İmzalı talep Alıcılar göndereni doğrulayabilir ve gövde üzerinde yapılan tahrifatları tespit edebilir. Zaman damgalı imza kullanın ve süresi geçmiş istekleri reddedin.
Kopya koruması Aksi takdirde yeniden denemeler, yinelenen dönüştürmelere veya güncellemelere neden olabilir. Bir olay kimliği gönderin ve alıcıyı idempotent hale getirin.
Sınırlı yeniden denemeler Geçici arızalar, bozulmuş bir uç noktayı aşırı yüklemeden kurtarılabilir. Titremeyi azaltın; belgelenmiş deneme sınırından sonra durun.
Uç nokta telemetrisi Sadece kuyruk derinliği bile yavaş veya başarısız olan iş ortaklarını gizler. Her bir uç nokta için başarı oranını, en eski beklemede olan olayı ve gecikme yüzdeliklerini takip edin.

Güvenlik ve yinelenen kontroller, yeniden deneme ayarlamalarından önce gelir.

Giden gövdeyi hassas operasyonel veri olarak ele alın. İstek gövdesini aynen imzalayın, bir teslimat zaman damgası ve değiştirilemez bir olay kimliği ekleyin, imzalama sırlarını değiştirin ve alıcı uygulamanın aynı olayın tekrarını güvenli bir şekilde yok saymasını sağlayın. Başarılı bir HTTP yanıtı, bir iş olayının tam olarak bir kez uygulandığının kanıtı değildir; bu kararı alıcı taraf vermelidir.

İş ortaklığı veya iGaming geri bildirim iş akışı için, erişim kontrolleri ve saklama kuralları açıkça belirtilmedikçe, tıklama kimliklerini, dönüşüm kimliklerini ve ödeme ile ilgili alanları kayıtlardan uzak tutun. Aşağıdaki mevcut Scaleo referansı yalnızca iş ortaklığı geri bildirim gecikmesi örneği olarak geçerlidir; kendi hizmetleriniz arasındaki teslimat sözleşmesini belgelemenin yerini tutmaz.

Faydalı uygulama referansları: Stripe webhook imza doğrulaması, PostgreSQL SELECT ve SKIP LOCKED, ve AWS'nin üstel geri çekilme ve titreşimle ilgili kılavuzu.

Veri Modeli

Her şey iki tabloyla başlar: biri teslimat kuyruğu için, diğeri gecikme ölçümleri için.

sql

-- Pending and in-flight webhook deliveries
CREATE TABLE webhook_queue (
    id              SERIAL PRIMARY KEY,
    endpoint_id     INTEGER NOT NULL,
    endpoint_url    TEXT NOT NULL,
    payload         JSONB NOT NULL,
    
    -- Delivery state
    status          VARCHAR(20) NOT NULL DEFAULT 'pending',
        -- pending, in_flight, delivered, failed, dead_letter
    attempt_count   INTEGER NOT NULL DEFAULT 0,
    max_attempts    INTEGER NOT NULL DEFAULT 5,
    
    -- Timing
    created_at      TIMESTAMP NOT NULL DEFAULT NOW(),
    next_attempt_at TIMESTAMP NOT NULL DEFAULT NOW(),
    delivered_at    TIMESTAMP,
    last_error      TEXT,
    
    -- Response tracking
    last_status_code INTEGER,
    last_response_ms INTEGER,  -- response time in milliseconds
    
    INDEX idx_queue_next (status, next_attempt_at)
        WHERE status IN ('pending', 'failed')
);

-- Per-endpoint latency measurements (ring buffer)
CREATE TABLE endpoint_latency (
    id              SERIAL PRIMARY KEY,
    endpoint_id     INTEGER NOT NULL,
    response_ms     INTEGER NOT NULL,
    status_code     INTEGER,
    measured_at     TIMESTAMP NOT NULL DEFAULT NOW(),
    
    INDEX idx_latency_endpoint (endpoint_id, measured_at)
);

-- Endpoint health state (circuit breaker)
CREATE TABLE endpoint_health (
    endpoint_id         INTEGER PRIMARY KEY,
    endpoint_url        TEXT NOT NULL,
    state               VARCHAR(20) NOT NULL DEFAULT 'closed',
        -- closed (healthy), open (broken), half_open (testing)
    consecutive_failures INTEGER NOT NULL DEFAULT 0,
    failure_threshold   INTEGER NOT NULL DEFAULT 10,
    last_failure_at     TIMESTAMP,
    last_success_at     TIMESTAMP,
    opened_at           TIMESTAMP,  -- when circuit opened
    cooldown_seconds    INTEGER NOT NULL DEFAULT 300,  -- 5 min before half_open
    
    -- Latency stats (updated periodically)
    p50_ms              INTEGER,
    p95_ms              INTEGER,
    p99_ms              INTEGER,
    sample_count        INTEGER NOT NULL DEFAULT 0
);

Bu şema hakkında dikkat edilmesi gereken üç şey var.

İlk olarak, webhook_queue tablo bir next_attempt_at Ayrı bir zamanlama mekanizması yerine sütun kullanılır. Çalışan, aşağıdaki satırları kontrol eder: status IN ('pending', 'failed') AND next_attempt_at <= NOW()Bu, basit bir gecikme kuyruğu ve dakikada yaklaşık 10,000 teslimata kadar sorunsuz çalışıyor. Bunun ötesinde, uygun bir mesaj aracı kullanın.

İkinci olarak, endpoint_latency Tablo, bir halka tampon görevi görüyor. Periyodik olarak 24 saatten eski satırları temizliyorum. Gecikme yüzdeleri şu şekildedir: endpoint_health Bu değerler, kayan pencereden hesaplanır; geçmiş ortalamaları değil, son dönemdeki davranışları temsil ederler.

Üçüncü olarak, endpoint_health Bu tablo, devre kesici durum makinesini uygulamaktadır. Bununla ilgili daha fazla bilgi aşağıda yer almaktadır.

Teslimat Görevlisi

Temel işleyici döngüsü kasıtlı olarak basittir. Karmaşıklık, teslimat yolunun kendisinde değil, yeniden deneme mantığında ve devre kesicide olmalıdır.

piton

import requests
import time
import psycopg2
from psycopg2.extras import RealDictCursor
from datetime import datetime, timedelta

DB_DSN = "postgresql://user:pass@localhost/webhooks"

def get_connection():
    return psycopg2.connect(DB_DSN)

def deliver_webhooks(batch_size=50):
    """
    Fetch pending webhooks and attempt delivery.
    Uses SELECT FOR UPDATE SKIP LOCKED for safe concurrent workers.
    """
    conn = get_connection()
    cur = conn.cursor(cursor_factory=RealDictCursor)
    
    try:
        cur.execute("""
            SELECT id, endpoint_id, endpoint_url, payload, 
                   attempt_count, max_attempts
            FROM webhook_queue
            WHERE status IN ('pending', 'failed')
              AND next_attempt_at <= NOW()
            ORDER BY next_attempt_at ASC
            LIMIT %s
            FOR UPDATE SKIP LOCKED
        """, (batch_size,))
        
        rows = cur.fetchall()
        
        for row in rows:
            # Check circuit breaker before attempting
            if is_circuit_open(cur, row['endpoint_id']):
                # Don't attempt delivery — reschedule for after cooldown
                reschedule_for_cooldown(cur, row['id'], row['endpoint_id'])
                continue
            
            # Attempt delivery and measure latency
            result = attempt_delivery(
                row['endpoint_url'], 
                row['payload']
            )
            
            # Record latency measurement regardless of success/failure
            record_latency(
                cur, 
                row['endpoint_id'], 
                result['response_ms'], 
                result['status_code']
            )
            
            if result['success']:
                mark_delivered(cur, row['id'], result)
                record_success(cur, row['endpoint_id'])
            else:
                handle_failure(
                    cur, row['id'], row['endpoint_id'],
                    row['attempt_count'], row['max_attempts'],
                    result
                )
        
        conn.commit()
    
    except Exception as e:
        conn.rollback()
        raise
    finally:
        cur.close()
        conn.close()


def attempt_delivery(url, payload):
    """
    Fire the webhook and measure response time.
    Returns dict with success, status_code, response_ms, error.
    """
    start = time.monotonic()
    
    try:
        response = requests.post(
            url,
            json=payload,
            timeout=15,          # 15 second hard timeout
            headers={
                'Content-Type': 'application/json',
                'User-Agent': 'WebhookDelivery/1.0',
                'X-Delivery-Timestamp': str(int(time.time()))
            }
        )
        
        elapsed_ms = int((time.monotonic() - start) * 1000)
        
        return {
            'success': 200 <= response.status_code < 300,
            'status_code': response.status_code,
            'response_ms': elapsed_ms,
            'error': None if response.ok else f"HTTP {response.status_code}"
        }
    
    except requests.Timeout:
        elapsed_ms = int((time.monotonic() - start) * 1000)
        return {
            'success': False,
            'status_code': None,
            'response_ms': elapsed_ms,
            'error': 'timeout_15s'
        }
    
    except requests.ConnectionError as e:
        elapsed_ms = int((time.monotonic() - start) * 1000)
        return {
            'success': False,
            'status_code': None,
            'response_ms': elapsed_ms,
            'error': f'connection_error: {str(e)[:200]}'
        }

MKS SELECT FOR UPDATE SKIP LOCKED Bu madde, birden fazla işçi örneğini çalıştırmak için kritik öneme sahiptir. Olmadan SKIP LOCKEDBu sayede, iki çalışan aynı satırda bloke olurdu. Her çalışan farklı bir bekleyen web kancası grubunu alır. Bu, daha fazla çalışan işlemi başlatarak yatay ölçeklendirme sağlar.

MKS time.monotonic() yerine aramak time.time() Bu kasıtlıdır. time.time() NTP ayarlamaları sırasında geriye doğru sıçrama yapabilir. time.monotonic() Asla geriye doğru gitmez, bu da saniyenin altındaki gecikme sürelerini ölçerken önemlidir.

Üstel Geri Çekilme ve Titreşimli Yeniden Deneme Mantığı

Bir teslimat başarısız olduğunda, yeniden deneme zamanlaması, sisteminizin sorunsuz bir şekilde toparlanıp toparlanmayacağını veya zor durumda olan bir uç noktayı hedef alan bir trafik fırtınası yaratıp yaratmayacağını belirler.

piton

import random

def calculate_next_attempt(attempt_count, base_delay=30, max_delay=3600):
    """
    Exponential backoff with full jitter.
    
    Attempt 1: 0-30s
    Attempt 2: 0-60s  
    Attempt 3: 0-120s
    Attempt 4: 0-240s
    Attempt 5: 0-480s (capped at max_delay)
    
    Full jitter prevents thundering herd when an endpoint
    recovers and hundreds of retries fire simultaneously.
    """
    exponential_delay = base_delay * (2 ** attempt_count)
    capped_delay = min(exponential_delay, max_delay)
    jittered_delay = random.uniform(0, capped_delay)
    
    return datetime.utcnow() + timedelta(seconds=jittered_delay)


def handle_failure(cur, webhook_id, endpoint_id, 
                   attempt_count, max_attempts, result):
    """
    Handle a failed delivery attempt.
    Either retry with backoff or move to dead letter queue.
    """
    new_attempt_count = attempt_count + 1
    
    if new_attempt_count >= max_attempts:
        # Exhausted retries — dead letter
        cur.execute("""
            UPDATE webhook_queue 
            SET status = 'dead_letter',
                attempt_count = %s,
                last_error = %s,
                last_status_code = %s,
                last_response_ms = %s
            WHERE id = %s
        """, (
            new_attempt_count, result['error'],
            result['status_code'], result['response_ms'],
            webhook_id
        ))
    else:
        # Schedule retry with backoff
        next_attempt = calculate_next_attempt(new_attempt_count)
        cur.execute("""
            UPDATE webhook_queue
            SET status = 'failed',
                attempt_count = %s,
                next_attempt_at = %s,
                last_error = %s,
                last_status_code = %s,
                last_response_ms = %s
            WHERE id = %s
        """, (
            new_attempt_count, next_attempt,
            result['error'], result['status_code'],
            result['response_ms'], webhook_id
        ))
    
    # Update circuit breaker
    record_failure(cur, endpoint_id)

Neden bağımsız rastgelelik veya eşit rastgelelik yerine tam rastgelelik? AWS bu konuda kesin bir analiz yayınladı. Tam rastgelelik (0 ile üstel sınır arasında rastgeleleştirme), tüm istemcilerde en düşük toplam tamamlama süresini üretir. Eşit rastgelelik (sınırın yarısı ile tam sınır arasında rastgeleleştirme) daha muhafazakardır ancak yeniden deneme birikimini boşaltmak daha yavaştır. Birçok bağımsız uç noktanın bulunduğu webhook teslimatı için tam rastgelelik doğru seçimdir çünkü her uç noktanın yeniden denemeleri bağımsızdır - aralarında koordinasyon sağlamazsınız.

Devre Kesici: Arızalı Uç Noktalara Sürekli Darbe Vermeyi Durdurun

Devre kesici deseni, sisteminizin sürekli başarısız olan uç noktalarda kaynak israfını önler. Bu desen olmadan, çalışmayan bir uç nokta, her biri 15 saniyede bir zaman aşımına uğrayan yüzlerce bekleyen yeniden deneme biriktirir; bu da çalışan kapasitenizi asla başarılı olamayacak teslimatlar için tüketir.

piton

def is_circuit_open(cur, endpoint_id):
    """
    Check if the circuit breaker is open (endpoint is broken).
    If open and cooldown has passed, transition to half_open.
    """
    cur.execute("""
        SELECT state, opened_at, cooldown_seconds
        FROM endpoint_health
        WHERE endpoint_id = %s
    """, (endpoint_id,))
    
    row = cur.fetchone()
    if not row:
        return False  # no health record = assume healthy
    
    if row['state'] == 'closed':
        return False
    
    if row['state'] == 'open':
        # Check if cooldown period has elapsed
        if row['opened_at'] and row['cooldown_seconds']:
            elapsed = (datetime.utcnow() - row['opened_at']).total_seconds()
            if elapsed >= row['cooldown_seconds']:
                # Transition to half_open — allow one probe
                cur.execute("""
                    UPDATE endpoint_health
                    SET state = 'half_open'
                    WHERE endpoint_id = %s
                """, (endpoint_id,))
                return False  # allow the probe delivery
        return True  # still in cooldown
    
    if row['state'] == 'half_open':
        return False  # allow probe delivery
    
    return False


def record_failure(cur, endpoint_id):
    """
    Record a delivery failure. Open circuit if threshold reached.
    """
    cur.execute("""
        UPDATE endpoint_health
        SET consecutive_failures = consecutive_failures + 1,
            last_failure_at = NOW()
        WHERE endpoint_id = %s
        RETURNING consecutive_failures, failure_threshold, state
    """, (endpoint_id,))
    
    row = cur.fetchone()
    if not row:
        # Create health record on first failure
        cur.execute("""
            INSERT INTO endpoint_health (endpoint_id, endpoint_url, 
                consecutive_failures, last_failure_at)
            VALUES (%s, '', 1, NOW())
            ON CONFLICT (endpoint_id) DO UPDATE
            SET consecutive_failures = endpoint_health.consecutive_failures + 1,
                last_failure_at = NOW()
        """, (endpoint_id,))
        return
    
    if row['state'] == 'half_open':
        # Probe failed — re-open circuit with longer cooldown
        cur.execute("""
            UPDATE endpoint_health
            SET state = 'open',
                opened_at = NOW(),
                cooldown_seconds = LEAST(cooldown_seconds * 2, 3600)
            WHERE endpoint_id = %s
        """, (endpoint_id,))
    
    elif (row['state'] == 'closed' and 
          row['consecutive_failures'] >= row['failure_threshold']):
        # Threshold reached — open circuit
        cur.execute("""
            UPDATE endpoint_health
            SET state = 'open',
                opened_at = NOW(),
                cooldown_seconds = 300  -- reset to 5 minutes
            WHERE endpoint_id = %s
        """, (endpoint_id,))


def record_success(cur, endpoint_id):
    """
    Record a delivery success. Close circuit if half_open.
    """
    cur.execute("""
        UPDATE endpoint_health
        SET consecutive_failures = 0,
            last_success_at = NOW(),
            state = 'closed'
        WHERE endpoint_id = %s
    """, (endpoint_id,))

Yarı açık → açık geçiş ve iki katına çıkarılmış bekleme süresi, çoğu uygulamanın gözden kaçırdığı bir detaydır. Bir uç nokta, deneme sırasında (yarı açık durum) başarısız olursa, 5 dakika sonra tekrar denemek istemezsiniz. Uç nokta hala bozuktur. Bekleme süresini 10 dakikaya, ardından 20 dakikaya, en fazla 1 saate çıkarın. Bu, devre kesicinin periyodik olarak tekrarlayan bir mekanizma haline gelmesini önler.

Gecikme Yüzdelik Dilimi Takibi

Ortalamalar yanıltıcı olabilir. Ortalama yanıt süresi 200 ms olan bir uç nokta, %95 oranında 50 ms'de, diğer %5'inde ise 3,000 ms'de yanıt verebilir. Ortalama iyi görünüyor. P95 ise her 20 teslimattan 1'ini etkileyen bir sorunu ortaya koyuyor.

piton

def record_latency(cur, endpoint_id, response_ms, status_code):
    """
    Record a latency measurement and update percentile stats.
    """
    cur.execute("""
        INSERT INTO endpoint_latency 
            (endpoint_id, response_ms, status_code)
        VALUES (%s, %s, %s)
    """, (endpoint_id, response_ms, status_code))


def update_latency_percentiles(cur, endpoint_id, window_hours=24):
    """
    Calculate P50, P95, P99 from the rolling window.
    Uses PostgreSQL's percentile_cont for exact percentiles.
    """
    cur.execute("""
        SELECT 
            COUNT(*) as sample_count,
            percentile_cont(0.50) WITHIN GROUP 
                (ORDER BY response_ms) AS p50,
            percentile_cont(0.95) WITHIN GROUP 
                (ORDER BY response_ms) AS p95,
            percentile_cont(0.99) WITHIN GROUP 
                (ORDER BY response_ms) AS p99
        FROM endpoint_latency
        WHERE endpoint_id = %s
          AND measured_at >= NOW() - INTERVAL '%s hours'
    """, (endpoint_id, window_hours))
    
    row = cur.fetchone()
    
    if row and row['sample_count'] > 0:
        cur.execute("""
            UPDATE endpoint_health
            SET p50_ms = %s,
                p95_ms = %s,
                p99_ms = %s,
                sample_count = %s
            WHERE endpoint_id = %s
        """, (
            int(row['p50']), int(row['p95']), 
            int(row['p99']), row['sample_count'],
            endpoint_id
        ))
    
    return row


def get_slow_endpoints(cur, p95_threshold_ms=2000):
    """
    Find endpoints whose P95 latency exceeds the threshold.
    These are candidates for investigation or circuit opening.
    """
    cur.execute("""
        SELECT endpoint_id, endpoint_url, 
               p50_ms, p95_ms, p99_ms, sample_count,
               state, consecutive_failures
        FROM endpoint_health
        WHERE p95_ms > %s
          AND sample_count >= 20  -- need sufficient samples
        ORDER BY p95_ms DESC
    """, (p95_threshold_ms,))
    
    return cur.fetchall()

PostgreSQL'in percentile_cont Bu, tam yüzdelik dilimleri hesaplayan sıralı küme toplama fonksiyonudur. Büyük veri kümeleri için, şuna geçersiniz: percentile_disc (Bu yöntem enterpolasyon yerine gerçek gözlemlenen değeri döndürür) veya t-digest yaklaşımını kullanın. 24 saatlik bir zaman dilimiyle webhook teslimatı için, ham verilerdeki kesin yüzdelikler, uç nokta başına yaklaşık 100,000 ölçüme kadar yeterince hızlıdır.

MKS get_slow_endpoints Bu fonksiyonu her 15 dakikada bir planlanmış bir kontrol olarak çalıştırıyorum. P95 değeri 2 saniyenin üzerinde olan uç noktalar inceleme için işaretleniyor. P95 değeri 5 saniyenin üzerinde olan uç noktaların devre kesici eşiği düşürülüyor; her başarısız teslimat, bir çalışan iş parçacığını tam zaman aşımı süresi boyunca meşgul ettiği için, devre açılmadan önce daha az ardışık hataya izin veriliyor.

Webhook Teslimat Sağlığının İzlenmesi

İşte her beş dakikada bir çalıştırdığım izleme sorgusu. Bu sorgu, tüm dağıtım hattının tek satırlık bir sağlık özetini üretir:

sql

SELECT
    -- Queue depth
    COUNT(*) FILTER (WHERE status = 'pending') AS pending,
    COUNT(*) FILTER (WHERE status = 'failed') AS awaiting_retry,
    COUNT(*) FILTER (WHERE status = 'in_flight') AS in_flight,
    COUNT(*) FILTER (WHERE status = 'dead_letter') AS dead_letter,
    
    -- Delivery rate (last hour)
    COUNT(*) FILTER (
        WHERE status = 'delivered' 
        AND delivered_at >= NOW() - INTERVAL '1 hour'
    ) AS delivered_last_hour,
    
    -- Failure rate (last hour)
    COUNT(*) FILTER (
        WHERE status IN ('failed', 'dead_letter')
        AND created_at >= NOW() - INTERVAL '1 hour'
    ) AS failed_last_hour,
    
    -- Oldest undelivered
    MIN(created_at) FILTER (
        WHERE status IN ('pending', 'failed')
    ) AS oldest_pending,
    
    -- Average delivery latency (last hour, successful only)
    AVG(last_response_ms) FILTER (
        WHERE status = 'delivered'
        AND delivered_at >= NOW() - INTERVAL '1 hour'
    ) AS avg_delivery_ms_last_hour

FROM webhook_queue;

MKS oldest_pending Bu sorguda en önemli ölçüt değerdir. Eğer değer, maksimum yeniden deneme pencerenizden (tüm geri çekilme gecikmelerinin toplamı) daha eski ise, yapısal bir sorun var demektir; ya çalışan takılı kalmıştır, ya uç nokta kara deliğe düşmüştür ya da kuyruk, boşaltabileceğinizden daha hızlı büyüyordur.

Üç koşulda uyarı veriyorum: ölü mektup sayısının artması (uç noktalar sürekli olarak başarısız oluyor ve kimse araştırma yapmıyor), bekleme kuyruğunun 30 dakikayı aşması (teslimat gecikiyor) ve İletim güvenilirliğinin azaldığını gösteren eşik değerleri aşan uç nokta başına P95 gecikmesi Üçüncüsü ise erken uyarı sinyalidir; arızalar meydana gelmeden önce gecikme artar. 200 ms'de yanıt veren bir uç nokta, 3 saniyede yanıt vermeye başlarsa, zaman aşımına uğramaya başlayacaktır.

Teslim alınmamış mektup kuyruğu sadece bir depolama alanı değil.

Çoğu ekip, başarısız web kancalarının gönderildiği ve yok edildiği bir tablo olarak ölü mektup kuyruğu uygular. Olay müdahalesi sırasında ara sıra kontrol ederler. Bu bir israftır.

Teslim edilemeyen gönderiler kuyruğu, en değerli hata ayıklama veri kümenizdir. Her satır, sisteminizin birden fazla kez denediği ve vazgeçtiği bir teslimatı temsil eder. Teslim edilemeyen gönderilerin modeli, başarı ölçütlerinin asla anlatamayacağı şeyleri size gösterir.

piton

def analyze_dead_letters(cur, hours=24):
    """
    Analyze recent dead letter entries for patterns.
    Returns per-endpoint failure analysis.
    """
    cur.execute("""
        SELECT 
            endpoint_id,
            endpoint_url,
            COUNT(*) AS dead_count,
            
            -- Most common error
            MODE() WITHIN GROUP (ORDER BY last_error) AS primary_error,
            
            -- Most common status code
            MODE() WITHIN GROUP (ORDER BY last_status_code) 
                AS primary_status_code,
            
            -- Timing
            MIN(created_at) AS first_dead,
            MAX(created_at) AS last_dead,
            
            -- Average attempts before giving up
            AVG(attempt_count)::INTEGER AS avg_attempts
            
        FROM webhook_queue
        WHERE status = 'dead_letter'
          AND created_at >= NOW() - INTERVAL '%s hours'
        GROUP BY endpoint_id, endpoint_url
        ORDER BY dead_count DESC
        LIMIT 20
    """, (hours,))
    
    return cur.fetchall()

Teslim alınmamış mektupları incelerken üç kalıba dikkat ediyorum.

Küme hataları: Aynı uç nokta için aynı saat içinde 50 ölü mektup, uç noktanın devre dışı kaldığı ve yeniden deneme penceresi içinde kurtarılamadığı anlamına gelir. Eylem: Yeniden deneme penceresini uzatın veya manuel yeniden kuyruğa alma uygulayın.

Durum kodu kalıpları: 401/403 hata kodlu isteklerde ani bir artış, uç noktanın kimlik bilgilerini değiştirdiğini ve webhook yapılandırmasının güncellenmediğini gösterir. 429 (Çok Fazla İstek) hata kodlu isteklerde ani bir artış ise, hız limitini aştığınız ve kısıtlama uygulamanız gerektiği anlamına gelir.

Aşamalı birikim: Tek bir uç nokta için günde 2-3 başarısız deneme, eşit olarak dağılmış durumda. Bu en sinsi modeldir — uç nokta çoğunlukla çalışıyor ancak zaman içinde yeniden denemeleri tüketen aralıklı hatalar yaşıyor. Çözüm genellikle artan maliyettir. max_attempts Bu belirli uç nokta için veya zaman aşımını azaltmak için.

İşçiyi Yönetmek

Her şeyi birbirine bağlayan ana döngü:

piton

import signal
import sys

running = True

def shutdown_handler(signum, frame):
    global running
    running = False
    print(f"Received signal {signum}, shutting down gracefully...")

signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGINT, shutdown_handler)

def main():
    print("Webhook delivery worker starting...")
    
    while running:
        try:
            deliver_webhooks(batch_size=50)
        except Exception as e:
            print(f"Worker error: {e}")
            time.sleep(5)  # back off on errors
            continue
        
        # Update latency stats every 100 iterations
        # (cheap operation, doesn't need to run every loop)
        if int(time.time()) % 100 == 0:
            conn = get_connection()
            cur = conn.cursor(cursor_factory=RealDictCursor)
            try:
                cur.execute(
                    "SELECT DISTINCT endpoint_id FROM endpoint_health"
                )
                for row in cur.fetchall():
                    update_latency_percentiles(cur, row['endpoint_id'])
                conn.commit()
            finally:
                cur.close()
                conn.close()
        
        # Poll interval — 500ms keeps latency low without
        # hammering the database
        time.sleep(0.5)

    print("Worker shut down cleanly.")

if __name__ == '__main__':
    main()

MKS SIGTERM Kapsayıcılaştırılmış ortamlarda temiz kapatmalar için işleyici çok önemlidir. Kubernetes SIGTERM sinyali gönderdiğinde, çalışan mevcut grubunu tamamlar, işlemi onaylar ve çıkar. Bu olmadan, satırlar takılı kalır. in_flight Hiçbir çalışanın bunları işlemediği durum.

Bu Sistem Neleri Yapmıyor (Ve Ne Zaman Daha Fazlasına İhtiyacınız Oluyor)

Bu uygulama, 2-3 işçi süreciyle tek bir PostgreSQL örneğinde dakikada yaklaşık 10,000 teslimatı işleyebiliyor. Bunun ötesinde üç değişiklik gerekiyor.

Öncelikle, PostgreSQL kuyruğunu Redis Streams veya RabbitMQ ile değiştirin. SELECT FOR UPDATE SKIP LOCKED Bu desen, yüksek işlem hacminde kuyruk tablosunda yazma çekişmesine neden olur. Özel bir mesaj aracı bu sorunu ortadan kaldırır.

İkinci olarak, uç nokta başına hız sınırlaması ekleyin. Bazı alıcı uç noktaların hız sınırları vardır (dakikada 100 istek, saatte 1,000 istek). İstemci tarafında hız sınırlaması olmadan, kotalarını aşacak ve 429 hatası alacaksınız. Her uç nokta için bir token havuzu uygulayın.

Üçüncüsü, istek imzalama özelliğini ekleyin. Veri yükündeki HMAC-SHA256 imzaları, alıcı uç noktanın web kancasının sisteminizden geldiğini ve iletim sırasında değiştirilmediğini doğrulamasına olanak tanır. Bu, finansal veri gönderen herhangi bir web kancası sistemi için olmazsa olmaz bir özelliktir.

Bu makaledeki sistem temeldir. Ölçek ne olursa olsun her webhook dağıtım sisteminin ihtiyaç duyduğu zorlu sorunları (yeniden deneme mantığı, devre kesme, gecikme ölçümü, ölü mektup analizi) ele alır. Eklediğiniz özel bileşenler (mesaj aracı, hız sınırlayıcı, istek imzalama) verim ve güvenlik gereksinimlerinize bağlıdır.

En önemli kısım, çoğu ekibin atladığı kısımdır: teslimat sisteminin kendisini ölçmek. Eğer "son 24 saatte X uç noktasına P95 teslimat gecikmesi nedir?" sorusuna cevap veremiyorsanız, körü körüne çalışıyorsunuz demektir. Önce ölçümleme araçlarını kurun. Gerisi kendiliğinden gelir.

Webhook teslimatı hakkında SSS

Webhook göndericisinin tam olarak bir kez teslimat sözü vermesi gerekir mi?

Genellikle hayır. Gönderici, yeniden denemeleri görünür hale getirmeli ve istikrarlı bir olay kimliği sağlamalıdır; alıcı ise, iş sonucunu tekrarlamadan bir olayın birden fazla kez iletilebilmesi için işlemeyi tekrarlanabilir hale getirmelidir.

Hangi başarısızlıklar yeniden denenmelidir?

Yalnızca sözleşmenizin geçici olarak sınıflandırdığı hataları (örneğin ağ hataları, zaman aşımı ve seçili sunucu yanıtları) yeniden deneyin. Hatalı istekleri, kimlik doğrulama hatalarını veya açık bir düzeltme yolu olmadan diğer kalıcı hataları tekrar tekrar denemeyin.

Bir ekip, veritabanı destekli kuyruk sisteminin ötesine ne zaman geçmelidir?

Ölçülen çekişme, birikmiş iş yükü yaşı, verimlilik veya operasyonel kurtarma ihtiyaçları, veritabanı kuyruğunun artık teslimat sözleşmesini karşılamadığını gösterdiğinde taşıma işlemi gerçekleştirin. Kapasite kararları, genel bir istek oranı iddiasına değil, gözlemlenen iş yüküne göre alınmalıdır.

Önceki Makale

2026 Yılının En İyi iGaming Ortaklık Takip Yazılımı

Sonraki Makale

iGaming'de Yapay Zeka: Kumarhaneler için ChatGPT Kullanım Örnekleri

Sezar Fikson
Yazar:

Sezar Fikson

Çevrimiçi oyun platformları ve kumar faaliyetleri ile piyasa trendleriyle ilgili verileri inceleme ve yorumlama konusunda uzmanlaşmış bir iGaming Veri Analistiyim. Oyun deneyimlerini ve iş stratejilerini optimize etmek için oyuncu davranışlarını, oyun performansını ve gelir trendlerini analiz ediyorum.

DEMO TALEP EDİN
STEP 1 3
Teşekkürler, sıraya alındınız.
NowG çözüm mühendislerinden biri, kurulum ve tanıtım görüşmesi için bir iş günü içinde sizinle iletişime geçecektir.
indeks