perf(actions): debounce runner heartbeat writes and throttle task picks (#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>
This commit is contained in:
bircni
2026-07-07 21:16:20 +02:00
committed by GitHub
parent 97078b96cf
commit 308a6f12ae
10 changed files with 317 additions and 30 deletions

View File

@@ -14,6 +14,8 @@ import (
const defaultMaxRerunAttempts = 50
const defaultMaxConcurrentTaskPicks = 16
// Actions settings
var (
Actions = struct {
@@ -31,13 +33,18 @@ var (
WorkflowDirs []string `ini:"WORKFLOW_DIRS"`
ScopedWorkflowDirs []string `ini:"SCOPED_WORKFLOW_DIRS"`
MaxRerunAttempts int64 `ini:"MAX_RERUN_ATTEMPTS"`
// MaxConcurrentTaskPicks bounds how many runners may run the task-assignment
// transaction at once per Gitea instance, to avoid a thundering herd when many
// runners poll together. It is a per-process limit, not a cluster-wide one.
MaxConcurrentTaskPicks int `ini:"MAX_CONCURRENT_TASK_PICKS"`
}{
Enabled: true,
DefaultActionsURL: defaultActionsURLGitHub,
SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
WorkflowDirs: []string{".gitea/workflows", ".github/workflows"},
ScopedWorkflowDirs: []string{".gitea/scoped_workflows"},
MaxRerunAttempts: defaultMaxRerunAttempts,
Enabled: true,
DefaultActionsURL: defaultActionsURLGitHub,
SkipWorkflowStrings: []string{"[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"},
WorkflowDirs: []string{".gitea/workflows", ".github/workflows"},
ScopedWorkflowDirs: []string{".gitea/scoped_workflows"},
MaxRerunAttempts: defaultMaxRerunAttempts,
MaxConcurrentTaskPicks: defaultMaxConcurrentTaskPicks,
}
)
@@ -128,6 +135,10 @@ func loadActionsFrom(rootCfg ConfigProvider) error {
Actions.MaxRerunAttempts = defaultMaxRerunAttempts
}
if Actions.MaxConcurrentTaskPicks <= 0 {
Actions.MaxConcurrentTaskPicks = defaultMaxConcurrentTaskPicks
}
if !Actions.LogCompression.IsValid() {
return fmt.Errorf("invalid [actions] LOG_COMPRESSION: %q", Actions.LogCompression)
}