chore(coderd/database): optimize AuditLogs queries (#18600)

Closes #17689

This PR optimizes the audit logs query performance by extracting the
count operation into a separate query and replacing the OR-based
workspace_builds with conditional joins.

## Query changes
* Extracted count query to separate one
* Replaced single `workspace_builds` join with OR conditions with
separate conditional joins
* Added conditional joins
* `wb_build` for workspace_build audit logs (which is a direct lookup)
    * `wb_workspace` for workspace create audit logs (via workspace)

Optimized AuditLogsOffset query:
https://explain.dalibo.com/plan/4g1hbedg4a564bg8

New CountAuditLogs query:
https://explain.dalibo.com/plan/ga2fbcecb9efbce3
This commit is contained in:
Kacper Sawicki
2025-07-01 07:31:14 +02:00
committed by GitHub
parent 74e1953619
commit 695de6e0c0
15 changed files with 769 additions and 250 deletions

View File

@ -33,7 +33,9 @@ import (
// - resource_type: string (enum)
// - action: string (enum)
// - build_reason: string (enum)
func AuditLogs(ctx context.Context, db database.Store, query string) (database.GetAuditLogsOffsetParams, []codersdk.ValidationError) {
func AuditLogs(ctx context.Context, db database.Store, query string) (database.GetAuditLogsOffsetParams,
database.CountAuditLogsParams, []codersdk.ValidationError,
) {
// Always lowercase for all searches.
query = strings.ToLower(query)
values, errors := searchTerms(query, func(term string, values url.Values) error {
@ -41,7 +43,8 @@ func AuditLogs(ctx context.Context, db database.Store, query string) (database.G
return nil
})
if len(errors) > 0 {
return database.GetAuditLogsOffsetParams{}, errors
// nolint:exhaustruct // We don't need to initialize these structs because we return an error.
return database.GetAuditLogsOffsetParams{}, database.CountAuditLogsParams{}, errors
}
const dateLayout = "2006-01-02"
@ -63,8 +66,24 @@ func AuditLogs(ctx context.Context, db database.Store, query string) (database.G
filter.DateTo = filter.DateTo.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
}
// Prepare the count filter, which uses the same parameters as the GetAuditLogsOffsetParams.
// nolint:exhaustruct // UserID is not obtained from the query parameters.
countFilter := database.CountAuditLogsParams{
RequestID: filter.RequestID,
ResourceID: filter.ResourceID,
ResourceTarget: filter.ResourceTarget,
Username: filter.Username,
Email: filter.Email,
DateFrom: filter.DateFrom,
DateTo: filter.DateTo,
OrganizationID: filter.OrganizationID,
ResourceType: filter.ResourceType,
Action: filter.Action,
BuildReason: filter.BuildReason,
}
parser.ErrorExcessParams(values)
return filter, parser.Errors
return filter, countFilter, parser.Errors
}
func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) {