7 dk

Cron + veritabanı deseni: kuyruk olmadan zamanlanmış arka plan görevleri

Cron + veritabanı desenini öğrenin: tekrar denemeler, kilitleme ve idempotans ile zamanlanmış arka plan görevlerini tam bir kuyruk sistemi kurmadan çalıştırın.

Cron + veritabanı deseni: kuyruk olmadan zamanlanmış arka plan görevleri

Sorun: ekstra altyapı olmadan zamanlanmış işler

Çoğu uygulamanın daha sonra yapılması ya da belirli aralıklarla çalışması gereken işler olur: takip e-postaları göndermek, gece faturalama kontrolü yapmak, eski kayıtları temizlemek, bir raporu yeniden oluşturmak veya önbelleği yenilemek.

Erken aşamada, arka plan işleri için tam bir kuyruk sistemi eklemek cazip gelebilir çünkü "doğru" yol gibi görünür. Ama kuyruklar ekstra parça getirir: çalıştırılacak, izlenecek, dağıtılacak ve hata ayıklanacak başka bir servis. Küçük bir ekip (ya da tek kurucu) için bu ekstra yük işi yavaşlatabilir.

Gerçek soru şu: daha fazla altyapı kurmadan zamanlanmış işleri güvenilir şekilde nasıl çalıştırırsınız?

İlk denemeler genellikle basittir: bir cron girdisi bir uç noktayı tetikler ve o uç nokta işi yapar. Bu işe yarar, ta ki yaramaz hale gelene kadar. Birden fazla sunucunuz olduğunda, yanlış zamanda yapılan bir deploy veya beklenenden uzun süren bir iş olduğunda kafa karıştırıcı hatalar görmeye başlarsınız.

Zamanlanmış işler genellikle birkaç öngörülebilir şekilde bozulur:

  • Çift çalıştırma: iki sunucu aynı görevi çalıştırır; faturalar iki kez oluşturulur ya da e-postalar çift gönderilir.
  • Kayıp çalıştırmalar: deploy sırasında cron çağrısı başarısız olur ve kimse fark etmez.
  • Sessiz hatalar: iş bir kere hata verir ve tekrar çalışmaz çünkü yeniden deneme planı yoktur.
  • Kısmi işler: iş yarıda çöker ve veriler garip bir durumda kalır.
  • Denetim izi yok: "en son ne zaman çalıştı?" veya "dün gece ne oldu?" gibi soruları cevaplayamazsınız.

Cron + veritabanı deseni orta bir yol sunar. Hala cron'u bir zamanlayıcı olarak kullanırsınız, ancak iş niyeti ve iş durumu veritabanında saklanır, böylece sistem koordinasyon, yeniden deneme ve ne olduğunun kaydını tutabilir.

Bu, zaten bir veritabanınız (çoğu zaman PostgreSQL), az sayıda iş türü ve minimum işletme işiyle öngörülebilir davranış istediğinizde iyi bir uyum sağlar. Ayrıca modern yığınlarda (örneğin React + Go + PostgreSQL) hızla kurulan uygulamalar için doğal bir tercihtir.

Çok yüksek throughput, ilerleme akışı gerektiren uzun süreli işler, birçok iş türü arasında sıkı sıralama veya yoğun fan-out (dakikada binlerce alt görev) gerektiğinde uygun değildir. Bu durumlarda gerçek bir kuyruğa ve adanmış worker'lara yatırım genellikle kendini öder.

Temel fikir, sadece

Cron + veritabanı deseni, tam bir kuyruk sistemi çalıştırmadan zamanlanmış arka plan işlerini yürütür. Hala cron (veya başka bir zamanlayıcı) kullanırsınız, ama cron neyi çalıştıracağını belirlemez. Sadece sık sık (dakikada bir yaygın) bir worker'ı uyandırır. Veritabanı hangi işin vaktinin geldiğini belirler ve her işin sadece bir worker tarafından alınmasını sağlar.

Bunu ortak bir beyaz tahtadaki paylaşılan bir kontrol listesi gibi düşünün. Cron her dakika odaya girip "Yapacak bir şeyi olan var mı?" diye soran kişi. Veritabanı ise neyin vaktinin geldiğini, hangisinin alındığını ve hangisinin yapıldığını gösteren beyaz tahta.

Parçalar basittir:

  • Tek bir zamanlayıcı tetikleyici sık çalışır.
  • Bir jobs tablosu "ne" ve "ne zaman"ı (çalışma zamanı), durum ve deneme sayısını tutar.
  • Bir veya daha fazla worker tabloyu tarar, bir işi talep eder ve işi yapar.
  • Talep etme, iki worker'ın aynı satırı kaptığı durumu önlemek için veritabanı kilidi ile yapılır.
  • Veritabanı, neyin çalıştığının, neyin başarısız olduğunun ve neyin yeniden deneneceğinin tek kaynağı olur.

Örnek: her sabah fatura hatırlatmaları göndermek, önbelleği her 10 dakikada bir yenilemek ve gece eski oturumları temizlemek istiyorsunuz. Üç ayrı cron komutu yerine (her birinin kendi çakışma ve hata modlarıyla), iş girdilerini tek bir yerde saklarsınız. Cron aynı worker sürecini başlatır. Worker Postgres'e "Şu anda ne vaktinde?" diye sorar ve Postgres çalışanın güvenli şekilde tam olarak bir işi almasına izin verir.

Bu yöntem kademeli olarak ölçeklenir. Bir sunucuda bir worker ile başlayabilirsiniz. Daha sonra birden fazla sunucuda beş worker çalıştırabilirsiniz. Anlaşma aynıdır: tablo sözleşmedir.

Zihniyet değişikliği basittir: cron sadece uyandırma çağrısıdır. Veritabanı trafiği yöneten görevli gibidir — ne çalıştırılabileceğine karar verir, ne olduğunu kaydeder ve bir şey ters gittiğinde size net bir geçmiş verir.

jobs tablosunu tasarlamak (pratik bir şema)

Bu desen en iyi, veritabanınızın neyin çalışması gerektiğinin, ne zaman çalışacağını ve son seferde ne olduğunu kaydettiği durumda işler. Şema süslü değildir, ama küçük detaylar (kilit alanları ve doğru indeksler) yük arttıkça büyük fark yaratır.

Tek tablo mu, iki tablo mu?

İki yaygın yaklaşım:

  • Tek birleşik tablo: sadece her işin en son durumunu önemsiyorsanız (basit, az join).
  • İki tablo: "bu iş nedir" ile "her çalıştığında ne oldu" arasında net ayrım isterseniz (daha iyi geçmiş, hata ayıklama kolaylığı).

Hataları sık sık debug etmeniz beklentisi varsa geçmiş tutun. En küçük kurulumu istiyorsanız tek tabloyla başlayıp sonra geçmiş ekleyin.

Pratik bir şema (iki tablo versiyonu)

Aşağıda PostgreSQL dostu bir düzen var. Go ile PostgreSQL inşa ediyorsanız bu sütunlar struct'lara temizce eşleşir.

-- What should exist (the definition)
create table job_definitions (
  id            bigserial primary key,
  job_type      text not null,
  payload       jsonb not null default '{}'::jsonb,
  schedule      text,                      -- optional: cron-like text if you store it
  max_attempts  int not null default 5,
  created_at    timestamptz not null default now(),
  updated_at    timestamptz not null default now()
);

-- What should run (each run / attempt group)
create table job_runs (
  id            bigserial primary key,
  definition_id bigint references job_definitions(id),
  job_type      text not null,
  payload       jsonb not null default '{}'::jsonb,
  run_at        timestamptz not null,
  status        text not null,             -- queued | running | succeeded | failed | dead
  attempts      int not null default 0,
  max_attempts  int not null default 5,

  locked_by     text,
  locked_until  timestamptz,

  last_error    text,
  created_at    timestamptz not null default now(),
  updated_at    timestamptz not null default now()
);

Birkaç detay ileride sizi kurtarır:

  • job_type kısa bir string olsun (örneğin send_invoice_emails) ve ona göre yönlendirme yapın.
  • payloadjsonb olarak saklayın, böylece migration yapmadan evriltebilirsiniz.
  • run_at sizin "bir sonraki vade zamanı"nızdır. Cron (veya zamanlayıcı betik) bunu ayarlar, worker'lar tüketir.
  • locked_by ve locked_until worker'ların işleri birbirinin üstüne binmeden talep etmesine izin verir.
  • last_error kısa ve insanın okuyabileceği bir mesaj olsun. Stack trace'leri ihtiyaç varsa başka yerde tutun.

İhtiyacınız olacak indeksler

İndeks yoksa worker'lar çok fazla tarama yapmak zorunda kalır. Şunlarla başlayın:

  • Vakit gelmiş işleri hızlı bulmak için bir indeks: (status, run_at)
  • Süresi dolmuş kilitleri tespit etmeye yardımcı indeks: (locked_until)
  • Opsiyonel: sadece aktif işler için kısmi indeks (örneğin status queued ve failed içinde)

Bunlar, tablo büyüdüğünde bile "sonraki çalıştırılabilir işi bul" sorgusunu hızlı tutar.

İşleri güvenli şekilde kilitleme ve alma

Amaç basit: birçok worker çalışabilir, ama sadece biri belirli bir işi almalı. Eğer iki worker aynı satırı işliyorsa çift e-postalar, çift ücretleme veya karmaşık verilerle karşılaşırsınız.

Güvenli bir yaklaşım, iş talebini bir "kira (lease)" gibi görmektir. Worker işi kısa bir süreliğine kilitler. Worker çökerse kira süresi biter ve başka bir worker işi alabilir. locked_until bunun için vardır.

Çöküşler işin sonsuza dek bloklanmaması için kiralamayı kullanın

Kiralamadan yoksun olunca, worker işi kilitlediği halde asla açamayabilir (proses öldü, sunucu yeniden başlatıldı, deploy hatalı). locked_until sayesinde zaman geçince iş tekrar alınabilir.

Tipik kural: bir iş locked_until NULL ise ya da locked_until <= now() ise alınabilir.

İşleri tek atomik güncellemede talep edin

Önemli detay, işi tek bir ifadeyle (veya tek transaction içinde) talep etmektir. Veritabanının hakem olmasını istersiniz.

Aşağıda PostgreSQL için yaygın bir desen: bir vadesi gelmiş işi seç, kilitle ve worker'a döndür. (Bu örnek tek jobs tablosu kullanıyor; aynı fikir job_runs için de geçerli.)

WITH next_job AS (
  SELECT id
  FROM jobs
  WHERE status = 'queued'
    AND run_at \u003c= now()
    AND (locked_until IS NULL OR locked_until \u003c= now())
  ORDER BY run_at ASC
  LIMIT 1
  FOR UPDATE SKIP LOCKED
)
UPDATE jobs j
SET status = 'running',
    locked_until = now() + interval '2 minutes',
    locked_by = $1,
    attempts = attempts + 1,
    updated_at = now()
FROM next_job
WHERE j.id = next_job.id
RETURNING j.*;

Neden işe yarıyor:

  • FOR UPDATE SKIP LOCKED birden fazla worker'ın yarışmasını sağlar ama birbirlerini bloklamazlar.
  • Kira talep anında ayarlanır, böylece diğer worker'lar süresi dolana kadar işi görmezden gelir.
  • RETURNING yarışı kazanan worker'a satırı verir.

Kira süresi ne kadar olmalı ve nasıl yenilenir?

Kirayı normal çalışma süresinden uzun, ama bir çöküşün hızlı toparlanacağı kadar kısa tutun. Çoğu iş 10 saniyede bitiyorsa 2 dakikalık kira yeterlidir.

Uzun işler için çalışırken kirayı yenileyin (heartbeat). Basit bir yaklaşım: her 30 saniyede bir locked_until'ı uzatın eğer hala işin sahibiyse.

  • Kira süresi: tipik iş zamanınızın 5x ila 20x'i
  • Heartbeat aralığı: kiranın 1/4 ila 1/2'si
  • Yenileme güncellemesi WHERE id = $job_id AND locked_by = $worker_id içermeli

Son koşul önemlidir. Bir worker artık sahibi olmadığı bir işin kirasını uzatmasını engeller.

Öngörülebilir davranan yeniden denemeler ve geri çekilme

Go and Postgres starter app
Start from a React front end and Go plus PostgreSQL backend built from your spec.

Yeniden denemeler, bu desenin ya sakin hissettirdiği ya da gürültülü bir karmaşaya dönüştüğü yerdir. Amaç basit: bir iş başarısız olduğunda, daha sonra tekrar deneyin; bunu açıklayabilir, ölçebilir ve durdurabilirsiniz.

İlk olarak iş durumunu açık ve sonlu yapın: queued, running, succeeded, failed, dead. Çoğu takım pratikte failedi "başarısız oldu ama yeniden denenecek" anlamında kullanır; dead ise "başarısız oldu ve vazgeçildi" demektir. Bu ayrım sonsuz döngüleri önler.

Deneme sayacı ikinci koruma katmanıdır. attempts (kaç kere denendi) ve max_attempts (kaç kere izin verildi) saklayın. Bir worker hata yakaladığında şu adımları izlemeli:

  • attempts'ı artır
  • attempts < max_attempts ise failed, değilse dead durumunu ata
  • Sonraki deneme için run_at hesapla (sadece failed için)

Geri çekilme (backoff), bir sonraki run_at'i belirleyen kuraldır. Birini seçin, belgelendirin ve tutarlı kalın:

  • Sabit gecikme: hep 1 dakika bekle
  • Üssel: 1dk, 2dk, 4dk, 8dk
  • Üssel ve üst sınır: üssel ama örneğin en fazla 30dk
  • Jitter ekle: işler aynı saniyede yeniden denememesin diye biraz rastgelelik katın

Jitter, bir bağımlılık çöktüğünde ve döndüğünde önem taşır. Yoksa yüzlerce iş aynı anda yeniden dener ve tekrar başarısız olur.

Hataları görünür ve hata ayıklamaya uygun tutacak kadar bilgi saklayın. Tam bir log sistemi gerekmez ama temel bilgiler olmalı:

  • last_error (kısa mesaj, admin ekranında güvenle gösterilebilir)
  • error_code veya error_type (benzer hataları gruplayabilmek için)
  • failed_at ve next_run_at
  • opsiyonel last_stack (boyutu kontrol ediliyorsa)

İyi çalışan somut bir kural: işleri 10 denemeden sonra dead yapın ve üssel geri çekilme ile jitter uygulayın. Bu, geçici hataların tekrar denenmesini sağlar ama bozuk işleri sonsuza kadar CPU yakmaya bırakmaz.

İdempotans: bir iş tekrar çalışsa bile tekrarı önlemek

İdempotans, işiniz iki kez çalıştırılsa bile aynı sonucun üretilmesini sağlar. Bu desen için önemlidir çünkü aynı satır çöküş, zaman aşımı veya yeniden deneme nedeniyle tekrar seçilebilir. "Fatura e-postası gönder" gibi bir iş iki kez çalıştırılmak istenmeyen sonuçlar doğurur.

Pratik düşünce: her işi (1) işi yapmak ve (2) etkiyi uygulamak olarak ayırın. Etkinin bir kere olmasını istersiniz, iş kaç kez denenirse denensin.

İşle ilgili idempotans anahtarı kullanın

Idempotans anahtarı işin temsil ettiği gerçek işten gelmeli, worker denemesinden değil. İyi anahtarlar kararlı ve anlaşılırdır: invoice_id, user_id + day veya report_name + report_date. İki deneme aynı gerçek dünya olayı içinse aynı anahtarı paylaşmalıdır.

Örnek: "2026-01-14 için günlük satış raporu oluştur" anahtarı sales_report:2026-01-14 olabilir. "812 numaralı faturayı tahsil et" için invoice_charge:812.

"Sadece bir kere" kuralını veritabanı kısıtlarıyla sağlayın

En basit koruma, PostgreSQL'in tekrarları reddetmesine izin vermektir. Idempotency anahtarını indekslenebilir bir yere koyun ve benzersiz kısıtlama ekleyin.

-- Example: ensure one logical job/effect per business key
ALTER TABLE jobs
ADD COLUMN idempotency_key text;

CREATE UNIQUE INDEX jobs_idempotency_key_uniq
ON jobs (idempotency_key)
WHERE idempotency_key IS NOT NULL;

Bu, aynı anahtara sahip iki satırın aynı anda var olmasını engeller. Tasarımınız birden fazla satıra izin veriyorsa (geçmiş için), benzersizliği bir "etki" tablosunda tutun; örneğin sent_emails(idempotency_key) veya payments(idempotency_key).

Korunması gereken yaygın yan etkiler:

  • E-postalar: göndermeden önce sent_emails tablosuna benzersiz anahtarla bir satır oluşturun veya gönderildikten sonra sağlayıcı mesaj kimliğini kaydedin.
  • Webhook'lar: delivered_webhooks(event_id) kaydı tutun ve varsa atlayın.
  • Ödemeler: ödeme sağlayıcılarının idempotency özelliğini kullanın ve kendi veritabanı benzersiz anahtarınızı ekleyin.
  • Dosya yazma: geçici bir isimle yazıp sonra yeniden adlandırın veya (type, date) ile anahtelenmiş file_generated kaydı tutun.

Postgres destekli bir yığında (örneğin Go + PostgreSQL) bu benzersizlik kontrolleri hızlıdır ve veriye yakın tutulması kolaydır. Özet: yeniden denemeler normaldir, kopyalar istenmeyen.

Adım adım: minimal bir worker ve zamanlayıcı kurun

Earn credits while you build
Create content or refer teammates and earn credits to keep building on Koder.ai.

Sıkıcı ama güvenilir bir çalışma zamanı seçin ve ona bağlı kalın. Cron + veritabanı deseninin amacı daha az hareketli parça olduğundan, PostgreSQL ile konuşan küçük bir Go, Node veya Python süreci genellikle yeterlidir.

Beş küçük adımda inşa edin

  1. Tabloları ve indeksleri oluşturun. Bir jobs tablosu (ve sonra kullanmak isteyebileceğiniz lookup tabloları) ekleyin, run_at'i indeksleyin ve işçi için uygun bir hızlandırıcı indeks (örneğin (status, run_at)) ekleyin.

  2. Küçük bir enqueue fonksiyonu yazın. Uygulamanız run_at'i "now" veya geleceğe ayarlanmış bir zaman olarak eklemeli. Payload'ı küçük ve öngörülebilir tutun (ID'ler ve iş tipi, büyük blob'lar değil).

INSERT INTO jobs (type, payload, status, run_at, attempts, max_attempts)
VALUES ($1, $2::jsonb, 'queued', $3, 0, 10);
  1. Claim döngüsünü (talep döngüsünü) uygulayın. Bunu bir transaction içinde çalıştırın. Birkaç vadesi gelmiş işi seçin, diğer worker'ların atlaması için kilitleyin ve aynı transaction içinde running olarak işaretleyin.
WITH picked AS (
  SELECT id
  FROM jobs
  WHERE status = 'queued' AND run_at \u003c= now()
  ORDER BY run_at
  FOR UPDATE SKIP LOCKED
  LIMIT 10
)
UPDATE jobs
SET status = 'running', started_at = now()
WHERE id IN (SELECT id FROM picked)
RETURNING *;
  1. İşle ve sonlandır. Her talep edilmiş iş için işi yapın, sonra done durumuna finished_at ile güncelleyin. Eğer hata olursa, hata mesajını kaydedin ve geri çekilme ile queued durumuna geri koyun. Sonlandırma güncellemeleri küçük tutun ve proses kapanırken bile mutlaka çalıştırın.

  2. Açıklanabilir yeniden deneme kuralları ekleyin. Basit bir formül kullanın: run_at = now() + (attempts^2) * interval '10 seconds', ve max_attempts sonrası status = 'dead' yapın.

Basit görünürlük ekleyin

İlk günden tam bir gösterge panosuna ihtiyacınız yok, ama problemleri fark edecek kadar bilgi olmalı.

  • Her iş için bir satır loglayın: alındı, başarılı, hatalı, yeniden denendi, öldü.
  • "Dead jobs" ve "uzun süredir running duran işler" için basit bir admin sorgusu veya görünüm oluşturun.
  • Sayılara göre (örneğin son bir saatte N'den fazla dead job) alarm kurun.

Go + PostgreSQL yığını kullanıyorsanız, bu tek bir worker binary'sine ve cron tetikli bir plana temizce uyacaktır.

Kopyalayıp yapıştırabileceğiniz gerçekçi örnek

Küçük bir SaaS uygulamasını hayal edin; iki zamanlanmış işi var:

  • Geceleyin süresi dolmuş oturumları ve eski geçici dosyaları kaldıran temizlik işlevi.
  • Her Pazartesi sabahı her kullanıcıya haftalık "aktivite raporu" e-postası gönderen iş.

Basit tutun: işleri tutan bir PostgreSQL tablosu ve her dakika çalışan (cron tarafından tetiklenen) bir worker. Worker vadesi gelmiş işleri alır, çalıştırır ve başarı veya hatayı kaydeder.

Ne zaman iş kuyruğa alınır

İşleri birkaç yerden kuyruğa alabilirsiniz:

  • Her gün 02:00'de: o gün için bir cleanup_nightly işi kuyruğa alınır.
  • Kayıt sırasında: kullanıcının bir sonraki Pazartesi'si için send_weekly_report kuyruğa alınır.
  • Bir olaydan sonra (örneğin "kullanıcı Export rapora tıkladı"): belirli bir tarih aralığı için hemen çalışacak bir send_weekly_report kuyruğa alınır.

Payload, worker'ın ihtiyaç duyduğu minimum bilgiden oluşsun. Tekrar denemesi kolay olsun diye küçük tutun.

{
  "type": "send_weekly_report",
  "payload": {
    "user_id": 12345,
    "date_range": {
      "from": "2026-01-01",
      "to": "2026-01-07"
    }
  }
}

İdempotans çift gönderimi nasıl önler

Worker en kötü anda çökebilir: e-postayı gönderdikten hemen sonra fakat işi "done" olarak işaretlemeden önce. Yeniden başladığında aynı işi tekrar alabilir.

Çift gönderimi önlemek için çalışmanın doğal bir dedupe anahtarı olsun ve veritabanının bunu zorlayabileceği bir yerde saklayın. Haftalık raporlar için iyi bir anahtar (user_id, week_start_date) olur. Göndermeden önce worker "Ben rapor X'i göndereceğim" kaydını tutar. Eğer o kayıt zaten varsa gönderimi atlar.

Bu basitçe (user_id, week_start_date) üzerinde benzersiz bir kısıtlama ile sent_reports tablosu veya iş üzerinde benzersiz idempotency_key olarak uygulanabilir.

Bir hata nasıl görünür (ve nasıl toparlanır)

Diyelim e-posta sağlayıcınız zaman aşımına uğradı. İş hata verir, bu durumda worker:

  • attempts'ı artırır
  • hata mesajını debug için kaydeder
  • geri çekilme ile bir sonraki deneme zamanını ayarlar (örneğin: +1 dk, +5 dk, +30 dk, +2 saat)

Eğer belirlediğiniz limitin (örneğin 10 deneme) ötesinde hata verirse, işi "dead" yapın ve yeniden denemeyi durdurun. İş ya bir kere başarılı olur ya da açık bir takvime göre yeniden denenir; idempotans sayesinde yeniden denemeler güvenlidir.

Yaygın hatalar ve tuzaklar

Build a Postgres job worker
Describe your jobs table and worker loop in chat and generate a working app skeleton.

Cron + veritabanı deseni basittir, ama küçük hatalar onu çoğaltmalar, takılı işler veya beklenmedik yük haline getirebilir. Çoğu sorun ilk çöküş, deploy veya trafik sıçramasından sonra ortaya çıkar.

Çift veya takılı işlere neden olan hatalar

Gerçek dünya olaylarının çoğu birkaç tuzaktan kaynaklanır:

  • Aynı işi birden fazla cron girdisiyle çalıştırmak ve lease kullanmamak. Eğer iki sunucu aynı dakikada tetiklenirse, talep etme adımı atomik değilse ikisi de aynı işi alabilir.

  • locked_until'u atlamak. Bir worker işi aldıktan sonra çökerse, o satır "in progress" olarak kalabilir. Kira zaman damgası başka bir worker'ın daha sonra güvenli şekilde almasını sağlar.

  • Başarısızlıklarda anında yeniden deneme. Bir API çöktüğünde anında yeniden denemeler spike yaratır, rate limit'leri tüketir ve sürekli başarısız olur. Sonraki denemeyi geleceğe planlayın.

  • "en az bir kere"yi "tam olarak bir kere" gibi düşünmek. Bir iş iki kere çalışabilir (zaman aşımı, worker yeniden başlatma, ağ sorunları). İki kere çalıştırma zararlıysa yan etkileri tekrara dayanıklı hale getirin.

  • İş satırına çok büyük payload'lar koymak. Büyük JSON blob'ları tabloyu şişirir, indeksleri yavaşlatır ve kilitlemeyi ağırlaştırır. Bunun yerine bir referans (ör. user_id, invoice_id veya bir dosya anahtarı) saklayın ve çalışırken geri alın.

Örnek: haftalık bir fatura e-postası gönderiyorsunuz. Worker e-postayı gönderdikten sonra zaman aşımına uğrarsa ve işi done olarak işaretlemeden önce kapanırsa aynı iş yeniden denenebilir ve çift e-posta gönderilebilir. Bu desen normaldir; koruma için benzersiz "email sent" olayını fatura id'sine göre kaydedin.

Daha az belirgin tuzaklar

Zamanlama ve yürütmeyi aynı uzun transaction içinde karıştırmaktan kaçının. Ağ çağrılarını yaparken transaction açık tutarsanız, kilitleri gereğinden uzun süre elinizde tutar ve diğer worker'ları engellersiniz.

Makinalar arasındaki saat farklarına dikkat edin. run_at ve locked_until için uygulama sunucusu saatini değil veritabanı saatini (NOW() PostgreSQL) kaynağınız yapın.

Net bir maksimum çalışma süresi belirleyin. Eğer bir iş 30 dakika alabiliyorsa, kira süresini normalden uzun yapın ve gerekirse yenileyin. Aksi halde başka bir worker işi çalıştırma ortasında alabilir.

İş tablonuzun sağlıklı kalmasını sağlayın. Tamamlanmış işler sonsuza kadar birikirse sorgular yavaşlar ve kilit rekabeti artar. Tablo çok büyümeden önce basit bir saklama (retention) kuralı belirleyin (arşivle veya sil).

Hızlı kontrol listesi ve sonraki adımlar

Hızlı kontrol listesi

Bu deseni hayata geçirmeden önce temel noktaları kontrol edin. Küçük bir eksiklik genellikle takılı işler, beklenmedik kopyalar veya veritabanını döven bir worker ile sonuçlanır.

  • Jobs tablonuzda temel alanlar var mı: run_at, status, attempts, locked_until, max_attempts (ve ne olduğunu görebilmek için last_error gibi bir alan).
  • Her iş iki kere çalışsa bile zarar vermeyecek şekilde güvenli mi? Emin değilseniz idempotency anahtarı veya yan etki etrafında benzersiz kural ekleyin (ör. bir invoice için sadece bir fatura).
  • Hataları gözlemleyip ne yapılacağını belirleyeceğiniz net bir yer var mı: failed işleri görme, bir işi tekrar çalıştırma veya durdurma (dead) yetkisi.
  • Kira (lock) zaman aşımı iş için makul mu? Normal çalışmalara yeterince uzun, ama çöken worker'ların saatlerce ilerlemeyi durdurmayacağı kadar kısa olsun.
  • Yeniden deneme geri çekilmesi öngörülebilir mi? Tekrarlayan hataları yavaşlatmalı ve max_attempts sonrası durdurmalı.

Bu noktalar doğruysa, cron + veritabanı deseni genellikle gerçek iş yükleri için yeterince stabil olur.

Sonraki adımlar

Kontrol listesi tamamlandıktan sonra günlük işletmeye odaklanın.

  • İki küçük admin işlemi ekleyin: "şimdi yeniden dene" (run_at = now() ve kilidi temizle) ve "iptal" (işi terminal duruma taşı). Bu, olaylarda zaman kazandırır.
  • Worker her iş için bir satır loglasın: iş türü, iş id, deneme numarası ve sonuç. Artan hata sayıları için alarm ekleyin.
  • Gerçekçi bir spike ile yük testi yapın: aynı dakikada planlanmış çok sayıda iş. Talep etme yavaşlarsa doğru indeksi ekleyin (çoğunlukla status, run_at).

Bu deseni hızlıca kurmak isterseniz, Koder.ai (koder.ai) şema'dan dağıtıma kadar Go + PostgreSQL uygulaması oluşturmanıza yardımcı olabilir; siz kilit, yeniden deneme ve idempotans kurallarına odaklanırsınız.

Eğer daha sonra bu yapı yetersiz kalırsa, iş yaşam döngüsünü öğrenmiş olursunuz ve aynı fikirler tam bir kuyruk sistemine de iyi şekilde geçiş yapar.

SSS

Arka plan işleri için cron'u veritabanıyla ne zaman kullanmalıyım?

Bu düzeni, zaten bir veritabanınız varsa, iş türü sayısı sınırlıysa ve ayrı bir kuyruk işletmeden zamanlanmış işler yürütmek istiyorsanız kullanın. E-posta hatırlatıcıları, temizlik işlemleri, raporlar ve önbellek yenilemeleri gibi görevler için uygundur.

Bu düzende cron ne yapar?

Cron yalnızca çalışanı düzenli bir aralıkla, çoğunlukla dakikada bir başlatmalıdır. Hangi işlerin zamanı geldiğine veritabanı karar verir, durumlarını kaydeder ve her işi tek bir çalışanın sahiplenmesini sağlar.

İşler tablosunda hangi alanlar bulunmalı?

İş türünü, küçük bir JSON yükünü, çalıştırılma zamanını, durumu, deneme sayısını, yeniden deneme sınırını, kilit sahibini, kilidin bitiş zamanını ve kısa bir hata mesajını saklayın. Bu alanlar, çalışanlara işleri güvenle yürütmek ve kurtarmak için yeterli bilgiyi verir.

Birden fazla çalışan aynı işi yürütmekten nasıl kaçınır?

İşleri, PostgreSQL FOR UPDATE SKIP LOCKED gibi satır kilitleme kullanan tek bir veritabanı işlemi içinde sahiplenin. Çalışan asıl görevi başlatmadan önce satırı çalışıyor durumuna güncelleyin ve kiralamasını ayarlayın.

Arka plan işlerinin neden bir kiralama zaman aşımına ihtiyacı vardır?

Kiralama, locked_until ile kaydedilen geçici bir kilittir. Bir çalışan çökerse kiralama sona erer ve işin sonsuza dek takılı kalması yerine başka bir çalışan işi devralabilir.

Başarısız işler nasıl yeniden denenmeli?

Açık bir geri çekilme kuralıyla daha sonra yeniden deneyin: örneğin 1 dakika, 2 dakika, 4 dakika, ardından küçük bir rastgelelik içeren sınırlandırılmış bir gecikme. İzin verilen deneme sayısından sonra işi ölü olarak işaretleyin; böylece çalışan süresini tüketmeyi bırakır.

İdempotency nedir ve neden önemlidir?

Yan etkiyi tekrarlandığında güvenli olacak şekilde tasarlayın. Örneğin, fatura kimliği veya kullanıcı kimliği ile rapor tarihinin birleşimi gibi sabit bir idempotency anahtarı kullanın; ardından benzersizliği PostgreSQL'de ya da harici sağlayıcıda zorunlu kılın.

Bu düzen tam olarak bir kez işlemeyi garanti eder mi?

Hayır. Bir çalışan, e-posta gönderdikten veya ödeme sağlayıcısını çağırdıktan sonra ancak başarıyı kaydetmeden önce çökebilir. En az bir kez işleme için tasarlayın, ardından yinelenen etkileri önlemek için idempotency kullanın.

Bir çalışan işi yürütürken veritabanı işlemini açık tutmalı mı?

Veritabanı işlemlerini kısa tutun. İşi sahiplenin, işlemi onaylayın, ağ çağrılarını işlem dışında gerçekleştirin; ardından başarıyı veya başarısızlığı ayrı bir güncellemeyle kaydedin. Uzun işlemler kilitleri tutar ve diğer çalışanları yavaşlatır.

Cron ve veritabanı işlerini nasıl izlerim?

Ölü işleri ve uzun süredir çalışan işleri izleyin, her sahiplenmeyi ve sonucu günlüğe kaydedin, başarısızlıklar arttığında uyarı verin. Bir işi hemen yeniden denemek, iptal etmek veya son hatasını incelemek için basit yönetici işlemleri ekleyin.

Related posts