mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-26 06:16:56 +09:00
perf(actions): batch per-row queries in run, job and cancellation paths (#39723)
Several Actions code paths issued one or more queries per listed row. They now load in batch, with unchanged output. - API job list: tasks, steps and runners load in batch - API run list: latest attempts and their trigger users load in batch - Repository runs page: the jobs of all active runs load in one query - Cancelling previous runs and replacing concurrency group entries fetch their jobs in one query Per call with 50 rows on SQLite, base vs head: | Path | Queries | Time | | --- | --- | --- | | API job list | 254 → 6 | 12.9 → 1.9 ms | | API run list | 305 → 207 | 11.7 → 6.2 ms | | Runs page job loading | 100 → 1 | 6.5 → 0.47 ms | | `CancelPreviousJobs` | 546 → 496 | ~120 ms, per-job writes dominate | The remaining run list queries come from per-run repository conversion, which this PR doesn't change. --------- Co-authored-by: Zettat123 <zettat123@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
10 files changed
+241
-121
No files matched your search
@@ -15,6 +15,7 @@ import (
|
||||
"gitea.dev/models/unittest"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -126,7 +127,7 @@ func TestToActionWorkflowRun_UsesTriggerEvent(t *testing.T) {
|
||||
assert.Equal(t, "schedule", apiRun.Event)
|
||||
}
|
||||
|
||||
func TestToActionWorkflowJob_StepStatusIsIndependentOfJobStatus(t *testing.T) {
|
||||
func TestToActionWorkflowJob_StepStatusAndBatchParity(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
ctx := t.Context()
|
||||
|
||||
@@ -142,10 +143,11 @@ func TestToActionWorkflowJob_StepStatusIsIndependentOfJobStatus(t *testing.T) {
|
||||
require.NoError(t, db.Insert(ctx, run))
|
||||
|
||||
task := &actions_model.ActionTask{
|
||||
ID: 900102,
|
||||
JobID: 9001,
|
||||
RepoID: 2,
|
||||
Status: actions_model.StatusFailure,
|
||||
ID: 900102,
|
||||
JobID: 9001,
|
||||
RepoID: 2,
|
||||
RunnerID: 34346,
|
||||
Status: actions_model.StatusFailure,
|
||||
}
|
||||
require.NoError(t, db.Insert(ctx, task))
|
||||
|
||||
@@ -162,8 +164,8 @@ func TestToActionWorkflowJob_StepStatusIsIndependentOfJobStatus(t *testing.T) {
|
||||
require.NoError(t, db.Insert(ctx, job))
|
||||
|
||||
require.NoError(t, db.Insert(ctx,
|
||||
&actions_model.ActionTaskStep{TaskID: task.ID, RepoID: 2, Index: 0, Name: "step-success", Status: actions_model.StatusSuccess},
|
||||
&actions_model.ActionTaskStep{TaskID: task.ID, RepoID: 2, Index: 1, Name: "step-failure", Status: actions_model.StatusFailure},
|
||||
&actions_model.ActionTaskStep{TaskID: task.ID, RepoID: 2, Index: 0, Name: "step-success", Status: actions_model.StatusSuccess},
|
||||
))
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
@@ -172,10 +174,15 @@ func TestToActionWorkflowJob_StepStatusIsIndependentOfJobStatus(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, apiJob.Steps, 2)
|
||||
|
||||
assert.Equal(t, "runner_to_be_deleted-user", apiJob.RunnerName)
|
||||
assert.Equal(t, "completed", apiJob.Steps[0].Status, "step 0 status")
|
||||
assert.Equal(t, "success", apiJob.Steps[0].Conclusion, "step 0 conclusion (succeeded before the failure)")
|
||||
assert.Equal(t, "completed", apiJob.Steps[1].Status, "step 1 status")
|
||||
assert.Equal(t, "failure", apiJob.Steps[1].Conclusion, "step 1 conclusion")
|
||||
|
||||
apiJobs, err := ToActionWorkflowJobs(ctx, []*actions_model.ActionRunJob{job})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []*api.ActionWorkflowJob{apiJob}, apiJobs)
|
||||
}
|
||||
|
||||
func TestToActionsStatus(t *testing.T) {
|
||||
|
||||
+63
-27
@@ -491,11 +491,7 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, conclusion := ToRunActionsStatus(job.Run, job.Status)
|
||||
var runnerID int64
|
||||
var runnerName string
|
||||
var steps []*api.ActionWorkflowStep
|
||||
|
||||
var runner *actions_model.ActionRunner
|
||||
if effectiveTaskID := job.EffectiveTaskID(); effectiveTaskID != 0 {
|
||||
if task == nil {
|
||||
task, _, err = db.GetByID[actions_model.ActionTask](ctx, effectiveTaskID)
|
||||
@@ -505,28 +501,68 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
|
||||
}
|
||||
|
||||
if task != nil {
|
||||
if task.Steps == nil {
|
||||
task.Steps, err = actions_model.GetTaskStepsByTaskID(ctx, task.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.Steps = util.SliceNilAsEmpty(task.Steps)
|
||||
}
|
||||
runnerID = task.RunnerID
|
||||
if runner, ok, _ := db.GetByID[actions_model.ActionRunner](ctx, runnerID); ok {
|
||||
runnerName = runner.Name
|
||||
}
|
||||
for i, step := range task.Steps {
|
||||
stepStatus, stepConclusion := ToActionsStatus(step.Status)
|
||||
steps = append(steps, &api.ActionWorkflowStep{
|
||||
Name: step.Name,
|
||||
Number: int64(i),
|
||||
Status: stepStatus,
|
||||
Conclusion: stepConclusion,
|
||||
StartedAt: step.Started.AsTime().UTC(),
|
||||
CompletedAt: step.Stopped.AsTime().UTC(),
|
||||
})
|
||||
if err := (actions_model.TaskList{task}).LoadSteps(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runner, _, _ = db.GetByID[actions_model.ActionRunner](ctx, task.RunnerID)
|
||||
}
|
||||
}
|
||||
return toActionWorkflowJob(ctx, repo, task, runner, job), nil
|
||||
}
|
||||
|
||||
// ToActionWorkflowJobs requires job.Run.Repo to be loaded
|
||||
func ToActionWorkflowJobs(ctx context.Context, jobs []*actions_model.ActionRunJob) ([]*api.ActionWorkflowJob, error) {
|
||||
tasks, err := actions_model.GetTasksMapByIDs(ctx, container.FilterSlice(jobs, func(job *actions_model.ActionRunJob) (int64, bool) {
|
||||
taskID := job.EffectiveTaskID()
|
||||
return taskID, taskID != 0
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
taskList := actions_model.TaskList(slices.Collect(maps.Values(tasks)))
|
||||
if err := taskList.LoadSteps(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runners, err := actions_model.GetRunnersMapByIDs(ctx, container.FilterSlice(taskList, func(task *actions_model.ActionTask) (int64, bool) {
|
||||
return task.RunnerID, task.RunnerID != 0
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*api.ActionWorkflowJob, len(jobs))
|
||||
for i, job := range jobs {
|
||||
task := tasks[job.EffectiveTaskID()]
|
||||
var runner *actions_model.ActionRunner
|
||||
if task != nil {
|
||||
runner = runners[task.RunnerID]
|
||||
}
|
||||
result[i] = toActionWorkflowJob(ctx, job.Run.Repo, task, runner, job)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func toActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task *actions_model.ActionTask, runner *actions_model.ActionRunner, job *actions_model.ActionRunJob) *api.ActionWorkflowJob {
|
||||
status, conclusion := ToRunActionsStatus(job.Run, job.Status)
|
||||
var runnerID int64
|
||||
var runnerName string
|
||||
var steps []*api.ActionWorkflowStep
|
||||
|
||||
if task != nil && job.EffectiveTaskID() != 0 {
|
||||
runnerID = task.RunnerID
|
||||
if runner != nil {
|
||||
runnerName = runner.Name
|
||||
}
|
||||
for i, step := range task.Steps {
|
||||
stepStatus, stepConclusion := ToActionsStatus(step.Status)
|
||||
steps = append(steps, &api.ActionWorkflowStep{
|
||||
Name: step.Name,
|
||||
Number: int64(i),
|
||||
Status: stepStatus,
|
||||
Conclusion: stepConclusion,
|
||||
StartedAt: step.Started.AsTime().UTC(),
|
||||
CompletedAt: step.Stopped.AsTime().UTC(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +587,7 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task
|
||||
CreatedAt: job.Created.AsTime().UTC(),
|
||||
StartedAt: job.Started.AsTime().UTC(),
|
||||
CompletedAt: job.Stopped.AsTime().UTC(),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getActionWorkflowEntry(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, refName git.RefName, folder string, entry *git.TreeEntry) *api.ActionWorkflow {
|
||||
|
||||
Reference in new issue
Block a user