Cron derdi olmadan arka plan işleri zamanlama: desenler
Hatırlatıcılar, günlük özetler ve temizlik işlerini güvenilir şekilde çalıştırmak için iş akışları ve bir işler tablosu kullanarak zamanlama kalıplarını öğrenin.

Cron ilk bakışta basit görünür, ta ki sorun çıkana kadar
Cron ilk gün harikadır: bir satır yazarsınız, zamanı seçersiniz, unutursunuz. Tek bir sunucu ve tek bir görev için genellikle işe yarar.
Gerçek ürün davranışı için zamanlamaya güvenildiğinde sorunlar ortaya çıkar: hatırlatmalar, günlük özetler, temizlik veya senkronizasyon işleri. Çoğu “kaçırılan çalışma” hikâyesi cron'un başarısız olması değildir. Çevresindeki her şeydir: sunucunun yeniden başlaması, deploy'un crontab'i üzerine yazması, görevin beklenenden uzun çalışması veya saat/zaman dilimi uyuşmazlığı. Birden fazla uygulama örneği çalıştırdığınızda ters bir hata moduyla da karşılaşabilirsiniz: iki makine aynı görevi çalıştıracağını düşünüp çoğaltmalara yol açar.
Test etme de ayrı bir zayıf nokta. Bir cron satırı “yarın 09:00'da ne olur”u tekrar edilebilir bir testte çalıştırmanın temiz bir yolunu vermez. Bu yüzden zamanlama manuel kontrollere, üretim sürprizlerine ve log avına dönüşür.
Bir yaklaşıma karar vermeden önce neyi zamanladığınız konusunda net olun. Çoğu arka plan işi birkaç kategoriye girer:
- Hatırlatmalar (belirli bir zamanda, yalnızca bir kez gönderilir)
- Günlük özetler (veriyi topla, sonra gönder)
- Temizlik görevleri (silme, arşivleme, süresi dolanları kaldırma)
- Periyodik senkronizasyonlar (güncelleme çekme veya gönderme)
Bazen zamanlamayı tamamen atlayabilirsiniz. Bir şey bir olayla aynı anda gerçekleşebiliyorsa (kullanıcı kaydoldu, ödeme başarılı oldu, bir bilet durumu değişti), olay odaklı iş zamanı bazlı olandan genellikle daha basit ve güvenilir olur.
Zamana gerçekten ihtiyacınız olduğunda, güvenilirlik çoğunlukla görünürlük ve kontrolde toplanır. Ne çalıştırılması gerektiğini, neyin çalıştığını ve neyin başarısız olduğunu kaydedeceğiniz bir yere, ayrıca çoğaltma yaratmadan güvenli bir şekilde yeniden deneme yöntemine ihtiyacınız var.
Temel desen: zamanlayıcı, işler tablosu, işçi
Cron baş ağrılarını önlemenin basit yollarından biri sorumlulukları ayırmaktır:
- Bir zamanlayıcı neyin ne zaman çalışacağını belirler.
- Bir işçi işi yapar.
Bu rolleri ayrı tutmak iki açıdan yardımcı olur. Zamanlamayı iş mantığına dokunmadan değiştirebilirsiniz; iş mantığını zamanlamayı bozmadan değiştirebilirsiniz.
Bir işler tablosu gerçeğin kaynağı olur. Durumu bir sunucu sürecinin veya bir cron satırının içinde gizlemek yerine, her iş bir satırdır: ne yapılacak, kime, ne zaman çalışacak ve son seferde ne oldu. Bir şey ters gittiğinde, tahmin etmeden inceleyebilir, yeniden deneyebilir veya iptal edebilirsiniz.
Tipik bir akış şöyledir:
- Zamanlayıcı uygun işleri tarar (örneğin
run_at \u003c= nowvestatus = queued). - Bir iş sadece bir işçi alacak şekilde sahiplenilir.
- Bir işçi iş detaylarını okur ve işlemi gerçekleştirir.
- İşçi sonucu aynı satıra yazar.
Ana fikir işi sihirli değil, devam ettirilebilir yapmak. Bir işçi yarıda çökerse, iş satırı size ne olduğunu ve sonraki adımı söylemelidir.
Yararlı kalan bir işler tablosu tasarlamak
Bir işler tablosu iki soruyu hızla cevaplamalıdır: sırada ne var ve son sefer ne oldu.
Kimlik, zamanlama ve ilerleme ile ilgili küçük bir alan setiyle başlayın:
- id, type: benzersiz bir id ve
send_reminderveyadaily_summarygibi kısa bir tip. - payload: işçinin ihtiyaç duyduğu doğrulanmış JSON (örneğin
user_id, tüm kullanıcı nesnesi değil). - run_at: işin çalışmaya uygun olduğu zaman.
- status:
queued,running,succeeded,failed,canceled. - attempts: her denemede artırılır.
Sonra eşzamanlılığı güvenli ve olayları daha kolay yönetilebilir yapan birkaç operasyonel sütun ekleyin. locked_at, locked_by ve locked_until bir işin bir işçi tarafından sahiplenilmesini sağlar, böylece aynı işi iki kez çalıştırmazsınız. last_error kısa bir mesaj (opsiyonel bir hata kodu ile) olmalı; satırları şişiren tam bir stack trace yerine kısa tutulmalı.
Son olarak, destek ve raporlama için yardımcı olacak zaman damgalarını saklayın: created_at, updated_at ve finished_at. Bunlar “Bugün kaç hatırlatma başarısız oldu?” gibi soruları loglarda gezinmeden cevaplamanızı sağlar.
Indexler önemlidir çünkü sisteminiz sürekli “sonraki ne?” diye sorar. Genellikle fayda sağlayan iki indeks:
(status, run_at)- uygun işleri hızlı bulmak için(type, status)- bir iş ailesini sorun sırasında incelemek veya duraklatmak için
Payload'lar için küçük, odaklanmış JSON tercih edin ve eklemeden önce doğrulayın. Kimlikleri ve parametreleri saklayın, iş verilerinin anlık görüntülerini değil. Payload yapısını bir API sözleşmesi gibi ele alın ki kuyruktaki eski işler uygulamanızı değiştirdiğinizde de çalışsın.
İş yaşam döngüsü: durumlar, kilitleme ve idempotentlik
Bir iş çalıştırıcısı güvenilir kaldığında her iş küçük, öngörülebilir bir yaşam döngüsünü takip eder. Bu yaşam döngüsü iki işçi aynı anda başladığında, bir sunucu ortasında yeniden başlatıldığında veya yeniden denemeden çoğaltma olmadan nasıl kurtulacağınıza dair güvenlik ağıdır.
Basit bir durum makinesi genellikle yeterlidir:
- queued:
run_atzamanından itibaren çalışmaya hazır - running: bir işçi tarafından sahiplenilmiş
- succeeded: tamamlandı ve tekrar çalıştırılmamalı
- failed: hata ile tamamlandı ve ilgi gerektiriyor
- canceled: kasıtlı olarak durduruldu (örneğin kullanıcı çıkış yaptı)
Çift işe karşı işleri sahiplenme
Çoğaltmaları önlemek için bir işi sahiplenmek atomik olmalıdır. Yaygın yaklaşım süreli bir kilittir (lease): bir işçi status=running ayarlayıp locked_by ile locked_until yazarak işi sahiplenir. İşçi çökerse, kilit süresi dolar ve başka bir işçi işi yeniden sahiplenebilir.
Pratik bir sahiplenme kural seti:
- yalnızca
run_at \u003c= nowolan queued işlerini sahiplenin status,locked_byvelocked_untilaynı güncellemede ayarlayınlocked_until \u003c nowolduğunda running işlerini yeniden sahiplenin- lease süresini kısa tutun ve iş uzun sürerse uzatın
Idempotentlik (sizi kurtaran alışkanlık)
Idempotentlik demek: aynı iş iki kez çalıştırılsa bile sonuç doğru kalır.
En basit araç benzersiz bir anahtardır. Örneğin günlük özet için kullanıcının her günü başına summary:user123:2026-01-25 gibi bir anahtarla bir iş zorunlu kılabilirsiniz. Eğer çift ekleme olursa, ikinci bir iş yerine aynı işe işaret eder.
Yan etkiler gerçekten tamamlandığında başarı işaretleyin (e-posta gönderildi, kayıt güncellendi). Yeniden denemirseniz, yeniden deneme yolu ikinci bir e-posta veya çoğaltılmış yazma yaratmamalı.
Drama olmadan yeniden denemeler ve hata yönetimi
Yeniden denemeler iş sistemlerini ya güvenilir yapar ya da gürültüye çevirir. Amaç basit: bir hata muhtemelen geçiciyse yeniden dene, değilse dur.
Varsayılan bir yeniden deneme politikası genellikle şunları içerir:
- maksimum deneme sayısı (örneğin toplam 5 deneme)
- gecikme stratejisi (sabit gecikme veya üstel geri çekilme)
- durdurma koşulları (örneğin “geçersiz giriş” türü hatalarda yeniden deneme)
- jitter (yeniden deneme yoğunluğunu önlemek için küçük rastgele ofset)
Yeni bir durum icat etmek yerine queued durumunu yeniden kullanabilirsiniz: run_at'ı bir sonraki deneme zamanına ayarlayıp işi tekrar kuyruğa sokun. Bu durum makinesini küçük tutar.
Bir iş kısmi ilerleme yapabiliyorsa, bunu normal sayın. Bir yeniden denemenin güvenli şekilde devam etmesi için checkpoint saklayın; ya iş payload'unda (last_processed_id gibi) ya da ilişkili bir tabloda.
Örnek: günlük özet işi 500 kullanıcı için mesajlar üretir. Eğer 320. kullanıcıda başarısız olursa, son başarılı kullanıcı ID'sini saklayın ve 321'den yeniden deneyin. Eğer ayrıca her kullanıcı için günlüğe summary_sent kaydı tutulursa, yeniden çalıştırma zaten yapılmış kullanıcıları atlayabilir.
Gerçekten işe yarayan loglama
Dakikalar içinde hatayı çözmek için yeterince loglayın:
- iş id'si, tipi ve deneme numarası
- ana girdiler (kullanıcı/ekip id'si, tarih aralığı)
- zamanlama (started_at, finished_at, bir sonraki çalıştırma zamanı)
- kısa hata özeti (varsa stack trace)
- yan etki sayıları (gönderilen e-postalar, güncellenen satırlar)
Adım adım: basit bir zamanlayıcı döngüsü kurun
Bir zamanlayıcı döngüsü, sabit bir ritimde uyanan, uygun işleri arayan ve devreye veren küçük bir süreçtir. Amaç sıkıcı güvenilirlik, mükemmel zamanlama değil. Birçok uygulama için “her dakika uyan” yeterlidir.
Uyanma sıklığını, işlerin ne kadar zaman duyarlı olduğuna ve veritabanınızın ne kadar yük kaldırabileceğine göre seçin. Hatırlatmalar gerçek zamanlıya yakın olmalıysa her 30–60 saniyede bir çalıştırın. Günlük özetler biraz kayabilir ise her 5 dakikada bir çalıştırmak yeterli ve daha ucuz olur.
Basit bir döngü:
- Uyanın ve geçerli zamanı alın (UTC kullanın).
status = 'queued'verun_at \u003c= nowolan uygun işleri seçin.- İşleri yalnızca bir işçi alacak şekilde güvenli biçimde sahiplenin.
- Her sahiplenilmiş işi bir işçiye verin.
- Sonraki tick'e kadar uyuyun.
Sahiplenme adımı birçok sistemin kırıldığı yerdir. Bir işi running olarak işaretlemek (ve locked_by, locked_until saklamak) ile seçmek aynı işlemde olmalı. Birçok veritabanı “skip locked” okuma desteği sağlar, böylece birden fazla zamanlayıcı birbirinin işine girmeden koşabilir.
-- concept example
BEGIN;
SELECT id FROM jobs
WHERE status='queued' AND run_at \u003c= NOW()
ORDER BY run_at
LIMIT 100
FOR UPDATE SKIP LOCKED;
UPDATE jobs
SET status='running', locked_until=NOW() + INTERVAL '5 minutes'
WHERE id IN (...);
COMMIT;
Batch boyutunu küçük tutun (örneğin 50–200). Daha büyük batch'ler veritabanını yavaşlatabilir ve çökmeler daha maliyetli olur.
Eğer zamanlayıcı batch ortasında çökse, lease sizi kurtarır. running durumda takılan işler locked_until süresi dolunca tekrar uygun hale gelir. İşçiniz idempotent olmalı ki yeniden sahiplenilen bir iş ikinci e-postayı veya çifte ücreti yaratmasın.
Hatırlatmalar, günlük özetler ve temizlik için desenler
Çoğu ekip aynı üç tür arka plan işiyle karşılaşır: zamanında gönderilmesi gereken mesajlar, zamanlanmış raporlar ve depolama/performansı sağlıklı tutan temizlik. Aynı işler tablosu ve işçi döngüsü bunların hepsini idare edebilir.
Hatırlatmalar
Hatırlatmalar için mesajı göndermek üzere gereken her şeyi iş satırında saklayın: kime, hangi kanal (e-posta, SMS, Telegram, uygulama içi), hangi şablon ve kesin gönderim zamanı. İşçi işi ekstra bağlam “etrafta bakmadan” çalıştırabilmelidir.
Birçok hatırlatma aynı anda uygunsa, hız sınırlama ekleyin. Kanal başına dakika başına mesaj sınırı koyun ve fazladan işler bir sonraki çalışmaya kalsın.
Günlük özetler
Günlük özetler zaman penceresinin belirsiz olduğu zaman başarısız olur. Kararlı bir kesme zamanı seçin (örneğin kullanıcının yerel saatine göre 08:00) ve pencereyi net tanımlayın (örneğin “dün 08:00 ile bugün 08:00 arası”). Yeniden çalıştırmaların aynı sonucu üretmesi için kesmeyi ve kullanıcı saat dilimini iş ile birlikte saklayın.
Her özet işini küçük tutun. Binlerce kaydı işlemesi gerekiyorsa, parçalara bölün (ekip başına, hesap başına veya ID aralığına göre) ve takip işleri sıraya koyun.
Temizlik işleri
Temizlikte “silme”yi “arşivleme”den ayırmak daha güvenlidir. Hangi şeylerin kalıcı olarak kaldırılabileceğine (geçici tokenlar, süresi dolmuş oturumlar) ve hangilerinin arşivlenmesi gerektiğine (denetim logları, faturalar) karar verin. Uzun kilitleri ve ani yük artışlarını önlemek için temizlik işlemlerini öngörülebilir partiler halinde çalıştırın.
Zaman ve saat dilimleri: hataların gizli kaynağı
Birçok hata zamanla alakalıdır: hatırlatma bir saat erken gider, günlük özet Pazartesi'yi atlar veya temizlik iki kez çalışır.
İyi bir varsayılan, planlama zaman damgalarını UTC olarak saklamak ve kullanıcı saat dilimini ayrı saklamaktır. run_at tek bir UTC anı olmalıdır. Kullanıcı “sabah 09:00 benim saatimde” dediğinde, zamanlamayı oluştururken bunu UTC'ye çevirin.
Yaz saati uygulaması (DST) çoğu amatör kurulumun bozulduğu yerdir. “Her gün 09:00” ifadesi “her 24 saatte bir” ile aynı değildir. DST değişikliklerinde 09:00 farklı UTC zamanına karşılık gelir; bazı yerel saatler hiç var olmaz (yaz saatine geçiş) veya iki kez olur (kışa dönüş). Daha güvenli yaklaşım, her yeniden planlamada bir sonraki yerel oluşumu hesaplamak ve sonra tekrar UTC'ye çevirmektir.
Günlük özet için “bir gün”ün ne anlama geldiğine kod yazmadan önce karar verin. Bir takvim günü (kullanıcının saat diliminde gece yarısından gece yarısına) insan beklentileriyle eşleşir. “Son 24 saat” daha basittir ama kayar ve insanları şaşırtır.
Geç gelen veri kaçınılmazdır: bir etkinlik bir yeniden denemeden sonra gelir veya bir not gece yarısından birkaç dakika sonra eklenir. Geç gelen olayların “düne” mi yoksa “bugüne” mi ait olduğuna karar verin (bir hoşgörü süresi ile) ve bu kuralı tutarlı uygulayın.
Pratik bir tampon kaçırmaları önleyebilir:
- son 2–5 dakika içinde uygun olan işleri de tarayın
- iş idempotent olsun ki tekrar çalıştırmalar güvenli olsun
- özetlerde kapsanan zaman aralığını payload'ta kaydedin ki tutarlılık bozulmasın
Kaçırılan veya çoğaltılmış çalışmalara yol açan yaygın hatalar
Çoğu acı birkaçı öngörülebilir varsayımdan gelir.
En büyük olanı “tam olarak bir kez” çalıştırılacağını varsaymaktır. Gerçek sistemlerde işçiler yeniden başlar, ağ çağrıları zaman aşımına uğrar ve kilitler kaybolabilir. Genelde “en az bir kez” teslim edersiniz; bu da çoğaltmaların normal olduğu anlamına gelir ve kodunuzun bunları tolere etmesi gerekir.
Bir diğer hata, etkileri önce yapmaktır (e-posta gönder, kartı tahsil et) ve sonra dedupe kontrolü eklememektir. Basit bir koruma genellikle bunu çözer: bir sent_at zaman damgası, (user_id, reminder_type, date) gibi benzersiz bir anahtar veya saklanmış bir dedupe tokenı.
Görünürlük bir diğer eksiklik. “Ne takılı kaldı, ne zamandır ve neden” sorusunu cevaplayamıyorsanız tahminlerle uğraşmak zorunda kalırsınız. Yakında tutulacak asgari veriler: durum, deneme sayısı, bir sonraki planlanan zaman, son hata ve işçi id'si.
En sık görülen hatalar:
- işleri tam bir kez çalışacakmış gibi tasarlayıp çoğaltmalara şaşırmak
- yan etkileri dedupe kontrolü olmadan yapmak
- her şeyi yapmaya çalışan tek büyük bir iş yazıp zaman aşımına uğramak
- sınırı olmayan sürekli yeniden denemeler
- temel kuyruk görünürlüğünü atlamak (kuyruk, hatalar, uzun süreli işler için net bir görünüm yok)
Somut bir örnek: bir günlük özet işi 50.000 kullanıcı üzerinde döner ve 20.000. kullanıcıda zaman aşımına uğrar. Yeniden denemede işi baştan başlatırsa ilk 20.000 kullanıcıya özetleri tekrar gönderir, ta ki kullanıcı bazlı tamamlanma takibi veya işleri parçalara ayırana kadar.
Güvenilir bir iş sisteminin hızlı kontrol listesi
Bir iş çalıştırıcısı ancak gece 02:00'de ona güvenebildiğinizde “tamamlanmış” sayılır.
Emin olun ki:
- Kuyruk görünürlüğü: queued, running ve failed için sayımlar ve en eski queued iş.
- Varsayılan idempotentlik: her işin iki kez çalışabileceğini varsayın; benzersiz anahtarlar veya “zaten işlendi” işaretleri kullanın.
- İş tipi başına yeniden deneme politikası: yeniden denemeler, backoff ve net durma koşulları.
- Tutarlı zaman saklama:
run_atUTC'de saklanmalı; yalnızca girişte ve gösterimde çevirin. - Kurtarılabilir kilitler: çökme durumunda işleri sonsuza kadar çalışan bırakmayacak lease.
Ayrıca batch boyutu (aynı anda kaç iş sahiplenirsiniz) ve işçi eşzamanlılığı (aynı anda kaç iş çalıştırılır) sınırlandırın. Sınır yoksa bir ani yük veritabanınızı aşırı yükleyebilir veya diğer işlerin aç kalmasına neden olabilir.
Gerçekçi bir örnek: küçük bir ekip için hatırlatmalar ve özetler
Küçük bir SaaS aracı 30 müşteri hesabına sahip. Her hesap iki şeye ihtiyaç duyuyor: açık görevler için sabah 09:00 hatırlatması ve gün içinde ne değiştiğine dair akşam 18:00 günlük özeti. Ayrıca veritabanının eski loglar ve süresi dolmuş tokenlarla dolmaması için haftalık temizlik gerekiyor.
Onlar bir işler tablosu ve uygun işleri sorgulayan bir işçi kullanıyor. Yeni bir müşteri kaydolduğunda backend, müşterinin saat dilimine göre ilk hatırlatma ve özet çalıştırmalarını zamanlar.
İşler birkaç yaygın anda oluşturulur: kayıt esnasında (tekrarlayan takvimler oluşturma), belirli olaylarda (tek seferlik bildirim ekleme), zamanlama tick'inde (gelecek çalışmaları ekleme) ve bakım gününde (temizlik ekleme).
Bir Salı günü e-posta sağlayıcısı 08:59'da geçici bir kesinti yaşar. İşçi hatırlatmaları göndermeye çalışır, zaman aşımı alır ve bu işleri backoff kullanarak (örneğin 2 dakika, sonra 10, sonra 30) yeniden zamanlar, her defasında attempts artırılır. Her hatırlatma işinin account_id + date + job_type gibi bir idempotentlik anahtarı olduğu için sağlayıcı ortada iyileşse bile tekrarlar çoğaltma yaratmaz.
Temizlik işlerini tek seferde milyonlarca satırı silmek yerine küçük partilerle haftalık çalıştırırlar; her koşuda N satır siler ve bitene kadar kendini yeniden zamanlar.
Bir müşteri “Özetimi almadım” diye şikayet ettiğinde, ekip o hesap ve gün için işler tablosunu kontrol eder: iş durumu, deneme sayısı, cari kilit alanları ve sağlayıcının döndürdüğü son hata. Bu “gönderilmiş olmalı”yı “işte tam olarak ne oldu”ya çevirir.
Sonraki adımlar: uygulayın, gözlemleyin, sonra ölçekleyin
Bir iş tipini seçin ve uçtan uca önce onu kurun. Tek bir hatırlatma işi iyi bir başlangıçtır çünkü her şeye değinir: zamanlama, uygun işi sahiplenme, mesaj gönderme ve sonuç kaydetme.
Güvenebileceğiniz bir sürüm ile başlayın:
- işler tablosunu oluşturun ve bir iş tipini işleyen bir işçi yazın
- uygun işleri sahiplenen bir zamanlayıcı döngüsü ekleyin
- işi ekstra tahmin gerektirmeyecek kadar payload saklayın
- her denemeyi ve sonucu loglayın ki “Çalıştı mı?” sorusu 10 saniyede cevaplansın
- başarısız işler için manuel yeniden çalıştırma yolu ekleyin ki kurtarma deploy gerektirmesin
Çalıştıktan sonra insanlara gözlemlenebilir hale getirin. Basit bir yönetici arayüzü bile hızlıca karşılığını verir: durumla arama, zamana göre filtreleme, payload inceleme, takılı işi iptal etme, belirli bir iş id'sini yeniden çalıştırma.
Eğer bu tür bir zamanlayıcı ve işçi akışını görsel backend mantığı ile inşa etmeyi tercih ediyorsanız, AppMaster (appmaster.io) PostgreSQL'de işler tablosunu modelleyebilir ve claim-process-update döngüsünü Business Process olarak uygulayabilir; ayrıca dağıtıma hazır gerçek kaynak kodu üretebilir.


