Skip to content

One repository can spend a sponsor's whole GitHub budget, because reconciliation schedules by due time and never by cost #227

Description

@Nitjsefnie

Description

Reconciliation gives every repository the same claim on a sponsor's GitHub budget regardless of what it costs to fold, so one expensive repository can spend a sponsor's whole window and leave their other repositories stale.

The queue has nothing to schedule on. repository_reconciliation_jobs carries run_after, attempt_count, lease columns and follow_up_requested, and no measure of what a fold cost or is expected to cost. Claiming is therefore due-time ordering: whichever job is due next is taken, and a repository that costs ten times another's budget share is indistinguishable at the point of the decision.

The disparity is already large at two repositories, both belonging to one sponsor:

repository issues fold avg fold max
Nitjsefnie/Overflow 150 48.7 s 61.3 s
Nitjsefnie-Harness-Commons/daedalus 407 165.3 s 180.1 s

Fold time tracks issue count closely, so daedalus consumes roughly three times Overflow's share of one shared 5,000/hour GraphQL budget, and the scheduler has no way to know that or act on it. When that budget ran out on 2026-09-07 at 09:28 — GraphQL used: 5000, remaining: 0 while core REST sat at 4,973 — both repositories stopped together, including the cheap one whose own activity had not caused it.

Expected Behavior

A repository's share of its sponsor's budget is bounded by something, so an expensive or highly active repository degrades its own freshness rather than everyone's. A sponsor with one large repository and several small ones should see the large one fall behind under pressure while the small ones stay current, instead of all of them stopping at the same moment.

Reproduction Steps

  1. Register two repositories of very different sizes under one sponsor.
  2. Let reconciliation run until that sponsor's GraphQL budget is exhausted.
  3. Observe both repositories stop. Nothing in repository_reconciliation_jobs or the claim query distinguishes the repository that consumed the budget from the one that did not.

Environment / Context

main at 1faa4fe. Depends on work already in flight and should not be designed before it lands:

That last point is the reason not to build this yet, and the reason to be careful about the word "deprioritise" when it is built: after #196 an expensive repository is a busy one, and busy is exactly the case where staleness is most visible to a member. The recorded target for acceptable staleness is 10 to 30 seconds. A policy that simply demotes the heaviest consumer optimises the wrong thing — it makes the active repository, whose users are watching, the one that falls behind.

Discovered During

A scalability review on 2026-09-07 prompted by the maintainer, after the GraphQL exhaustion above. Raised by the maintainer as "a repo that generates too much traffic should be deprioritised".

Suggested Fix

Unverified, and the shape matters more than the mechanism.

Charge each fold what it actually cost, using the rateLimit.cost figure #219 makes readable, and keep a decaying per-repository total. Then bound a repository's share of its sponsor's remaining window rather than ranking repositories against each other: a repository over its share has its next claim deferred through the existing deferReconciliationJob path, which already releases the lease and is how a cooled-down repository is handled today.

Bounding a share degrades the heavy repository's freshness in proportion to its own consumption while leaving others untouched. Ranking, by contrast, starves whichever repository is last — and under sustained pressure that is the same repository every cycle, which is the failure this issue is about, merely relocated.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: infraDeployment, database, migrations and opsenhancementNew feature or requestoffered: largeOpening catalog · comparison 8 · reserve 8

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions