Hasura Scheduled Triggers ile Otomatik Görev Zamanlama ve Cron Job Yönetimi

Eğer bir web uygulaması geliştiriyorsanız ve belirli görevleri düzenli aralıklarla otomatik olarak çalıştırmanız gerekiyorsa, aklınıza ilk gelen şey muhtemelen geleneksel cron job’lardır. Sunucuya SSH aç, crontab düzenle, loglara bak, bir şeyler bozulunca debug et… Tanıdık geldi mi? Hasura’nın Scheduled Triggers özelliği tam da bu noktada hayatı ciddi anlamda kolaylaştırıyor. GraphQL API’niz zaten Hasura üzerinden yönetiliyorsa, zamanlı görevlerinizi de aynı ekosistem içinde, görsel arayüzle ya da API ile yönetebilirsiniz.

Scheduled Triggers Nedir ve Neden Kullanmalıyız?

Hasura’daki Scheduled Triggers, arka planda gerçek anlamda bir cron job motoru çalıştırır. Tanımladığınız zamanlama planına göre belirttiğiniz bir HTTP endpoint’ini çağırır. Bu kadar basit ama bir o kadar da güçlü. Klasik cron’dan farkı şu: trigger’larınız veritabanına kaydedilir, execution logları tutulur, başarısız denemelerde retry mekanizması devreye girer ve tüm bunları Hasura Console üzerinden izleyebilirsiniz.

Bir de One-off Scheduled Events var, bunlar tek seferlik zamanlı görevler için kullanılıyor. Örneğin bir kullanıcı kayıt olduğunda 24 saat sonra hoşgeldin e-postası göndermek istiyorsunuz, tam bu iş için biçilmiş kaftan.

Neden Hasura Scheduled Triggers tercih edilmeli:

  • Altyapı karmaşıklığını azaltır, ayrı bir job scheduler kurmaya gerek kalmaz
  • Execution history ve retry logic kutudan çıkar
  • Hasura metadata olarak export edilip versiyon kontrolüne alınabilir
  • Payload ile birlikte HTTP endpoint’e POST isteği gönderir, dil bağımsız çalışır
  • Kubernetes ya da container ortamlarında crontab yönetmek zorunda kalmazsınız

Ön Gereksinimler ve Kurulum

Öncelikle çalışan bir Hasura instance’ınız olmalı. Docker Compose ile hızlıca ayağa kaldıralım:

version: '3.6'
services:
  postgres:
    image: postgres:15
    restart: always
    environment:
      POSTGRES_PASSWORD: postgrespassword
    volumes:
      - db_data:/var/lib/postgresql/data

  graphql-engine:
    image: hasura/graphql-engine:v2.36.0
    ports:
      - "8080:8080"
    depends_on:
      - postgres
    restart: always
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres
      HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
      HASURA_GRAPHQL_DEV_MODE: "true"
      HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey
      HASURA_GRAPHQL_ENABLED_APIS: "metadata,graphql,pgdump,config"

volumes:
  db_data:
docker-compose up -d
docker-compose logs -f graphql-engine

Hasura ayağa kalktıktan sonra http://localhost:8080/console adresine gidin ve admin secret’ınızla giriş yapın. Sol menüden Events sekmesini göreceksiniz, Scheduled Triggers oradan yönetiliyor.

İlk Scheduled Trigger: API ile Oluşturma

Console üzerinden tıklayarak da oluşturabilirsiniz ama gerçek sysadmin gibi API’yi kullanalım. Hasura’nın metadata API’si üzerinden trigger tanımlıyoruz:

curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: myadminsecretkey' 
  -d '{
    "type": "create_cron_trigger",
    "args": {
      "name": "daily_cleanup_trigger",
      "webhook": "http://your-backend-service:3000/api/tasks/daily-cleanup",
      "schedule": "0 2 * * *",
      "payload": {
        "task": "cleanup",
        "environment": "production",
        "dry_run": false
      },
      "headers": [
        {
          "name": "Authorization",
          "value_from_env": "WEBHOOK_SECRET_TOKEN"
        },
        {
          "name": "Content-Type",
          "value": "application/json"
        }
      ],
      "retry_conf": {
        "num_retries": 3,
        "timeout_seconds": 60,
        "tolerance_seconds": 21600,
        "retry_interval_seconds": 300
      },
      "include_in_metadata": true,
      "comment": "Her gece saat 02:00da eski verileri temizler"
    }
  }'

Buradaki önemli parametreler:

  • schedule: Standart cron formatı, 0 2 * her gece 02:00 anlamına gelir
  • webhook: POST isteği gönderilecek endpoint
  • payload: Webhook’a gönderilecek JSON body
  • retry_conf: Başarısız denemelerde ne yapılacağı
  • num_retries: Kaç kez tekrar deneneceği
  • timeout_seconds: Webhook’un kaç saniyede cevap vermesi bekleneceği
  • tolerance_seconds: Hasura kapalıyken planlanan trigger’ların ne kadar geriye dönük çalıştırılacağı
  • include_in_metadata: Trigger’ın metadata export’a dahil edilip edilmeyeceği

Gerçek Dünya Senaryosu 1: Günlük Rapor Oluşturma

Diyelim ki e-ticaret platformunuz var ve her sabah 06:00’da bir önceki günün satış raporunu oluşturup ilgili kişilere e-posta göndermeniz gerekiyor. Node.js ile basit bir webhook handler yazalım:

const express = require('express');
const app = express();
app.use(express.json());

app.post('/api/tasks/daily-report', async (req, res) => {
  // Hasura'dan gelen payload
  const { scheduled_time, payload } = req.body;
  
  console.log(`Rapor görevi başlatıldı: ${scheduled_time}`);
  
  try {
    // Dün için tarih aralığı hesapla
    const yesterday = new Date(scheduled_time);
    yesterday.setDate(yesterday.getDate() - 1);
    const dateStr = yesterday.toISOString().split('T')[0];
    
    // Hasura GraphQL üzerinden satış verilerini çek
    const salesData = await fetchSalesData(dateStr);
    
    // Raporu oluştur ve gönder
    await generateAndSendReport(salesData, dateStr, payload.recipients);
    
    res.json({ 
      success: true, 
      message: `${dateStr} tarihli rapor başarıyla gönderildi`,
      records_processed: salesData.length
    });
  } catch (error) {
    console.error('Rapor oluşturma hatası:', error);
    // 500 döndürmek Hasura'ya retry yapmasını söyler
    res.status(500).json({ 
      success: false, 
      error: error.message 
    });
  }
});

async function fetchSalesData(date) {
  const query = `
    query GetDailySales($date: date!) {
      orders(where: {
        created_at: {_gte: $date},
        status: {_eq: "completed"}
      }) {
        id
        total_amount
        customer_id
        items_count
      }
    }
  `;
  
  const response = await fetch('http://graphql-engine:8080/v1/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-hasura-admin-secret': process.env.HASURA_ADMIN_SECRET
    },
    body: JSON.stringify({ query, variables: { date } })
  });
  
  const { data } = await response.json();
  return data.orders;
}

app.listen(3000, () => console.log('Webhook handler port 3000 üzerinde çalışıyor'));

Bu handler için trigger’ı tanımlayalım:

curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: myadminsecretkey' 
  -d '{
    "type": "create_cron_trigger",
    "args": {
      "name": "daily_sales_report",
      "webhook": "http://webhook-handler:3000/api/tasks/daily-report",
      "schedule": "0 6 * * 1-5",
      "payload": {
        "report_type": "daily_sales",
        "recipients": ["[email protected]", "[email protected]"],
        "include_charts": true
      },
      "retry_conf": {
        "num_retries": 2,
        "timeout_seconds": 120,
        "tolerance_seconds": 3600,
        "retry_interval_seconds": 600
      },
      "include_in_metadata": true,
      "comment": "Hafta ici her sabah 06:00da gunluk satis raporu"
    }
  }'

0 6 1-5 ifadesi hafta içi her sabah 06:00 anlamına gelir, hafta sonları çalışmaz.

Gerçek Dünya Senaryosu 2: One-off Events ile Kullanıcı Yaşam Döngüsü Yönetimi

Bir kullanıcı premium aboneliğini iptal ettiğinde, 7 gün sonra “geri dön” e-postası göndermek istiyorsunuz. Bunun için Hasura’nın Event Trigger + One-off Scheduled Event kombinasyonunu kullanabiliriz.

Önce kullanıcı abonelik iptali sırasında one-off event oluşturalım:

curl -X POST 
  http://localhost:8080/v1/query 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: myadminsecretkey' 
  -d '{
    "type": "create_scheduled_event",
    "args": {
      "webhook": "http://webhook-handler:3000/api/tasks/win-back-email",
      "schedule_at": "2024-03-20T10:00:00Z",
      "payload": {
        "user_id": 12345,
        "user_email": "[email protected]",
        "cancellation_reason": "price",
        "offer_code": "COMEBACK30"
      },
      "headers": [
        {
          "name": "X-Internal-Token",
          "value_from_env": "INTERNAL_API_TOKEN"
        }
      ],
      "retry_conf": {
        "num_retries": 3,
        "timeout_seconds": 30,
        "retry_interval_seconds": 900
      },
      "comment": "Kullanici 12345 icin geri donus e-postasi"
    }
  }'

Tabii ki bu isteği her seferinde manuel atmıyoruz. Uygulama kodunuzdan veya bir Event Trigger handler’ından dinamik olarak tetikliyorsunuz.

Metadata Yönetimi ve Versiyon Kontrolü

Hasura’nın en güzel özelliklerinden biri, tüm yapılandırmanın metadata olarak export edilebilmesi. Scheduled trigger’larınızı da bu şekilde Git’e alabilirsiniz.

# Hasura CLI kurulumu
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash

# Projeyi başlat
hasura init my-project --directory ./hasura
cd hasura

# Metadata'yı çek
hasura metadata export 
  --endpoint http://localhost:8080 
  --admin-secret myadminsecretkey

# Şimdi hasura/metadata/cron_triggers.yaml dosyasına bakın
cat metadata/cron_triggers.yaml

Export edilen YAML şöyle görünür:

- name: daily_sales_report
  webhook: http://webhook-handler:3000/api/tasks/daily-report
  schedule: 0 6 * * 1-5
  include_in_metadata: true
  payload:
    report_type: daily_sales
    recipients:
      - [email protected]
      - [email protected]
    include_charts: true
  retry_conf:
    interval_sec: 600
    num_retries: 2
    timeout_sec: 120
    tolerance_seconds: 3600
  headers:
    - name: Authorization
      value_from_env: WEBHOOK_SECRET_TOKEN
  comment: Hafta ici her sabah 06:00da gunluk satis raporu

- name: daily_cleanup_trigger
  webhook: http://your-backend-service:3000/api/tasks/daily-cleanup
  schedule: 0 2 * * *
  include_in_metadata: true
  payload:
    task: cleanup
    environment: production
    dry_run: false
  retry_conf:
    interval_sec: 300
    num_retries: 3
    timeout_sec: 60
    tolerance_seconds: 21600
  headers:
    - name: Authorization
      value_from_env: WEBHOOK_SECRET_TOKEN
  comment: Her gece saat 02:00da eski verileri temizler

Bu dosyayı Git’e commit edin, CI/CD pipeline’ınızda hasura metadata apply ile deploy edin. Cron job yönetimi artık infrastructure-as-code:

# CI/CD pipeline'ında metadata uygula
hasura metadata apply 
  --endpoint $HASURA_ENDPOINT 
  --admin-secret $HASURA_ADMIN_SECRET

# Tutarsızlıkları kontrol et
hasura metadata diff 
  --endpoint $HASURA_ENDPOINT 
  --admin-secret $HASURA_ADMIN_SECRET

Monitoring ve Log Yönetimi

Scheduled trigger’larınızın durumunu API üzerinden sorgulayabilirsiniz:

# Belirli bir trigger'ın son çalışmalarını listele
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: myadminsecretkey' 
  -d '{
    "type": "get_cron_trigger_event_invocations",
    "args": {
      "name": "daily_sales_report",
      "limit": 10,
      "offset": 0
    }
  }'

Daha kapsamlı monitoring için Hasura’nın sağladığı Prometheus metriklerini kullanabilirsiniz. docker-compose.yml dosyanıza Prometheus ve Grafana ekleyin:

# Hasura metriklerini kontrol et
curl http://localhost:8080/v1/metrics 
  -H 'x-hasura-admin-secret: myadminsecretkey' | grep scheduled

# Beklenen çıktı örneği:
# hasura_cron_events_processed_total{status="delivered"} 142
# hasura_cron_events_processed_total{status="error"} 3
# hasura_scheduled_trigger_pending_events 0

Başarısız trigger’lar için alert kurmak istiyorsanız, basit bir health check script’i yazabilirsiniz:

#!/bin/bash

# Scheduled trigger sağlık kontrolü
HASURA_ENDPOINT="http://localhost:8080"
ADMIN_SECRET="myadminsecretkey"
ALERT_WEBHOOK="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
MAX_FAILURES=3

check_trigger_health() {
  local trigger_name=$1
  
  response=$(curl -s -X POST 
    "$HASURA_ENDPOINT/v1/metadata" 
    -H "Content-Type: application/json" 
    -H "x-hasura-admin-secret: $ADMIN_SECRET" 
    -d "{
      "type": "get_cron_trigger_event_invocations",
      "args": {
        "name": "$trigger_name",
        "limit": 5
      }
    }")
  
  # Son 5 çalışmadan kaçı hatalıydı?
  error_count=$(echo "$response" | python3 -c "
import sys, json
data = json.load(sys.stdin)
invocations = data.get('invocations', [])
errors = sum(1 for inv in invocations if inv.get('status') == 'error')
print(errors)
")
  
  if [ "$error_count" -ge "$MAX_FAILURES" ]; then
    echo "UYARI: $trigger_name trigger'i son $error_count calistirmada basarisiz oldu!"
    
    # Slack'e bildir
    curl -s -X POST "$ALERT_WEBHOOK" 
      -H 'Content-Type: application/json' 
      -d "{
        "text": ":warning: Hasura Trigger Alarmi: `$trigger_name` son $error_count denemede basarisiz!"
      }"
  fi
}

# Kritik trigger'ları kontrol et
check_trigger_health "daily_sales_report"
check_trigger_health "daily_cleanup_trigger"

echo "Saglık kontrolu tamamlandi"

Bu script’i 15 dakikada bir crontab’a ekleyin, ironik ama Hasura trigger’larını kontrol etmek için klasik cron kullanıyoruz:

*/15 * * * * /opt/scripts/check_hasura_triggers.sh >> /var/log/hasura-health.log 2>&1

Trigger Silme ve Güncelleme

Mevcut bir trigger’ı güncellemek için önce silip yeniden oluşturmanız gerekir:

# Trigger'ı sil
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: myadminsecretkey' 
  -d '{
    "type": "delete_cron_trigger",
    "args": {
      "name": "daily_sales_report"
    }
  }'

# Güncel konfigürasyonla yeniden oluştur
curl -X POST 
  http://localhost:8080/v1/metadata 
  -H 'Content-Type: application/json' 
  -H 'x-hasura-admin-secret: myadminsecretkey' 
  -d '{
    "type": "create_cron_trigger",
    "args": {
      "name": "daily_sales_report",
      "webhook": "http://webhook-handler:3000/api/tasks/daily-report",
      "schedule": "0 7 * * 1-5",
      "payload": {
        "report_type": "daily_sales",
        "recipients": ["[email protected]"],
        "include_charts": true,
        "include_comparison": true
      },
      "retry_conf": {
        "num_retries": 3,
        "timeout_seconds": 180,
        "tolerance_seconds": 3600,
        "retry_interval_seconds": 600
      },
      "include_in_metadata": true,
      "comment": "Hafta ici 07:00da gunluk satis raporu - v2"
    }
  }'

Güvenlik İpuçları

Production ortamında dikkat etmeniz gereken birkaç kritik nokta:

  • Webhook endpoint’lerinizi mutlaka kimlik doğrulamayla koruyun. Hasura’nın gönderdiği istekleri doğrulamak için value_from_env ile environment variable’dan secret token okuyun, asla sabit değer yazmayın.
  • Webhook handler’larınızı idempotent yazın. Retry mekanizması devrede olduğu için aynı istek birden fazla kez gelebilir. Her çalışmaya unique bir ID atayıp duplicate işlemleri engelleyin.
  • Tolerance seconds değerini dikkatli ayarlayın. Hasura uzun süre kapalı kalırsa ve tolerance süresi dolmuşsa, kaçırılan trigger’lar çalıştırılmaz. Kritik görevler için bu değeri yüksek tutun ama çok yüksek tutarsanız Hasura yeniden açıldığında binlerce eski trigger birden tetiklenebilir.
  • Webhook handler’larınızın timeout değerini Hasura’daki timeout_seconds‘dan küçük tutun. Böylece kendi tarafınızda graceful shutdown yapabilirsiniz.
  • Network güvenliği için webhook handler’larınızı doğrudan internet’e açmayın, Hasura ve handler aynı iç network’te olsun.

Sonuç

Hasura Scheduled Triggers, özellikle zaten Hasura kullanıyorsanız, ayrı bir cron job yönetim sistemi kurma zahmetinden kurtarıyor. Evet, RabbitMQ, Celery ya da özel job scheduler çözümleri daha gelişmiş senaryolar için gerekli olabilir. Ama günlük rapor oluşturma, temizlik görevleri, hatırlatma e-postaları gibi standart scheduled task ihtiyaçları için Hasura’nın sunduğu çözüm yeterince güçlü ve çok daha az operasyonel yük getiriyor.

En büyük avantajı, trigger konfigürasyonlarının metadata olarak versiyon kontrolüne girmesi. Artık “production sunucusundaki crontab’ı kim düzenledi, ne zaman değişti” gibi sorular geçmişte kalıyor. Her şey Git’te, her değişiklik review edilebilir, her deployment tekrarlanabilir. Sysadmin olarak bu tür izlenebilirlik benim için paha biçilmez.

Başlangıç noktası olarak en kritik cron job’larınızı alın, bunları Scheduled Trigger’lara dönüştürün ve birkaç hafta izleyin. Retry mekanizmasının sizi kaç kez kurtardığını görünce gerisini kendiniz yapacaksınız.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir