Skip to content

The sweep

A sweep is one pass over every family whose interval has elapsed, except that the running service starts one family of six hours or more in each sweep, more only where one could not keep every cadence, and leaves the others that are due for the next ticks: the slow families take turns. It is an increment, not a rebuild: it asks for the little that can have changed since last time, writes what it got to every configured store, and records when each family ran.

Two things in that diagram are the whole design. Every collector produces dated points, and the stores that cannot hold a date get them reduced to current values first, by the same reducer, before they ever see the data. That is the subject of dating a point.

The surfaces move at wildly different speeds. Workflow runs finish every few minutes on a busy account. Labels and milestones are edited a few times a week. The list of forks changes a few times a year. One interval for all of them would either waste the rate limit on the slow ones or lose the fast ones, so each family carries its own.

FamilyDefaultCollects
actions15mWorkflow runs, jobs, steps, the Actions cache
activity15mThe repository activity log, where a force push is recorded
events15mThe account event feed, which keeps only the last 300
notifs15mThe notification inbox
ratelimit15mWhat the collector has left to spend, in each budget
deployments30mDeployments and their environments, batched over every repository
account1hProfile, contribution calendar, contribution totals
achievements1hThe profile badges, and the distance to each next tier
analyses1hCode scanning analyses, which GitHub prunes
artifacts1hArtifacts and their expiry
billing1hUsage per day, product, SKU and repository
commits1hLines changed and signature state, per commit
discussions1hThe forum half of a repository
issueevents1hThe timeline of what moved: labels, assignments, transitions
issues1hPull requests, issues and reviews, per item
outbound1hStars given, and work in other people’s repositories
repo1hStars, forks, languages, topics, releases, rulesets
security1hDependabot and code scanning alerts
stars1hStars per day for every repository, and the stargazer walk once, then the newest hundred
totals1hThe lifetime numbers, asked of GitHub rather than added up here
planning6hLabels and milestones
settings6hWebhooks and their deliveries, environments, deploy keys
traffic6hThe whole 14-day window, rewritten
forks12hWho forked, and when
profile12hPackages, gists, social accounts
stats12hCommits per week, the punch card, the workflow definitions
branches24hWhich branches are live and how stale each tip is
inventory24hWhat a workflow’s own token may do, both secret stores, default code scanning
keys24hThe account’s SSH and GPG keys, and when each expires
policyfiles24hSECURITY.md, CODEOWNERS, dependabot.yml and FUNDING.yml
rulesets24hEvery version of every ruleset’s changelog
depsoffThe dependency SBOM of each repository, and what changed
historyoffEach year’s contribution calendar, the current one included
joblogsoffThe tail of every failed job’s log

Setting any of them to 0 switches it off entirely. See cadences.

The repository list is rebuilt once an hour, by the first sweep that finds it an hour old, give or take half a tick: a sweep reads its clock a few milliseconds either side of the hour, and without that margin a repository created in between waited a tick more. Repositories are created rarely and listing them costs a page per hundred, so anything shorter spends quota to learn nothing. Forks and archived repositories are excluded by default, for the reason set out in targets.

A family that fails on one repository is logged and skipped; the sweep continues. This matters more than it sounds, because “failure” here is usually a feature being switched off: of fifty repositories, most have Dependabot off, and each of them answers 403. Treating that as an error would lose the other forty-nine.

There is one exception, and it is deliberate. A family where every repository failed is not marked as run. Marking it would hide the outage until the next cadence, which for the twelve-hour families is half a day.

level=WARN msg="family failed everywhere, not marking it as run" family=security

state_file holds nine things, and the two a sweep is judged by are when each family last ran and when each repository was first seen.

The second is what makes the one-off full walk of the stargazer list happen once instead of on every sweep, as history_read does for the daily star history. It is written through a temporary file and renamed, so a crash mid-write cannot leave a truncated state that would trigger a full re-collection.

Beside it the collector keeps what makes a sweep cheap rather than what makes it complete: the ETag cache, the workflow runs whose jobs were written, the refusals and the pull request page sizes, in <name>-cache.bin. A restart reads it back and starts where the last process stopped, not from nothing, and deleting it costs one full-price pass of each family and loses nothing.

Before each family the collector checks the three rate buckets it actually spends from. If any of them is at or below its reserve and the window has not reset yet, the family is skipped and a warning is logged rather than the budget being spent to the last call. See rate limits.

A backfill is the opposite intention: it waits for the window to turn over instead of skipping.

Written and maintained by
MIT licenceRelease history