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_envile 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.
