Fix worker error handling, queues, and stale task reclaim #20

Merged
graham merged 2 commits from fix/workers into main 2026-09-22 12:13:27 -05:00
Owner

Audit workers/contexts group.

  • WeatherFetchWorker returned bare :error (Oban success) — now {:error, reason} so failures retry.
  • HrrrNativeGridWorker targeted a nonexistent :hrrr queue — retargeted to :weather.
  • RepoListener reconnect was a no-op (Process.exit(pid, :normal)) — now GenServer.stop, plus {:eventually, ref} handled.
  • HrrrPointEnqueuer reclaims hrrr_fetch_tasks stuck 'running' when hrrr-point-rs dies mid-batch.
  • CanadianSounding/StationElevation/radar workers no longer mark complete on failed upserts; failures logged.
  • GefsFetchWorker logs seed insert failures; IemreFetchWorker uniqueness covers all incomplete states.
  • UserHomeQthLookupWorker moved to :admin (the :weather queue is paused on hot pods).

Verified: 169 targeted tests pass; full suite green.

Audit workers/contexts group. - WeatherFetchWorker returned bare :error (Oban success) — now {:error, reason} so failures retry. - HrrrNativeGridWorker targeted a nonexistent :hrrr queue — retargeted to :weather. - RepoListener reconnect was a no-op (Process.exit(pid, :normal)) — now GenServer.stop, plus {:eventually, ref} handled. - HrrrPointEnqueuer reclaims hrrr_fetch_tasks stuck 'running' when hrrr-point-rs dies mid-batch. - CanadianSounding/StationElevation/radar workers no longer mark complete on failed upserts; failures logged. - GefsFetchWorker logs seed insert failures; IemreFetchWorker uniqueness covers all incomplete states. - UserHomeQthLookupWorker moved to :admin (the :weather queue is paused on hot pods). Verified: 169 targeted tests pass; full suite green.
Fix worker error handling, queues, and stale task reclaim
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
b01ddb955b
- WeatherFetchWorker returns {:error, reason} so Oban retries failed
  sounding stores instead of recording success
- HrrrNativeGridWorker targets :weather (the :hrrr queue never existed,
  so native backfill jobs sat available forever)
- RepoListener actually stops the broken LISTEN connection on reconnect
  (Process.exit(pid, :normal) on a non-self pid was a no-op leak)
- HrrrPointEnqueuer reclaims hrrr_fetch_tasks rows stuck 'running' after
  a worker dies mid-batch (15-min stale window, 5-attempt cap)
- CanadianSoundingFetchWorker/StationElevationWorker/radar workers no
  longer mark work complete on failed upserts; failures are logged
- GefsFetchWorker logs seed_extended_horizon insert failures
- IemreFetchWorker uniqueness covers all incomplete states
- UserHomeQthLookupWorker moves to :admin so it runs on hot pods
  (:weather is paused there)
skippy-bot left a comment

🤖 Skippy PR review

2 findings — 1 blocking before merge.

Severity Location Issue
🟠 High lib/microwaveprop/weather/hrrr_point_enqueuer.ex:156 Reclaim writes failed, which the backfill reads as "give up on these contacts"
🔵 Suggestion lib/microwaveprop/workers/station_elevation_worker.ex:47 Failed elevation upsert still reports job success

Reviewed b01ddb955bce. Comment skippy review to re-run.

### 🤖 Skippy PR review **2 findings** — 1 blocking before merge. | Severity | Location | Issue | | --- | --- | --- | | 🟠 High | `lib/microwaveprop/weather/hrrr_point_enqueuer.ex:156` | Reclaim writes `failed`, which the backfill reads as "give up on these contacts" | | 🔵 Suggestion | `lib/microwaveprop/workers/station_elevation_worker.ex:47` | Failed elevation upsert still reports job success | <sub>Reviewed `b01ddb955bce`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
@ -109,0 +153,4 @@
)
|> Repo.update_all(
set: [
status: "failed",
First-time contributor

🟠 High — Reclaim writes failed, which the backfill reads as "give up on these contacts"

BackfillEnqueueWorker.reconcile_failed_hrrr_task_contacts/1 (lib/microwaveprop/workers/backfill_enqueue_worker.ex:168) treats every hrrr_fetch_tasks row with status = 'failed' as a permanently unreachable valid_time: it flips every contact in that hour from hrrr_status: :queued to :unavailable and logs marked N hrrr-queued contacts as unavailable (upstream archive gap). Until now failed was only written by Rust's fail_hrrr_task/3, i.e. after the worker itself reported a terminal batch error (wgrib2 decode, idx 404). This new flip writes the same value for a row whose only fault is that hrrr-point-rs died after 5 claims (OOM, node drain, rolling deploy), which is precisely the transient case the reclaim exists to recover, so a crash-looping hrrr-point-rs now turns recoverable enrichments into "no data" for other users' contacts and waits out the 24 h @unavailable_reprobe_cooldown_seconds before trying again. It also does not retire a row the way the docstring claims: the next enqueue for that valid_time hits the on_conflict CASE (status IN ('done','failed') THEN 'queued', attempt THEN 0), so the row is resurrected with a fresh 5-attempt budget while the contacts it feeds sit :unavailable and no longer select for re-enqueue. Suggest not overloading failed - make the reconcile skip reclaim rows (t.error NOT LIKE 'reclaim-orphan:%'), or give the reclaim its own state so failed keeps meaning "the Rust worker gave up".

**🟠 High — Reclaim writes `failed`, which the backfill reads as "give up on these contacts"** `BackfillEnqueueWorker.reconcile_failed_hrrr_task_contacts/1` (`lib/microwaveprop/workers/backfill_enqueue_worker.ex:168`) treats **every** `hrrr_fetch_tasks` row with `status = 'failed'` as a permanently unreachable `valid_time`: it flips every contact in that hour from `hrrr_status: :queued` to `:unavailable` and logs `marked N hrrr-queued contacts as unavailable (upstream archive gap)`. Until now `failed` was only written by Rust's `fail_hrrr_task/3`, i.e. after the worker itself reported a terminal batch error (wgrib2 decode, `idx` 404). This new flip writes the same value for a row whose only fault is that hrrr-point-rs died after 5 claims (OOM, node drain, rolling deploy), which is precisely the transient case the reclaim exists to recover, so a crash-looping hrrr-point-rs now turns recoverable enrichments into "no data" for other users' contacts and waits out the 24 h `@unavailable_reprobe_cooldown_seconds` before trying again. It also does not retire a row the way the docstring claims: the next `enqueue` for that `valid_time` hits the `on_conflict` CASE (`status IN ('done','failed') THEN 'queued'`, `attempt THEN 0`), so the row is resurrected with a fresh 5-attempt budget while the contacts it feeds sit `:unavailable` and no longer select for re-enqueue. Suggest not overloading `failed` - make the reconcile skip reclaim rows (`t.error NOT LIKE 'reclaim-orphan:%'`), or give the reclaim its own state so `failed` keeps meaning "the Rust worker gave up".
skippy-bot marked this conversation as resolved
@ -45,2 +45,3 @@
{:error, _} ->
{:error, reason} ->
Logger.warning(
First-time contributor

🔵 Suggestion — Failed elevation upsert still reports job success

The PR description lists StationElevation among the workers that "no longer mark complete on failed upserts", but the upsert failure branch above this hunk (lines 42-44) still returns :ok - Logger.warning/1 returns :ok, so a failed Repo.update reaches Oban as a successful job, is never retried, and the station silently keeps elevation_m: nil. The added SRTM log line is a genuine improvement; this branch is the missing half of the stated fix. Return {:error, changeset} there (the SRTM lookup branch can stay :ok, which the moduledoc already documents as non-fatal).

**🔵 Suggestion — Failed elevation upsert still reports job success** The PR description lists StationElevation among the workers that "no longer mark complete on failed upserts", but the upsert failure branch above this hunk (lines 42-44) still returns `:ok` - `Logger.warning/1` returns `:ok`, so a failed `Repo.update` reaches Oban as a successful job, is never retried, and the station silently keeps `elevation_m: nil`. The added SRTM log line is a genuine improvement; this branch is the missing half of the stated fix. Return `{:error, changeset}` there (the SRTM lookup branch can stay `:ok`, which the moduledoc already documents as non-fatal).
skippy-bot marked this conversation as resolved
graham force-pushed fix/workers from b01ddb955b
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
to cfb90af535
All checks were successful
skippy-bot/review Skippy review: clean — no open findings
2026-09-22 12:04:19 -05:00
Compare
Address review: reclaim-orphan is not an archive gap; failed upsert errors
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
62d45a20c3
- reconcile_failed_hrrr_task_contacts now excludes rows whose error is
  'reclaim-orphan:*' — a stale-running reclaim after the point worker
  dies mid-batch is transient, not an unreachable valid_time, so its
  contacts stay :queued instead of being flipped :unavailable
- StationElevationWorker returns {:error, changeset} on a failed
  elevation upsert so Oban retries instead of recording success
Author
Owner

Both findings addressed in 62d45a20:

  1. Reclaim writes failedreconcile_failed_hrrr_task_contacts now excludes rows whose error is reclaim-orphan:* (NULL error still counts as a real failure). A worker death is transient; the next enqueue for that valid_time resets the row to queued, so contacts stay :queued instead of being flipped :unavailable. New test covers the exclusion.

  2. Failed elevation upsertStationElevationWorker now returns {:error, changeset} on the Repo.update failure branch so Oban retries; the SRTM-lookup branch stays :ok per the moduledoc.

Both findings addressed in 62d45a20: 1. **Reclaim writes `failed`** — `reconcile_failed_hrrr_task_contacts` now excludes rows whose error is `reclaim-orphan:*` (NULL error still counts as a real failure). A worker death is transient; the next enqueue for that valid_time resets the row to `queued`, so contacts stay `:queued` instead of being flipped `:unavailable`. New test covers the exclusion. 2. **Failed elevation upsert** — `StationElevationWorker` now returns `{:error, changeset}` on the `Repo.update` failure branch so Oban retries; the SRTM-lookup branch stays `:ok` per the moduledoc.
Author
Owner

skippy review

skippy review
First-time contributor

Resolved 2 of 2 earlier findings — reclaim_stale_running/1 now tags its rows reclaim-orphan: and reconcile_failed_hrrr_task_contacts/1 excludes them (so a dead point worker no longer reads as an archive gap); StationElevationWorker returns {:error, changeset} instead of an :ok from the logger.

Nothing new in cfb90af..62d45a2.

Resolved 2 of 2 earlier findings — `reclaim_stale_running/1` now tags its rows `reclaim-orphan:` and `reconcile_failed_hrrr_task_contacts/1` excludes them (so a dead point worker no longer reads as an archive gap); `StationElevationWorker` returns `{:error, changeset}` instead of an `:ok` from the logger. Nothing new in cfb90af..62d45a2. <!-- skippy-pr-review -->
graham force-pushed fix/workers from 62d45a20c3
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
to 60f1a68f93
Some checks failed
skippy-bot/review Skippy review: 1 blocking finding open — see the PR thread
2026-09-22 12:11:31 -05:00
Compare
skippy-bot left a comment

🤖 Skippy PR review

2 findings — 1 blocking before merge.

Severity Location Issue
🟡 Warning lib/microwaveprop/weather/hrrr_point_enqueuer.ex:55 Reclaim runs once per contact enqueue, not once per sweep
🔵 Suggestion lib/microwaveprop/weather/hrrr_point_enqueuer.ex:38 Cutoff keys off claimed_at, which is never refreshed during a batch

Branch was force-pushed (b01ddb95 is no longer in the PR history), so this pass re-read the full diff. Resolved both earlier findings: reclaim-orphan: rows are now excluded from the backfill reconcile, and failed elevation upserts are returned as errors. Nothing from the previous pass is still open.

Reviewed 62d45a20c3ad. Comment skippy review to re-run.

### 🤖 Skippy PR review **2 findings** — 1 blocking before merge. | Severity | Location | Issue | | --- | --- | --- | | 🟡 Warning | `lib/microwaveprop/weather/hrrr_point_enqueuer.ex:55` | Reclaim runs once per contact enqueue, not once per sweep | | 🔵 Suggestion | `lib/microwaveprop/weather/hrrr_point_enqueuer.ex:38` | Cutoff keys off `claimed_at`, which is never refreshed during a batch | Branch was force-pushed (b01ddb95 is no longer in the PR history), so this pass re-read the full diff. Resolved both earlier findings: `reclaim-orphan:` rows are now excluded from the backfill reconcile, and failed elevation upserts are returned as errors. Nothing from the previous pass is still open. <sub>Reviewed `62d45a20c3ad`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
@ -30,0 +35,4 @@
# and the unique index on `valid_time` blocks any replacement.
# Mirrors `Propagation.GridTaskEnqueuer`'s cutoff: 15 min comfortably
# exceeds a healthy batch's wall time.
@stale_running_cutoff_seconds 15 * 60
First-time contributor

🔵 Suggestion — Cutoff keys off claimed_at, which is never refreshed during a batch

claim_next_hrrr_task sets claimed_at = NOW() exactly once (rust/prop_grid_rs/src/db.rs:376) and nothing touches it again until the terminal update, so a batch whose wall time exceeds 15 minutes (busy hour with a large point union, loaded node, slow S3) is still running with an ancient claimed_at when the next sweep reclaims it. A second worker then claims the same row via SKIP LOCKED and re-downloads/decodes that hour concurrently with the first, and each reclaim cycle pushes attempt toward @max_reclaim_attempts. Either have the Rust side touch claimed_at after each extracted point (a cheap heartbeat), or set the cutoff above any realistic batch wall time so the two claims cannot overlap.

**🔵 Suggestion — Cutoff keys off `claimed_at`, which is never refreshed during a batch** `claim_next_hrrr_task` sets `claimed_at = NOW()` exactly once (`rust/prop_grid_rs/src/db.rs:376`) and nothing touches it again until the terminal update, so a batch whose wall time exceeds 15 minutes (busy hour with a large point union, loaded node, slow S3) is still `running` with an ancient `claimed_at` when the next sweep reclaims it. A second worker then claims the same row via SKIP LOCKED and re-downloads/decodes that hour concurrently with the first, and each reclaim cycle pushes `attempt` toward `@max_reclaim_attempts`. Either have the Rust side touch `claimed_at` after each extracted point (a cheap heartbeat), or set the cutoff above any realistic batch wall time so the two claims cannot overlap.
@ -36,6 +52,8 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do
"""
@spec enqueue(%{DateTime.t() => [{float(), float()}]}) :: {:ok, non_neg_integer()} | {:error, term()}
def enqueue(groups) when is_map(groups) do
_ = reclaim_stale_running()
First-time contributor

🟡 Warning — Reclaim runs once per contact enqueue, not once per sweep

enqueue/1 now calls reclaim_stale_running/1, but the per-contact path reaches enqueue/1 once per contact: ContactWeatherEnqueueWorker.enqueue_for_contact/2 (line 147) calls HrrrPointEnqueuer.enqueue_for_contacts([contact]), and BackfillEnqueueWorker drives that with Enum.each(contacts, ...) under the */30 * * * * cron at limit: 2000. A sweep over a fresh backlog therefore fires up to 2000 reclaim transactions and 4000 UPDATE hrrr_fetch_tasks statements, each filtered on status = 'running' and claimed_at < ? - and the only status index cannot serve that predicate, because hrrr_fetch_tasks_queued_idx is partial (WHERE status = 'queued'). Every one of those statements seq-scans a table with no prune path that grows one row per enriched hour. GridTaskEnqueuer calls its reclaim once per seed cycle (PropagationGridWorker, HrdpsGridWorker), not per row, which is the pattern worth keeping here. Fix: hoist the call out of enqueue/1 to the sweep entry points, or add create index(:hrrr_fetch_tasks, [:claimed_at], where: "status = 'running'") if per-contact reclaim is deliberate.

**🟡 Warning — Reclaim runs once per contact enqueue, not once per sweep** `enqueue/1` now calls `reclaim_stale_running/1`, but the per-contact path reaches `enqueue/1` once per contact: `ContactWeatherEnqueueWorker.enqueue_for_contact/2` (line 147) calls `HrrrPointEnqueuer.enqueue_for_contacts([contact])`, and `BackfillEnqueueWorker` drives that with `Enum.each(contacts, ...)` under the `*/30 * * * *` cron at `limit: 2000`. A sweep over a fresh backlog therefore fires up to 2000 reclaim transactions and 4000 `UPDATE hrrr_fetch_tasks` statements, each filtered on `status = 'running' and claimed_at < ?` - and the only status index cannot serve that predicate, because `hrrr_fetch_tasks_queued_idx` is partial (`WHERE status = 'queued'`). Every one of those statements seq-scans a table with no prune path that grows one row per enriched hour. `GridTaskEnqueuer` calls its reclaim once per seed cycle (`PropagationGridWorker`, `HrdpsGridWorker`), not per row, which is the pattern worth keeping here. Fix: hoist the call out of `enqueue/1` to the sweep entry points, or add `create index(:hrrr_fetch_tasks, [:claimed_at], where: "status = 'running'")` if per-contact reclaim is deliberate.
graham merged commit fae3f340c6 into main 2026-09-22 12:13:27 -05:00
skippy-bot left a comment

🤖 Skippy PR review

4 findings — 1 blocking before merge.

Severity Location Issue
🟡 Warning lib/microwaveprop/weather/hrrr_point_enqueuer.ex:55 Reclaim sweeps the table once per contact, not once per pass
🔵 Suggestion lib/microwaveprop/weather/hrrr_point_enqueuer.ex:38 Cutoff keys off claimed_at, which is never refreshed during a batch
🔵 Suggestion lib/microwaveprop/workers/gefs_fetch_worker.ex:88 Seed-failure guard can never fire: insert_all returns conflicts too
🔵 Suggestion lib/microwaveprop/workers/hrrr_native_grid_worker.ex:20 Retarget does not revive hours already enqueued onto :hrrr

Branch was force-pushed (prev head 62d45a2 is no longer in history), so this re-read the full diff. Both earlier findings are resolved at 60f1a68 (reclaim rows tagged reclaim-orphan: and excluded from the archive-gap reconcile; StationElevationWorker returns {:error, changeset}) and were not reposted.

Reviewed 60f1a68f93f5. Comment skippy review to re-run.

### 🤖 Skippy PR review **4 findings** — 1 blocking before merge. | Severity | Location | Issue | | --- | --- | --- | | 🟡 Warning | `lib/microwaveprop/weather/hrrr_point_enqueuer.ex:55` | Reclaim sweeps the table once per contact, not once per pass | | 🔵 Suggestion | `lib/microwaveprop/weather/hrrr_point_enqueuer.ex:38` | Cutoff keys off `claimed_at`, which is never refreshed during a batch | | 🔵 Suggestion | `lib/microwaveprop/workers/gefs_fetch_worker.ex:88` | Seed-failure guard can never fire: insert_all returns conflicts too | | 🔵 Suggestion | `lib/microwaveprop/workers/hrrr_native_grid_worker.ex:20` | Retarget does not revive hours already enqueued onto :hrrr | Branch was force-pushed (prev head 62d45a2 is no longer in history), so this re-read the full diff. Both earlier findings are resolved at 60f1a68 (reclaim rows tagged reclaim-orphan: and excluded from the archive-gap reconcile; StationElevationWorker returns {:error, changeset}) and were not reposted. <sub>Reviewed `60f1a68f93f5`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
@ -30,0 +35,4 @@
# and the unique index on `valid_time` blocks any replacement.
# Mirrors `Propagation.GridTaskEnqueuer`'s cutoff: 15 min comfortably
# exceeds a healthy batch's wall time.
@stale_running_cutoff_seconds 15 * 60
First-time contributor

🔵 Suggestion — Cutoff keys off claimed_at, which is never refreshed during a batch

claim_next_hrrr_task stamps claimed_at = NOW() exactly once (rust/prop_grid_rs/src/db.rs:376) and nothing touches it again until the terminal update, so a batch whose wall time exceeds this 15 minute cutoff (large point union, loaded node, slow S3) is still running with an ancient claimed_at when the next sweep reclaims it. Another replica can then claim the same row through SKIP LOCKED and re-download plus re-decode that hour concurrently with the first, and every reclaim cycle also pushes attempt toward @max_reclaim_attempts. Either have the Rust side touch claimed_at after each extracted point (a cheap heartbeat), or set the cutoff above the worst realistic batch wall time.

**🔵 Suggestion — Cutoff keys off `claimed_at`, which is never refreshed during a batch** `claim_next_hrrr_task` stamps `claimed_at = NOW()` exactly once (`rust/prop_grid_rs/src/db.rs:376`) and nothing touches it again until the terminal update, so a batch whose wall time exceeds this 15 minute cutoff (large point union, loaded node, slow S3) is still `running` with an ancient `claimed_at` when the next sweep reclaims it. Another replica can then claim the same row through SKIP LOCKED and re-download plus re-decode that hour concurrently with the first, and every reclaim cycle also pushes `attempt` toward `@max_reclaim_attempts`. Either have the Rust side touch `claimed_at` after each extracted point (a cheap heartbeat), or set the cutoff above the worst realistic batch wall time.
skippy-bot marked this conversation as resolved
@ -36,6 +52,8 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do
"""
@spec enqueue(%{DateTime.t() => [{float(), float()}]}) :: {:ok, non_neg_integer()} | {:error, term()}
def enqueue(groups) when is_map(groups) do
_ = reclaim_stale_running()
First-time contributor

🟡 Warning — Reclaim sweeps the table once per contact, not once per pass

enqueue/1 is also the per-contact entry point, so this new sweep runs thousands of times per pass, not once: ContactWeatherEnqueueWorker.enqueue_for_contact/2 calls HrrrPointEnqueuer.enqueue_for_contacts([contact]) per contact (line 147), and BackfillEnqueueWorker drives that with Enum.each/2 at limit: 2000 under the */30 * * * * cron. A fresh backlog sweep is therefore up to 2000 reclaim transactions and 4000 UPDATE hrrr_fetch_tasks statements. Neither update can use an index, because the only status index is partial (hrrr_fetch_tasks_queued_idx, where: "status = 'queued'", migration 20260419231502), so status = 'running' and claimed_at < ? seq-scans a table nothing prunes (it grows a row per enriched hour; the completed_at index has no deleting job behind it). This repo already has the right shape next door: GridTaskEnqueuer.reclaim_stale_running/1 is called once per seed cycle (PropagationGridWorker:46, HrdpsGridWorker:56). Hoist the call to the pass entry points (BackfillEnqueueWorker, the PSKR sampler, the contact retry button), or add create index(:hrrr_fetch_tasks, [:claimed_at], where: "status = 'running'") if per-call reclaim is deliberate.

**🟡 Warning — Reclaim sweeps the table once per contact, not once per pass** `enqueue/1` is also the per-contact entry point, so this new sweep runs thousands of times per pass, not once: `ContactWeatherEnqueueWorker.enqueue_for_contact/2` calls `HrrrPointEnqueuer.enqueue_for_contacts([contact])` per contact (line 147), and `BackfillEnqueueWorker` drives that with `Enum.each/2` at `limit: 2000` under the `*/30 * * * *` cron. A fresh backlog sweep is therefore up to 2000 reclaim transactions and 4000 `UPDATE hrrr_fetch_tasks` statements. Neither update can use an index, because the only status index is partial (`hrrr_fetch_tasks_queued_idx`, `where: "status = 'queued'"`, migration 20260419231502), so `status = 'running' and claimed_at < ?` seq-scans a table nothing prunes (it grows a row per enriched hour; the `completed_at` index has no deleting job behind it). This repo already has the right shape next door: `GridTaskEnqueuer.reclaim_stale_running/1` is called once per seed cycle (`PropagationGridWorker:46`, `HrdpsGridWorker:56`). Hoist the call to the pass entry points (BackfillEnqueueWorker, the PSKR sampler, the contact retry button), or add `create index(:hrrr_fetch_tasks, [:claimed_at], where: "status = 'running'")` if per-call reclaim is deliberate.
skippy-bot marked this conversation as resolved
@ -87,0 +85,4 @@
new(%{"run_time" => DateTime.to_iso8601(run_time), "forecast_hour" => fh})
end)
inserted = length(Oban.insert_all(jobs))
First-time contributor

🔵 Suggestion — Seed-failure guard can never fire: insert_all returns conflicts too

inserted < length(hours) is dead code. On the Smart engine Oban.insert_all/1 returns one entry per input changeset - a job rejected by the unique key comes back with conflict?: true (Oban.Pro.Engines.Smart.apply_conflicts/1 returns old_jobs ++ new_jobs; only on_conflict: :skip drops conflicting entries from the result). So inserted == length(hours) on every call and a partial seed stays as silent as it was before this change, which is the one signal this hunk was meant to add. Count the real inserts instead: Oban.insert_all(jobs, on_conflict: :skip) (the result then holds only newly inserted jobs) or Enum.count(result, &(not &1.conflict?)).

**🔵 Suggestion — Seed-failure guard can never fire: insert_all returns conflicts too** `inserted < length(hours)` is dead code. On the Smart engine `Oban.insert_all/1` returns one entry per input changeset - a job rejected by the unique key comes back with `conflict?: true` (`Oban.Pro.Engines.Smart.apply_conflicts/1` returns `old_jobs ++ new_jobs`; only `on_conflict: :skip` drops conflicting entries from the result). So `inserted == length(hours)` on every call and a partial seed stays as silent as it was before this change, which is the one signal this hunk was meant to add. Count the real inserts instead: `Oban.insert_all(jobs, on_conflict: :skip)` (the result then holds only newly inserted jobs) or `Enum.count(result, &(not &1.conflict?))`.
@ -17,3 +18,3 @@
use Oban.Pro.Worker,
queue: :hrrr,
queue: :weather,
First-time contributor

🔵 Suggestion — Retarget does not revive hours already enqueued onto :hrrr

Nothing serves the retired :hrrr queue, so jobs inserted before this change sit :available forever - and because this worker is unique on {year, month, day, hour} with period: :infinity and states: :incomplete, those rows keep holding the unique key. mix hrrr_native_backfill and release.native_backfill will therefore report skipped for exactly those hours instead of re-inserting them, so after deploy only hours never enqueued before will actually run. Worth a one-off check (oban_jobs where queue = 'hrrr' and state in ('available','scheduled')) and a cancel, so the retarget can backfill the hours it was meant to unstick.

**🔵 Suggestion — Retarget does not revive hours already enqueued onto :hrrr** Nothing serves the retired `:hrrr` queue, so jobs inserted before this change sit `:available` forever - and because this worker is unique on `{year, month, day, hour}` with `period: :infinity` and `states: :incomplete`, those rows keep holding the unique key. `mix hrrr_native_backfill` and `release.native_backfill` will therefore report `skipped` for exactly those hours instead of re-inserting them, so after deploy only hours never enqueued before will actually run. Worth a one-off check (`oban_jobs` where `queue = 'hrrr'` and `state in ('available','scheduled')`) and a cancel, so the retarget can backfill the hours it was meant to unstick.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
graham/prop!20
No description provided.