Skip to content

fix(queue): phase-spread the scheduled cron enqueue (#1948) - #1952

Merged
JSONbored merged 2 commits into
mainfrom
claude/bold-galileo-8f555c
Jul 1, 2026
Merged

fix(queue): phase-spread the scheduled cron enqueue (#1948)#1952
JSONbored merged 2 commits into
mainfrom
claude/bold-galileo-8f555c

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

The self-host cron flushed every due scheduled job with run_after = now in one Promise.all (src/index.ts), so at the top of the hour — and especially the top of every 6th hour — the hourly singletons, the 30-min set, and all six heavy full-sync fan-out parents became due in the same 2-min slot. Their per-repo children then piled on together, draining the shared GitHub REST bucket and tripping GitHub's secondary (burst) rate limit, which parked the queue.

This phase-spreads the enqueue: each scheduled job type gets a stable, deterministic slot across a jitter window (reusing the existing deterministicJitterMs helper), so the maintenance fan-out rolls out over minutes instead of firing in one instant. The per-repo children each parent fans out inherit the offset for free, since their own index-stagger is relative to when the parent runs. The every-tick priority jobs — the re-gate sweep and its Orb-relay retry, which drive timely merges/closes — stay immediate. The enqueued set of jobs is byte-identical; only run_after timing changes.

Advances #1948 (the phase-spread deliverable; extending maintenance-headroom backpressure to every fan-out type and fixing the per-repo min(index*step, cap) tail clamp remain as follow-ups). Root cause and the full audit backlog are on #1936.

  • SCHEDULED_ENQUEUE_JITTER_MS (default 5 min) makes the window operator-tunable, matching the existing QUEUE_*_JITTER_MS knobs.

Scope

Validation

  • git diff --check
  • npm run typecheck
  • npm run test:coverage locally — the changed lines and branches in src/index.ts (the jittered enqueue ternary) and src/selfhost/queue-common.ts (scheduledEnqueueJitterMs / scheduledEnqueueDelaySeconds) are fully covered (both the immediate → no-delay and jittered → delayed sides).
  • npm run test:ci (actionlint, db:migrations:check, typecheck, coverage, workers, mcp, ui:*)
  • npm audit --audit-level=moderate — 0 vulnerabilities
  • New behavior has unit tests: immediate vs jittered classification, deterministic in-window slots, sub-second/zero-window collapse to immediate, env-parse fallbacks, and an end-to-end assertion that a full-sync tick enqueues the same job set with the sweep immediate and the periodic jobs deferred.

If any required check was skipped, explain why:

  • No ui:openapi / cf-typegen / migration regen: this is a backend-only change with no API/schema, no Cloudflare binding (the new knob is read from process.env, matching the sibling QUEUE_*_JITTER_MS helpers), and no DB change.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, PATs, private keys, trust scores, rankings, or maintainer evidence are exposed.
  • Public GitHub text is unchanged.
  • No auth/cookie/CORS/GitHub App/session behavior changes (enqueue timing only).
  • No API/OpenAPI/MCP behavior change.
  • No UI changes.

Notes

  • Correctness: the enqueued job set is unchanged, so coalescing (jobCoalesceKey), backpressure gating, and terminal-state guards all behave exactly as before — a job delayed within the window is still deduped against the next tick's copy. Only run_after moves.
  • Scope choice: the change stays in src/index.ts + src/selfhost/queue-common.ts and does not touch the guarded src/queue/** fan-out code; jittering the parents is sufficient to de-collide their children.

The self-host cron flushed every due scheduled job with run_after=now in one
Promise.all, so the top-of-hour (and top-of-6h) tick fanned out the hourly
singletons, the 30-min set, and all six heavy full-sync parents in the same
2-min slot; their per-repo children then piled on together, draining the
shared GitHub REST bucket and tripping the secondary rate limit.

Give each scheduled job type a stable deterministic slot across a jitter
window (reusing deterministicJitterMs) so the maintenance fan-out rolls out
over minutes. The per-repo children inherit the offset because their index
stagger is relative to when the parent runs. The every-tick sweep and its
Orb-relay retry stay immediate. The enqueued job set is byte-identical; only
run_after timing changes. Window is tunable via SCHEDULED_ENQUEUE_JITTER_MS.
@dosubot dosubot Bot added the size:M label Jul 1, 2026
@loopover-orb

loopover-orb Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Tip

🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩

✅ Gittensory review result - approve/merge recommended

Review updated: 2026-07-01 04:14:27 UTC

4 files · 1 AI reviewer · no blockers · readiness 75/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
The AI review returned non-blocking notes for this change but did not include a separate narrative summary. Review the nits below before deciding this PR.

Nits — 5 non-blocking
  • nit: test/unit/selfhost-queue-common.test.ts:858 says distinct job types land in distinct slots, but the assertion only proves there is more than one slot and deterministic jitter can legitimately collide.
  • nit: test/unit/index.test.ts:512 derives the expected delay with the same scheduledEnqueueDelaySeconds helper used by production, so the integration test mostly checks that the helper is called rather than pinning one concrete send option.
  • nit: src/selfhost/queue-common.ts:551 introduces SCHEDULED_ENQUEUE_JITTER_MS, but the provided diff does not show operator docs or env-sample coverage for the new knob.
  • Change the selfhost-queue-common test comment to say the selected maintenance jobs are spread across more than one slot, or assert the actual collision-free set if that is a requirement.
  • In test/unit/index.test.ts, pin at least one concrete maintenance job's delay to a known numeric value under the default window while keeping the helper unit tests for the detailed policy.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ✅ No-issue rationale PR body explains why no issue is linked.
Related work ⚠️ 1 scoped overlap Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (size label size:M; no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 68 registered-repo PR(s), 58 merged, 274 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 68 PR(s), 274 issue(s).
Gate result ✅ Passing No configured blocker found.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 68 PR(s), 274 issue(s).
  • Related work: Titles/paths share 5 meaningful terms. (PR #1933, PR #1976)
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Review top overlaps.
  • Add a concise scope and risk note.
  • Triage stale or unlinked PRs.
  • No action.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
  • Check active issues and PRs before submitting.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added gittensor gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. labels Jul 1, 2026
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.61%. Comparing base (ef039a9) to head (2fae6de).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1952   +/-   ##
=======================================
  Coverage   95.61%   95.61%           
=======================================
  Files         218      218           
  Lines       24298    24305    +7     
  Branches     8816     8818    +2     
=======================================
+ Hits        23232    23239    +7     
  Misses        436      436           
  Partials      630      630           
Files with missing lines Coverage Δ
src/index.ts 94.66% <100.00%> (+0.14%) ⬆️
src/selfhost/queue-common.ts 91.57% <100.00%> (+0.16%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JSONbored
JSONbored merged commit e6c2758 into main Jul 1, 2026
8 checks passed
@JSONbored
JSONbored deleted the claude/bold-galileo-8f555c branch July 1, 2026 04:34
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

perf(scheduler): phase-spread and backpressure the whole hourly job fan-out

1 participant