Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungBreeze TTS 2 vs ElevenLabs: Open Source TTS Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungAgentic AI vs Generative AI: The 2026 Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungI made my agent prove every quote against the source document(23.09.2026 um 05:45 Uhr)
Sichere Programmierung8mb.video Alternative: Skip the Line, Skip the Upsell(23.09.2026 um 05:47 Uhr)
Sichere ProgrammierungBuilding a GTA 6 JSON API for entities and current status(23.09.2026 um 05:52 Uhr)
Sichere ProgrammierungEvery filter needs a documented exception(23.09.2026 um 06:01 Uhr)
Sichere ProgrammierungBreeze TTS 2 vs ElevenLabs: Open Source TTS Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungAgentic AI vs Generative AI: The 2026 Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungI made my agent prove every quote against the source document(23.09.2026 um 05:45 Uhr)
Sichere Programmierung8mb.video Alternative: Skip the Line, Skip the Upsell(23.09.2026 um 05:47 Uhr)
Sichere ProgrammierungBuilding a GTA 6 JSON API for entities and current status(23.09.2026 um 05:52 Uhr)
Sichere ProgrammierungEvery filter needs a documented exception(23.09.2026 um 06:01 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

The Reconciler Pattern: When a Queued Job Simply Never Runs

TL;DR A job dispatched once, at row-creation time, has a silent failure mode: if that dispatch is lost, the row sits pending forever — and never appears in failed_jobs. Retries only exist for jobs that ran. A job that never entered the q…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




TL;DR




  • A job dispatched once, at row-creation time, has a silent failure mode: if that dispatch is lost, the row sits pending forever — and never appears in failed_jobs.

  • Retries only exist for jobs that ran. A job that never entered the queue has nothing to retry.

  • Fix: a reconciler — a scheduled sweep for stale, unclaimed rows that re-dispatches them. The database is the source of truth; the queue is just delivery.






The bug that leaves no trace



Records stuck in pending. No error, no failed job, no log line. The shape of the code:




$status = SyncStatus::create([...]);   // sync_status = 'pending'
ProcessSyncStatus::dispatch($status->id);






One dispatch, at creation. Fine 99% of the time. The 1%:

































What happened Where the job went What you see

queue:clear during an incident
Payload deleted Stuck pending, no error
No worker running at dispatch Flushed Stuck pending, no error
Redeploy dropped the connection mid-enqueue Never enqueued Stuck pending, no error
Job ran and threw failed_jobs An error you can act on


Only the last row is visible. The first three are invisible failures — dashboards green, data quietly rotting.



The bad mental model: "the queue is durable, so a dispatched job will eventually run." Your database row is the durable thing. The queue is a delivery hint.






Retry ≠ reconcile




























Retry Reconcile
Triggered by A job that ran and failed A state that looks wrong
Source of truth The queue / failed_jobs
The database
Catches lost dispatches? No Yes


Retry is a queue-level mechanism. Reconciliation is a domain-level one. You need both.






The reconciler



Periodically ask the database "does anything look stuck?", and fix it.




  every 15 min
|
v
+----------------------------+
| sweep pending rows |
| updated_at < now()-15m | <- stale
| AND claimed_at IS NULL | <- never picked up by a worker
+----------------------------+
| touch(row) <- so the next tick won't sweep it again
v
re-dispatch ProcessSyncStatus






Two predicates carry the weight. Stale: updated_at older than a threshold — fresh pending rows are just working, give the queue time. Unclaimed: the job stamps the row the moment a worker picks it up, so a pending-and-claimed row is in flight right now; leave it alone. Pending and unclaimed after 15 minutes was never delivered.




final class ReconcilePendingSync extends Command
{
protected $signature = 'sync:reconcile-pending
{--minutes=15 : Only sweep rows untouched this long}
{--limit=200 : Cap re-dispatches per run}
{--dry-run}'
;

public function handle(): int
{
$rows = SyncStatus::query()
->where('sync_status', 'pending')
->whereNull('claimed_at')
->where('updated_at', '<', now()->subMinutes((int) $this->option('minutes')))
->limit((int) $this->option('limit'))
->get();

foreach ($rows as $row) {
if ($this->option('dry-run')) { continue; } // ...report only

$row->touch(); // don't sweep again next tick
ProcessSyncStatus::dispatch($row->id);
Log::info('reconciler: re-dispatched', ['id' => $row->id]);
}

return self::SUCCESS;
}
}






Three things that are easy to get wrong:





  • touch() before dispatch. Otherwise the next tick sees the same stale row and dispatches it again — a job multiplier, not a reconciler.


  • --limit. If something breaks for a day, the first sweep shouldn't dump 40,000 jobs into Redis.


  • Idempotent job. Reconciliation will occasionally re-run something that was fine. That must be a no-op: claim the row at the top of handle(), make the work an upsert.






Tests



Queue::fake() plus a backdated updated_at covers the cases that matter:




function agePendingRow(SyncStatus $row, int $minutes): void
{
// Bypass Eloquent timestamps to backdate updated_at.
DB::table('sync_status')->where('id', $row->id)
->update(['updated_at' => now()->subMinutes($minutes)]);
}

it('re-dispatches a stale unclaimed pending row', function () {
Queue::fake();
$stale = SyncStatus::factory()->create(['sync_status' => 'pending', 'claimed_at' => null]);
agePendingRow($stale, 60);

$this->artisan('sync:reconcile-pending', ['--minutes' => 15])->assertSuccessful();

Queue::assertPushed(ProcessSyncStatus::class, 1);
});

it('skips a pending row already claimed by a worker', function () {
Queue::fake();
$claimed = SyncStatus::factory()->create(['sync_status' => 'pending', 'claimed_at' => now()]);
agePendingRow($claimed, 60);

$this->artisan('sync:reconcile-pending', ['--minutes' => 15])->assertSuccessful();

Queue::assertNotPushed(ProcessSyncStatus::class);
});






Plus a third: a fresh pending row must be left alone. Anyone can write a sweeper that re-dispatches everything — the value is in what it refuses to touch.






Takeaway



If a database row implies "a job should be running for this", something needs to check that claim on a schedule. Dispatch-on-create is a happy-path optimisation, not a guarantee.



The rule I'm keeping: the queue delivers, the database decides. Any state that can only be advanced by a queued job needs a reconciler behind it — otherwise "eventually consistent" really means "usually consistent, and silent when it isn't."

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten The Reconciler Pattern: When a Queued Job Simply Never Runs

Thematisch verwandte Begriffe: Reconciler, Pattern, When, Queued · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-17636 | IBM Financial Transaction Manager (FTM) for RedHat OpenShift could allow…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick