DevOps & Scaling

PagerDuty ile Özel CaptchaAI Uyarıları Oluşturma

Bir CAPTCHA çözüm hattını üretimde ayakta tutmak için aslında yalnızca üç sinyali izlemeniz yeterlidir: bakiyenin tükenmesi, hata oranının yükselmesi ve worker'ların durması. Bunlardan biri eşiği aştığı anda sorunu fark etmek için panelin başında beklemenize gerek yoktur — bildirim doğrudan nöbetçi mühendise gider.

Bu rehberde tam olarak bunu kuruyoruz: CaptchaAI'nin getbalance uç noktasını ve kendi çözüm metriklerinizi periyodik olarak sorgulayan, eşik aşıldığında PagerDuty Events API v2 üzerinden olay açan küçük bir izleme servisi. Böylece bir aksaklığın kaybettirdiği saatler dakikalara iner ve nöbetçi, günlükleri tek tek açmadan sorunu teşhis edecek bağlamı baştan alır.

Hangi sinyalde kimi uyaralım: CaptchaAI uyarı stratejisi

Her olay aynı aciliyette değildir. İyi bir uyarı stratejisi, gerçekten birini uyandırması gereken durumları (bakiye bitmiş, tüm worker'lar durmuş) düşük öncelikli bilgilendirmelerden (gecikme biraz yükselmiş) ayırır. Aşağıdaki eşikler pratikte iyi çalışan bir başlangıç noktasıdır; kendi hacminize göre ayarlayın.

Önem Durum PagerDuty eylemi
Kritik Bakiye < $2 Nöbetçi mühendisi çağır
Kritik Tüm worker'lar durdu Nöbetçi mühendisi çağır
Yüksek Hata oranı 5 dakika boyunca > %20 Acil (urgent) olay oluştur
Uyarı Bakiye < $10 Düşük öncelikli olay oluştur
Uyarı Kuyruk derinliği 10 dakika boyunca > 100 Düşük öncelikli olay oluştur
Bilgi Çözüm gecikmesi p95 > 120s Mevcut olaya ekle veya günlüğe yaz

İki eşik türünü ayırt etmek önemli. Bakiye eşikleri kesin sayılardır: $2'nin altı, hattın birkaç dakika içinde tamamen durabileceği anlamına gelir; bu bir "hemen uyandır" durumudur. Hata oranı ise pencereli bir metriktir — tek bir başarısız çözüm önemli değildir, ama beş dakikalık pencerede isteklerin beşte birinden fazlası düşüyorsa hedef sitede bir değişiklik ya da altyapınızda bir sorun var demektir. Gecikme ve kuyruk derinliği çoğunlukla "bilgi" seviyesinde kalır: nöbetçiyi uyandırmaz, ama sabah bakıldığında bir eğilimi gösterir.

Python ile izleme servisi: PagerDuty Events API v2

Aşağıdaki servis iki parçadan oluşur. CaptchaPagerDuty sınıfı PagerDuty tarafını yönetir — olay açar (trigger), susturur (acknowledge) ve kapatır (resolve). CaptchaMonitor ise ölçümü yapar: her çözümün sonucunu kayan bir pencerede tutar, hata oranını hesaplar ve getbalance ile bakiyeyi çeker. dedup_key alanına dikkat edin; PagerDuty aynı tekilleştirme anahtarına sahip tekrar eden tetikleyicileri tek bir olayda birleştirir, böylece uyarı seli oluşmaz.

import os
import time
import hashlib
import requests
from datetime import datetime

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
PAGERDUTY_ROUTING_KEY = os.environ["PAGERDUTY_ROUTING_KEY"]

session = requests.Session()


class CaptchaPagerDuty:
    EVENTS_URL = "https://events.pagerduty.com/v2/enqueue"

    def __init__(self, routing_key):
        self.routing_key = routing_key

    def trigger(self, summary, severity="error", source="captcha-pipeline",
                details=None, dedup_key=None):
        """Trigger a new PagerDuty incident."""
        payload = {
            "routing_key": self.routing_key,
            "event_action": "trigger",
            "payload": {
                "summary": summary,
                "severity": severity,  # critical, error, warning, info
                "source": source,
                "timestamp": datetime.utcnow().isoformat() + "Z",
                "custom_details": details or {}
            }
        }

        if dedup_key:
            payload["dedup_key"] = dedup_key

        resp = requests.post(self.EVENTS_URL, json=payload, timeout=10)
        resp.raise_for_status()
        return resp.json()

    def resolve(self, dedup_key):
        """Resolve an existing incident."""
        payload = {
            "routing_key": self.routing_key,
            "event_action": "resolve",
            "dedup_key": dedup_key
        }
        resp = requests.post(self.EVENTS_URL, json=payload, timeout=10)
        resp.raise_for_status()
        return resp.json()

    def acknowledge(self, dedup_key):
        """Acknowledge an existing incident."""
        payload = {
            "routing_key": self.routing_key,
            "event_action": "acknowledge",
            "dedup_key": dedup_key
        }
        resp = requests.post(self.EVENTS_URL, json=payload, timeout=10)
        resp.raise_for_status()
        return resp.json()


pagerduty = CaptchaPagerDuty(PAGERDUTY_ROUTING_KEY)


class CaptchaMonitor:
    def __init__(self):
        self.error_window = []  # (timestamp, is_error)
        self.window_size = 300  # 5 minutes in seconds

    def record_solve(self, success):
        now = time.time()
        self.error_window.append((now, not success))
        # Prune old entries
        self.error_window = [
            (t, e) for t, e in self.error_window
            if now - t < self.window_size
        ]

    @property
    def error_rate(self):
        if not self.error_window:
            return 0.0
        errors = sum(1 for _, e in self.error_window if e)
        return errors / len(self.error_window)

    def check_balance(self):
        resp = session.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "getbalance", "json": 1
        })
        data = resp.json()
        if data.get("status") != 1:
            return None
        return float(data["request"])

    def run_checks(self):
        """Run all monitoring checks and trigger alerts."""
        # Check balance
        balance = self.check_balance()
        if balance is not None:
            if balance < 2:
                pagerduty.trigger(
                    summary=f"CaptchaAI balance critically low: ${balance:.2f}",
                    severity="critical",
                    dedup_key="captcha-balance-critical",
                    details={"balance": balance, "threshold": 2}
                )
            elif balance < 10:
                pagerduty.trigger(
                    summary=f"CaptchaAI balance low: ${balance:.2f}",
                    severity="warning",
                    dedup_key="captcha-balance-warning",
                    details={"balance": balance, "threshold": 10}
                )
            else:
                # Resolve if balance recovered
                try:
                    pagerduty.resolve("captcha-balance-critical")
                    pagerduty.resolve("captcha-balance-warning")
                except Exception:
                    pass  # No incident to resolve

        # Check error rate
        rate = self.error_rate
        if rate > 0.20:
            total = len(self.error_window)
            errors = sum(1 for _, e in self.error_window if e)
            pagerduty.trigger(
                summary=f"CaptchaAI error rate {rate:.0%} "
                        f"({errors}/{total} in 5 min)",
                severity="error",
                dedup_key="captcha-error-rate-high",
                details={
                    "error_rate": round(rate, 3),
                    "total_tasks": total,
                    "failed_tasks": errors,
                    "window_seconds": self.window_size
                }
            )
        elif rate < 0.05 and len(self.error_window) > 10:
            try:
                pagerduty.resolve("captcha-error-rate-high")
            except Exception:
                pass


monitor = CaptchaMonitor()

# After each solve:
# monitor.record_solve(success=True)

# Run checks every 60 seconds:
# while True:
#     monitor.run_checks()
#     time.sleep(60)

Çalıştırmak için run_checks fonksiyonunu 60 saniyede bir çağırmanız yeterli; her başarılı ya da başarısız çözümden sonra record_solve ile pencereyi güncellersiniz. Bakiye toparlandığında kod ilgili olayı otomatik olarak resolve eder, yani nöbetçinin olayı elle kapatmasına gerek kalmaz.

Node.js ile aynı entegrasyonu kurma

Yığınınız Node.js üzerindeyse mantık birebir aynıdır: PagerDutyAlerter olayları açıp kapatır, CaptchaHealthMonitor kayan pencerede hata oranını izler. Bakiye axios ile getbalance üzerinden çekilir ve setInterval kontrolü her 60 saniyede bir tetikler.

const axios = require("axios");

const API_KEY = process.env.CAPTCHAAI_API_KEY;
const PD_ROUTING_KEY = process.env.PAGERDUTY_ROUTING_KEY;
const PD_EVENTS_URL = "https://events.pagerduty.com/v2/enqueue";

class PagerDutyAlerter {
  constructor(routingKey) {
    this.routingKey = routingKey;
  }

  async trigger(summary, severity = "error", details = {}, dedupKey = null) {
    const payload = {
      routing_key: this.routingKey,
      event_action: "trigger",
      payload: {
        summary,
        severity,
        source: "captcha-pipeline",
        timestamp: new Date().toISOString(),
        custom_details: details,
      },
    };
    if (dedupKey) payload.dedup_key = dedupKey;

    const resp = await axios.post(PD_EVENTS_URL, payload, { timeout: 10000 });
    return resp.data;
  }

  async resolve(dedupKey) {
    await axios.post(PD_EVENTS_URL, {
      routing_key: this.routingKey,
      event_action: "resolve",
      dedup_key: dedupKey,
    }, { timeout: 10000 });
  }
}

const alerter = new PagerDutyAlerter(PD_ROUTING_KEY);

class CaptchaHealthMonitor {
  constructor(windowMs = 300000) {
    this.results = [];
    this.windowMs = windowMs;
  }

  record(success) {
    this.results.push({ time: Date.now(), success });
    const cutoff = Date.now() - this.windowMs;
    this.results = this.results.filter((r) => r.time > cutoff);
  }

  get errorRate() {
    if (this.results.length === 0) return 0;
    const errors = this.results.filter((r) => !r.success).length;
    return errors / this.results.length;
  }

  async checkAndAlert() {
    // Balance check
    try {
      const resp = await axios.get("https://ocr.captchaai.com/res.php", {
        params: { key: API_KEY, action: "getbalance", json: 1 },
      });
      if (resp.data.status === 1) {
        const balance = parseFloat(resp.data.request);
        if (balance < 2) {
          await alerter.trigger(
            `CaptchaAI balance critically low: $${balance.toFixed(2)}`,
            "critical",
            { balance },
            "captcha-balance-critical"
          );
        } else if (balance < 10) {
          await alerter.trigger(
            `CaptchaAI balance low: $${balance.toFixed(2)}`,
            "warning",
            { balance },
            "captcha-balance-warning"
          );
        } else {
          await alerter.resolve("captcha-balance-critical").catch(() => {});
          await alerter.resolve("captcha-balance-warning").catch(() => {});
        }
      }
    } catch (err) {
      console.error("Balance check failed:", err.message);
    }

    // Error rate check
    const rate = this.errorRate;
    if (rate > 0.2 && this.results.length > 10) {
      await alerter.trigger(
        `CaptchaAI error rate: ${(rate * 100).toFixed(1)}%`,
        "error",
        { errorRate: rate, totalTasks: this.results.length },
        "captcha-error-rate"
      );
    } else if (rate < 0.05 && this.results.length > 10) {
      await alerter.resolve("captcha-error-rate").catch(() => {});
    }
  }
}

const monitor = new CaptchaHealthMonitor();

// Run checks every 60 seconds
setInterval(() => monitor.checkAndAlert(), 60000);

module.exports = { monitor, alerter };

PagerDuty tarafını kurma: adım adım kontrol listesi

Kod tarafı hazır olduğunda geriye PagerDuty'yi yapılandırmak kalır. Aşağıdaki altı adım, yönlendirme anahtarını almaktan yükseltme politikasını kurmaya kadar tek seferlik hazırlığı kapsar.

Adım Eylem
1 "CaptchaAI Pipeline" için PagerDuty'de bir hizmet oluşturun
2 Hizmete Events API v2 entegrasyonunu ekleyin
3 Yönlendirme anahtarını PAGERDUTY_ROUTING_KEY ortam değişkenine kopyalayın
4 Yükseltme (escalation) politikasını ayarlayın: nöbetçi → ekip lideri → yönetici
5 Bildirim kurallarını yapılandırın (anlık bildirim, SMS, telefon)
6 Planlı bakımlar için bakım aralıkları (maintenance window) ekleyin

Sorun giderme: uyarılar tetiklenmediğinde

Entegrasyonda en sık görülen dört sorun ve hızlı çözümleri aşağıda.

Sorun Sebep Düzeltme
Uyarı tetiklenmiyor Yanlış yönlendirme anahtarı Anahtarın hizmetin Events API entegrasyonuyla eşleştiğini doğrulayın
Yinelenen olaylar dedup_key eksik Her uyarı türü için tutarlı bir tekilleştirme anahtarı belirleyin
Uyarı seli Tetikleyiciler arasında bekleme yok PagerDuty aynı tekilleştirme anahtarına sahip kopyaları bastırır; anahtarı kullandığınızdan emin olun
Otomatik kapatma (auto-resolve) çalışmıyor Tekilleştirme anahtarı uyuşmuyor resolve çağrısının tetikleyiciyle tamamen aynı anahtarı kullandığından emin olun

Türkiye'deki ekipler için pratik notlar

Türkiye'deki geliştirici ekiplerinin çoğu e-ticaret ve fintek tarafında yoğunlaşır; gece boyunca çalışan ödeme adımı ve checkout regresyon testleri, CAPTCHA çözüm hattının en sessiz saatlerde bile ayakta kalmasını gerektirir. Nöbet rotasyonunuzu Europe/Istanbul saat dilimine göre kurun ki uyarı, o an klavye başındaki kişiye ulaşsın.

Maliyet tarafında CaptchaAI'nin thread tabanlı planları öngörülebilirlik sağlar: BASIC ($15/ay, 5 thread) küçük bir izleme hattı için yeterken, yüksek hacimli işler VIP-3'e ($7,500/ay, 5.000 thread) kadar ölçeklenir. Fiyatlar USD üzerinden sabittir; TL'nin oynaklığı düşünüldüğünde bu, aylık bütçeyi tahmin etmeyi kolaylaştıran gerçek bir avantajdır. Hattınız kişisel veri işleyen kaynaklara dokunuyorsa KVKK yükümlülüklerini göz önünde bulundurun ve otomasyonu yalnızca yetkili QA ve veri toplama iş akışlarında çalıştırın.

Sık sorulan sorular

Bakiye uyarısı için hangi eşiği seçmeliyim?

Bu, günlük tüketiminize bağlıdır. Pratik kural: kritik eşiği kalan bakiyenin bir sonraki iş gününü çıkaramayacağı seviyeye, uyarı eşiğini ise rahatça yeniden yükleme yapacak kadar zaman tanıyan seviyeye koyun. Örnekteki $2 (kritik) ve $10 (uyarı) küçük bir hat için iyi bir başlangıçtır; hacim arttıkça bu sayıları yukarı çekin.

Hangi CaptchaAI metriklerini izlemeliyim?

En değerli üç sinyal bakiye, hata oranı ve worker durumudur. Bunlara getbalance ile çekilen bakiyeyi, kayan pencerede hesapladığınız çözüm hata oranını ve p95 çözüm gecikmesini ekleyin. Kuyruk derinliği de kapasite darboğazlarını erkenden gösterir.

Uyarı yorgunluğunu nasıl azaltırım?

İlgili tetikleyicileri tek bir olayda toplamak için dedup_key kullanın ve uyarı seviyesindeki bildirimleri düşük öncelikli (nöbetçiyi uyandırmayan) tutun. Kritik ve yüksek önceliği yalnızca bakiye < $2 ya da tüm worker'ların durması gibi gerçekten acil durumlara ayırın.

Aynı sorun için tekrar tekrar olay açılıyor; nasıl engellerim?

Neredeyse her zaman dedup_key eksikliğidir. Her uyarı türü için tutarlı bir tekilleştirme anahtarı belirleyin; PagerDuty aynı anahtara sahip yeni tetikleyicileri mevcut olayla birleştirir, durum düzeldiğinde resolve aynı anahtarla olayı otomatik kapatır.

PagerDuty yerine Datadog veya New Relic üzerinden yönlendirebilir miyim?

Evet. Hem Datadog hem de New Relic yerleşik PagerDuty entegrasyonlarına sahiptir. Metrikleri zaten bir gözlemlenebilirlik platformuna gönderiyorsanız uyarıları oradan yönlendirmek pratiktir. Buradaki doğrudan API entegrasyonu ise en fazla kontrolü istediğinizde en iyi seçenektir.

İlgili makaleler

Sonraki adımlar

CAPTCHA hattınız sorun yaşadığı anda haberdar olun: CaptchaAI API anahtarınızı alın ve PagerDuty'yi bağlayın.

İlgili kılavuzlar:

Bu makale için yorumlar devre dışı bırakılmıştır.