İçeriğe geç
Toova
Tüm Araçlar

Gerçek Üretimde Kullanılan 5 Cron İfade Deseni

Toova

Cron ifadeleri basit görünür — beş alan, bir avuç işleç — ancak üretim ekipleri aynı hataları defalarca yapar: belirli aylarda sessizce atlanan işler, paylaşılan durumu bozan örtüşen çalışmalar ve aynı saniyede uyanan ve bir veritabanını alt eden yüzlerce iş. Sorun nadiren ifadedir; sorun, uç durumları düşünmemektir.

Bu kılavuz, mühendislik ekiplerinin üretimde gerçekten kullandığı beş cron desenini ele alır; her birinin ardındaki gerekçeyle ve kaçınılması gereken tuzaklarla. Herhangi bir ifadeyi doğrulamak ve dağıtmadan önce sonraki on yürütme zamanını görmek için Toova Cron Parser'ı kullanın.

Cron İfade Anatomisi

Standart bir cron ifadesinin boşlukla ayrılmış beş alanı vardır:

*/15  *  *  *  *
  |   |  |  |  |
  |   |  |  |  +---- haftanın günü (0-6, 0=Pazar)
  |   |  |  +------- ay (1-12)
  |   |  +---------- ayın günü (1-31)
  |   +------------- saat (0-23)
  +----------------- dakika: her 15'inci (0, 15, 30, 45)

Her alan şunları kabul eder: belirli bir değer (5), bir joker karakter (*), bir aralık (1-5), bir liste (1,3,5) veya bir adım değeri (*/15). Bazı uygulamalar saniyeler için altıncı bir alan ekler; diğerleri (GitHub Actions gibi) tam olarak beş alan bekler. Karmaşık ifadeler yazmadan önce her zaman zamanlayıcınızın hangi lehçeyi kullandığını kontrol edin.

Desen 1: Her 15 Dakikada Bir — */15 * * * *

*/15  *  *  *  *
  |   |  |  |  |
  |   |  |  |  +---- haftanın günü (0-6, 0=Pazar)
  |   |  |  +------- ay (1-12)
  |   |  +---------- ayın günü (1-31)
  |   +------------- saat (0-23)
  +----------------- dakika: her 15'inci (0, 15, 30, 45)

Kuyruk işleyicileri, heartbeat kontrolleri ve sık ama sürekli olmayan şekilde çalışması gereken yoklama işleri için başvurulan ifadedir. Her saatin :00, :15, :30 ve :45'inde, her gün tetiklenir.

Bir crontab'da:

# crontab girdisi — toplu işleyiciyi her 15 dakikada bir çalıştır
*/15 * * * * /opt/app/bin/process-queue.sh >> /var/log/queue.log 2>&1

Node.js'te:

// node-cron ile Node.js
import cron from 'node-cron';

cron.schedule('*/15 * * * *', async () => {
  const batch = await db.query(
    'SELECT * FROM jobs WHERE status = $1 LIMIT 100',
    ['pending']
  );
  await processBatch(batch);
});

Thundering herd sorunu. Aynı */15 ifadesiyle yüzlerce işçi çalıştırdığınızda, hepsi :00, :15, :30 ve :45'te aynı anda tetiklenir. Her işçi paylaşılan bir veritabanına vurursa, akıcı bir yük yerine saatte dört zirve elde edersiniz. Çözüm, her çalıştırmadan önce rastgele titreşim eklemektir:

// Dağıtık sistemlerde thundering herd'i önlemek için titreşim ekle
cron.schedule('*/15 * * * *', async () => {
  const jitterMs = Math.floor(Math.random() * 30_000); // 30s'ye kadar
  await sleep(jitterMs);
  await processBatch();
});

30 saniyeye kadar titreşim, işi beklenen 15 dakikalık döngü içinde tutarken zirveyi 30 saniyelik bir pencereye yayar. Daha gelişmiş kademelendirme için Desen 5'e (Jenkins H sözdizimi) bakın.

İş zamanlamasında hata ayıklarken bir sonraki planlanmış çalıştırmanızı Unix epoch'tan okunabilir bir zamana çevirmek için Timestamp Converter'ı kullanın.

Desen 2: Hafta İçi Mesai Saatleri — 0 9 * * 1-5

0   9   *   *   1-5
|   |   |   |   |
|   |   |   |   +-- haftanın günü: Pazartesi–Cuma (1-5)
|   |   |   +------ ay: her ay
|   |   +---------- ayın günü: her gün
|   +-------------- saat: 9
+------------------ dakika: 0 (tam saatte)

Bu ifade, Pazartesi'den Cuma'ya kadar saat 09:00'da tam olarak bir kez tetiklenir. Şunlar için standart desendir: günlük özet e-postaları gönderme, mesai saatleri raporları çalıştırma, iş günü boyunca insan incelemesi gerektiren iş akışlarını tetikleme ve mühendislik gününün başında staging'e dağıtım yapma.

Bir Kubernetes CronJob'da:

# Kubernetes CronJob — UTC saatiyle 09:00'da hafta içi özet e-postası
apiVersion: batch/v1
kind: CronJob
metadata:
  name: digest-email
spec:
  schedule: "0 9 * * 1-5"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: mailer
            image: myapp/mailer:latest
            command: ["/app/send-digest"]
          restartPolicy: OnFailure

Saat dilimi tuzağı. Kubernetes CronJob zamanlamaları küme saat diliminde çalışır; bu varsayılan olarak UTC'dir. "UTC 09:00", kışın Doğu saatiyle 05:00 ve Pasifik saatiyle 04:00'tür. Kullanıcılarınız "sabah özetini" gecenin ortasında alacaktır. IANA saat dilimi dizgilerini kabul eden bir cron kütüphanesi kullanın:

// Saat dilimini yönet — "UTC 09:00" nadiren kullanıcıların beklediği zamandır
// IANA saat dilimlerini anlayan bir kütüphane kullanın
import { CronJob } from 'cron';

const job = new CronJob(
  '0 9 * * 1-5',
  () => sendDailyDigest(),
  null,
  true,
  'America/New_York' // UTC 09:00 değil, Doğu Saatiyle 09:00'da çalışır
);

Timezone Converter, herhangi bir IANA saat dilimindeki belirli bir yerel saatin UTC eşdeğerini bulmanıza yardımcı olur.

Haftanın günü numaralandırması. Standart cron tanımı, Pazar için 0 veya 7 kullanır. Bazı uygulamalar (Quartz, cron4j), Pazartesi için 0 yerine 1 kullanır. Zamanlayıcınızın belgelerini kontrol edin ve dağıtmadan önce Cron Parser ile test edin.

Desen 3: Gece Toplu İşi — 0 22 * * *

0   22   *   *   *
|    |   |   |   |
|    |   |   |   +-- haftanın günü: herhangi
|    |   |   +------ ay: herhangi
|    |   +---------- ayın günü: herhangi
|    +-------------- saat: 22 (22:00)
+------------------- dakika: 0

Günde bir kez saat 22:00'da çalışır. Gece toplu işi, veri mühendisliğindeki en yaygın desenlerden biridir: günün olaylarını birleştirme, arama indekslerini yeniden oluşturma, raporlar üretme, logları arşivleme, gün sonu bildirimleri gönderme.

Gece yarısı yerine neden 22:00? İki neden. Birincisi, 22:00 gün dönmeden önce bir tampon sağlar ve geç gelen olayları toplama olasılığını azaltır. İkincisi, gece yarısı ağır bir iş çalıştırmak, her ayın ilkinde ay sonu faturalama (Desen 4) ile rekabet ettiği anlamına gelir.

Günlük bir iş yazmanın en yaygın üç yolu:

# "Geceleri 22:00'da" yazmanın üç yolu:
0 22 * * *    # standart cron
@daily        # gece yarısı — 22:00 ile AYNI DEĞİL
0 22 * * *    # gece yarısı olmayan zamanlar için her zaman @makrolardan çok açık zamanı tercih edin

Örtüşme önleme. Normalde 45 dakika süren bir gece işi ara sıra 2 saat sürebilir — ve bir sonraki gecenin tetikleyicisi tetiklendiğinde hâlâ çalışıyorsa, her iki iş de aynı veriyi değiştirecektir. Dağıtık bir kilit kullanın:

// İdempotans kontrolü — iş örtüştüğünde çift işlemeyi önle
async function runNightlyBatch() {
  const lock = await redis.set(
    'nightly-batch-lock',
    process.pid,
    'EX', 3600,   // TTL: 1 saat
    'NX'          // yalnızca yoksa ayarla
  );

  if (!lock) {
    console.log('Toplu iş zaten çalışıyor, atlanıyor.');
    return;
  }

  try {
    await processNightlyData();
  } finally {
    await redis.del('nightly-batch-lock');
  }
}

Redis NX bayrağı, bir defada yalnızca bir sürecin kilidi tutabilmesini sağlar. TTL, çöken bir işin kilidi sonsuza kadar tutmasını engeller.

Desen 4: Aylık Faturalama — 0 0 1 * *

0   0   1   *   *
|   |   |   |   |
|   |   |   |   +-- haftanın günü: herhangi
|   |   |   +------ ay: herhangi
|   |   +---------- ayın günü: 1 (ilk)
|   +-------------- saat: 0 (gece yarısı)
+------------------ dakika: 0

Her ayın ilk gününde gece yarısı tetiklenir. Şunlar için klasik desen: aylık faturalar oluşturma, kullanım kotalarını sıfırlama, ay sonu mutabakatı çalıştırma ve önceki ayın verilerini arşivleme.

Aylık işler için cron tuzakları:

# Ayın ilki UTC gece yarısı
0 0 1 * *

# ---- KAÇINILMASI GEREKEN TUZAKLAR ----

# Bu, güvenilir bir şekilde "her ay" çalışmaz:
0 0 29-31 * *   # daha az günü olan aylar sessizce atlanır

# Çeyrek faturalama (Oca, Nis, Tem, Eki):
0 0 1 1,4,7,10 *

# Ayın sonu zordur — standart cron'da "son gün" yoktur
# @reboot + uygulama mantığı veya günlük çalışan ve
# bugünün ayın son günü olup olmadığını kodda kontrol eden bir cron kullanın.

Ay sonu faturalama. İlk gün yerine her ayın son günü faturalandırmanız gerekiyorsa, bunun için temiz bir cron ifadesi yoktur. Geçici çözüm:

// Uygulama düzeyinde "ayın son günü" kontrolü
cron.schedule('0 23 28-31 * *', async () => {
  const today = new Date();
  const tomorrow = new Date(today);
  tomorrow.setDate(today.getDate() + 1);

  // Yarın 1'iyse, bugün ayın son günüdür
  if (tomorrow.getDate() === 1) {
    await runEndOfMonthBilling();
  }
});

Bu, her ayın 28–31. günlerinde tetiklenir, ardından kodda bugünün gerçekten ayın son günü olup olmadığını kontrol eder. Gereksiz yere dört kez çalışır (31'den daha az günü olan aylarda), ancak kontrol ucuz ve mantık nettir.

Çeyrek başı faturalama için ay alanında liste işlecini kullanın: 0 0 1 1,4,7,10 * (Ocak, Nisan, Temmuz, Ekim).

Desen 5: Karmalı Zamanlama (Jenkins H Sözdizimi) — H/30 * * * *

H/30 * * * *
|
+--- H, Jenkins'in iş başına rastgele bir dakika seçtiği anlamına gelir.
     H/30 = her 30 dakikada bir, H'den başlayarak.
     Farklı işler farklı H değerleri alır ve yükü dağıtır.

H belirteci, cron sözdizimine özgü bir Jenkins uzantısıdır. H'yi, iş adından türetilen kararlı bir sözde rastgele sayıyla değiştirir. Dakika alanındaki H/30, "her 30 dakikada bir, iş adının karması tarafından belirlenen bir dakikadan başlayarak" anlamına gelir.

Bir Jenkinsfile'da:

// Jenkinsfile — her 30 dakikada bir tetiklenen, kademeli ardışık düzen
pipeline {
  triggers {
    cron('H/30 * * * *')
  }
  stages {
    stage('Deploy') {
      steps {
        sh './deploy.sh'
      }
    }
  }
}

Bunun neden önemli olduğu. Jenkins kurulumlarının genellikle yüzlerce ardışık düzeni vardır. Her ardışık düzen */30 * * * * kullanırsa, hepsi :00 ve :30'da tetiklenir ve aracı havuzunda ve aşağı akış sistemlerinde büyük yük artışları yaratır. H/30 ile her iş farklı bir ofset alır — biri :03/:33'te, diğeri :17/:47'de — yükü saat boyunca eşit olarak dağıtır.

GitHub Actions H'yi desteklemez. Birden çok Actions iş akışını kademelendirmek için manuel olarak farklı sabit dakika ofsetleri seçin:

// GitHub Actions — H desteklenmez; manuel olarak ofsetle schedule kullanın
// İki işi kademelendirmek için: farklı sabit dakikalar seçin
name: Job A
on:
  schedule:
    - cron: '5 * * * *'   # her saatin :05'inde çalışır

---
name: Job B
on:
  schedule:
    - cron: '35 * * * *'  # :35'te çalışır — Job A'dan 30 dakika sonra

Bu, Jenkins H'den daha az zariftir ancak az sayıda iş akışı için aynı etkiyi sağlar. Çok sayıda iş akışı için, dağıtık zamanlamayı doğal olarak destekleyen harici bir orkestratör düşünün.

Çapraz Kesen Konular

İdempotans Tartışmaya Açık Değildir

Her cron işi iki kez çalıştırılmaya güvenli olmalıdır. Altyapı arızalanır, zamanlayıcılar yeniden başlar ve dağıtık kilitler ara sıra zaman aşımına uğrar. İşleri, aynı girdilerle aynı zamanda ikinci kez çalıştırmanın aynı sonucu üretecek şekilde tasarlayın. Insert yerine upsert kullanın, kontrol-eylem desenleri kullanın ve harici API çağrıları için idempotans anahtarları kullanın.

Ölü Mektup Uyarısı

Sessizce başarısız olan bir cron işi, hiç çalışmayandan daha kötüdür. Bir iş başarısız olduğunda veya beklenen zaman penceresinde tamamlanmadığında bir ölü mektup kuyruğuna veya izleme sistemine uyarı gönderin. Sentry Crons, Healthchecks.io ve Cronitor gibi araçlar bir heartbeat kontrolü ekler: iş başladığında ve bittiğinde bir URL'ye ping atar. Bitiş ping'i son tarih içinde gelmezse, bir uyarı tetiklenir.

Zamanlamayı Logla

Her cron çalıştırmasının başında ve sonunda yapılandırılmış bir log satırı yayınlayın; ifadeyi, tetiklenme zaman damgasını (titreşim nedeniyle biraz farklı olabilen mevcut zamanı değil) ve süreyi içerin. Bu, bir aşağı akış olayını — "veritabanı 22:00'da yavaşladı" — belirli bir toplu iş çalıştırmasıyla ilişkilendirmeyi kolaylaştırır.

Hızlı Referans

Herhangi bir ifadeyi Cron Parser ile ayrıştırın ve doğrulayın. Planlanan zamanları saat dilimleri arasında Timezone Converter ile dönüştürün. İşiniz Unix zaman damgaları logluyorsa, Timestamp Converter bunları anında okunabilir tarihlere dönüştürür.

Cron sözdizimi ve yaygın örneklerin kapsamlı bir referansı için, crontab.guru kanonik hızlı referanstır. Jenkins H sözdizimi ve tam şartnamesi için, Jenkins Pipeline cron sözdizimi belgelerine bakın.