Skip to content

perf(actions): debounce runner heartbeat writes and throttle task picks - #38281

Merged
bircni merged 7 commits into
go-gitea:mainfrom
bircni:perf/actions-runner-poll-nodb
Jul 7, 2026
Merged

bircni merged 7 commits into
go-gitea:mainfrom
bircni:perf/actions-runner-poll-nodb

Conversation

@bircni

@bircni bircni commented Jun 30, 2026

Copy link
Copy Markdown
Member

Split out of #38150, which is being broken into smaller, independently reviewable PRs. This one contains the parts that need no database schema change and can be reviewed/merged on its own.

3 reductions in the DB load generated by many runners polling FetchTask:

1. Debounce runner heartbeat writes
Every poll wrote last_online, and every UpdateTask/UpdateLog wrote last_active — while a runner streams logs that is many writes per second per runner. These are now persisted only when stale enough to actually affect the active/offline status (ShouldPersistLastOnline / ShouldPersistLastActive), using the existing columns.

2. Throttle concurrent task picks
A new in-process semaphore (MAX_CONCURRENT_TASK_PICKS) bounds how many runners run the task-assignment transaction at once, so a fleet polling together cannot stampede the query. Throttled polls retry on their next poll without advancing the runner's tasks version.

3. Paginate the task-pick query
CreateTaskForRunner previously loaded every waiting job in the runner's scope into memory on each poll (no LIMIT). Now it pages through the waiting backlog oldest-first with LIMIT, claiming the first label-matching job.

No migration — uses existing columns and adds one config option.

Two runner-poll load reductions that need no DB schema changes:

- Debounce last_online/last_active: skip the per-poll and per-UpdateLog
  runner status writes, persisting only when stale enough to affect the
  active/offline status (ShouldPersistLastOnline/ShouldPersistLastActive).
- Bound concurrent task-pick transactions with an in-process semaphore
  (MAX_CONCURRENT_TASK_PICKS) so a fleet polling together can't stampede
  the assignment query; throttled polls retry without advancing the
  runner's tasks version.

Assisted-by: Claude:claude-opus-4-8
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Jun 30, 2026
@bircni
bircni requested a review from Zettat123 June 30, 2026 14:33
@bircni bircni added the backport/v1.27 This PR should be backported to Gitea 1.27 label Jun 30, 2026
@github-actions github-actions Bot added the docs-update-needed The document needs to be updated synchronously label Jun 30, 2026
…oll-nodb

# Conflicts:
#	models/actions/runner_test.go
Comment thread routers/api/actions/runner/runner.go
@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Jul 6, 2026
@bircni
bircni requested a review from lunny July 7, 2026 16:55
@bircni bircni mentioned this pull request Jul 7, 2026
22 tasks
@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Jul 7, 2026
@bircni
bircni enabled auto-merge (squash) July 7, 2026 18:57
@bircni
bircni merged commit 308a6f1 into go-gitea:main Jul 7, 2026
23 checks passed
@GiteaBot GiteaBot added this to the 28.0.0 milestone Jul 7, 2026
@GiteaBot GiteaBot added the backport/done All backports for this PR have been created label Jul 7, 2026
lunny pushed a commit that referenced this pull request Jul 7, 2026
…ks (#38281) (#38368)

Backport #38281 by @bircni

3 reductions in the DB load generated by many runners polling `FetchTask`:

**1. Debounce runner heartbeat writes**
Every poll wrote `last_online`, and every `UpdateTask`/`UpdateLog` wrote
`last_active` — while a runner streams logs that is many writes per
second per runner. These are now persisted only when stale enough to
actually affect the active/offline status (`ShouldPersistLastOnline` /
`ShouldPersistLastActive`), using the existing columns.

**2. Throttle concurrent task picks**
A new in-process semaphore (`MAX_CONCURRENT_TASK_PICKS`) bounds how many
runners run the task-assignment transaction at once, so a fleet polling
together cannot stampede the query. Throttled polls retry on their next
poll without advancing the runner's tasks version.

**3. Paginate the task-pick query**
`CreateTaskForRunner` previously loaded every waiting job in the
runner's scope into memory on each poll (no `LIMIT`). Now it pages
through the waiting backlog oldest-first with `LIMIT`, claiming the
first label-matching job.

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
@bircni
bircni deleted the perf/actions-runner-poll-nodb branch July 8, 2026 19:45
zjjhot added a commit to zjjhot/gitea that referenced this pull request Jul 9, 2026
* 'main' of https://github.com/go-gitea/gitea: (28 commits)
  enhance(actions): only create filtered-out workflow commit status for required contexts (go-gitea#38371)
  fix(ui): restore commits table column widths (go-gitea#38379)
  test(e2e): fix race in pdf file render test (go-gitea#38380)
  refactor: introduce ActivePageTimer to help to do partial page refresh (go-gitea#38372)
  [skip ci] Updated translations via Crowdin
  chore(typo): fix grammar in comments, API docs and error messages (go-gitea#38370)
  fix: golang html template url escaping (go-gitea#38363)
  perf(actions): debounce runner heartbeat writes and throttle task picks (go-gitea#38281)
  fix(mirror): disable HTTP redirects on pull mirror sync (go-gitea#38320)
  chore(deps): update dependency djlint to v1.40.1 (go-gitea#38354)
  fix(deps): update npm dependencies (go-gitea#38352)
  chore(deps): update action dependencies (go-gitea#38353)
  fix(deps): update go dependencies (go-gitea#38346)
  fix: minio init check (go-gitea#38355)
  fix(pulls): add `branch-name` option for `DEFAULT_TITLE_SOURCE` (go-gitea#38356)
  fix: org project view assignee list (go-gitea#38357)
  feat(webhook): add reviewer name to MS Teams review request notifications (go-gitea#38289)
  chore(deps): update action dependencies (go-gitea#38340)
  chore: Upgrade xorm to 1.4.1 (go-gitea#38224)
  chore(deps): update tool dependencies (go-gitea#38344)
  ...
zjjhot added a commit to zjjhot/gitea that referenced this pull request Jul 9, 2026
* main:
  enhance(actions): only create filtered-out workflow commit status for required contexts (go-gitea#38371)
  fix(ui): restore commits table column widths (go-gitea#38379)
  test(e2e): fix race in pdf file render test (go-gitea#38380)
  refactor: introduce ActivePageTimer to help to do partial page refresh (go-gitea#38372)
  [skip ci] Updated translations via Crowdin
  chore(typo): fix grammar in comments, API docs and error messages (go-gitea#38370)
  fix: golang html template url escaping (go-gitea#38363)
  perf(actions): debounce runner heartbeat writes and throttle task picks (go-gitea#38281)
  fix(mirror): disable HTTP redirects on pull mirror sync (go-gitea#38320)
  chore(deps): update dependency djlint to v1.40.1 (go-gitea#38354)
  fix(deps): update npm dependencies (go-gitea#38352)
  chore(deps): update action dependencies (go-gitea#38353)
  fix(deps): update go dependencies (go-gitea#38346)
  fix: minio init check (go-gitea#38355)
  fix(pulls): add `branch-name` option for `DEFAULT_TITLE_SOURCE` (go-gitea#38356)
zeekay pushed a commit to hanzoai/forge that referenced this pull request Jul 26, 2026
…ks (go-gitea#38281)

3 reductions in the DB load generated by many runners polling
`FetchTask`:

**1. Debounce runner heartbeat writes**
Every poll wrote `last_online`, and every `UpdateTask`/`UpdateLog` wrote
`last_active` — while a runner streams logs that is many writes per
second per runner. These are now persisted only when stale enough to
actually affect the active/offline status (`ShouldPersistLastOnline` /
`ShouldPersistLastActive`), using the existing columns.

**2. Throttle concurrent task picks**
A new in-process semaphore (`MAX_CONCURRENT_TASK_PICKS`) bounds how many
runners run the task-assignment transaction at once, so a fleet polling
together cannot stampede the query. Throttled polls retry on their next
poll without advancing the runner's tasks version.

**3. Paginate the task-pick query**
`CreateTaskForRunner` previously loaded every waiting job in the
runner's scope into memory on each poll (no `LIMIT`). Now it pages
through the waiting backlog oldest-first with `LIMIT`, claiming the
first label-matching job.

---------

Co-authored-by: Zettat123 <zettat123@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport/done All backports for this PR have been created backport/v1.27 This PR should be backported to Gitea 1.27 docs-update-needed The document needs to be updated synchronously lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants