From 6e68cacd9bb8fb90ac5cb6a60f46ceca33032b18 Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Tue, 23 Jun 2026 05:58:00 +0800 Subject: [PATCH] fix(workbench): bound session summary runtime query (#1952) --- deploy/deploy.schema.json | 10 +++++ deploy/deploy.yaml | 6 ++- internal/workbenchruntime/service.go | 64 +++++++++++++++++++--------- scripts/gitops-render.mjs | 13 ++++++ 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/deploy/deploy.schema.json b/deploy/deploy.schema.json index ec806f7e..541305bd 100644 --- a/deploy/deploy.schema.json +++ b/deploy/deploy.schema.json @@ -392,6 +392,7 @@ "type": "object", "properties": { "client": { "$ref": "#/$defs/workbenchRuntimeClient" }, + "sessionsSummary": { "$ref": "#/$defs/workbenchRuntimeSessionsSummary" }, "factsQuery": { "$ref": "#/$defs/workbenchRuntimeFactsQuery" }, "postgres": { "$ref": "#/$defs/workbenchRuntimePostgres" }, "cache": { "$ref": "#/$defs/workbenchRuntimeCache" } @@ -414,6 +415,15 @@ }, "additionalProperties": false }, + "workbenchRuntimeSessionsSummary": { + "type": "object", + "properties": { + "maxAttempts": { "type": "integer", "minimum": 1 }, + "attemptTimeoutMs": { "type": "integer", "minimum": 1 }, + "backoffMs": { "type": "integer", "minimum": 0 } + }, + "additionalProperties": false + }, "workbenchRuntimePostgres": { "type": "object", "properties": { diff --git a/deploy/deploy.yaml b/deploy/deploy.yaml index 82767783..c3f47832 100644 --- a/deploy/deploy.yaml +++ b/deploy/deploy.yaml @@ -309,12 +309,16 @@ lanes: workbenchRuntime: client: timeoutMs: 3000 + sessionsSummary: + maxAttempts: 1 + attemptTimeoutMs: 2500 + backoffMs: 0 factsQuery: maxAttempts: 1 backoffBaseMs: 0 backoffMaxMs: 1 postgres: - poolMax: 2 + poolMax: 4 queryTimeoutMs: 3000 readyTimeoutMs: 2000 cache: diff --git a/internal/workbenchruntime/service.go b/internal/workbenchruntime/service.go index b0d53846..b805250e 100644 --- a/internal/workbenchruntime/service.go +++ b/internal/workbenchruntime/service.go @@ -71,12 +71,15 @@ type sessionsSummaryRevision struct { } type Config struct { - Addr string - DatabaseURL string - QueryTimeout time.Duration - ReadinessDelay time.Duration - PoolMax int - Cache cacheConfig + Addr string + DatabaseURL string + QueryTimeout time.Duration + ReadinessDelay time.Duration + PoolMax int + SessionSummaryMaxAttempts int + SessionSummaryAttemptTimeout time.Duration + SessionSummaryRetryBackoff time.Duration + Cache cacheConfig } type Server struct { @@ -1442,16 +1445,17 @@ func (s *Server) dbQueryAttrs(table string, argCount int, extra map[string]any) func (s *Server) querySessionSummaries(ctx context.Context, stage string, sqlText string, args []any) ([]any, error) { var lastErr error - for attempt := 1; attempt <= sessionSummaryQueryMaxAttempts; attempt++ { - attemptResult, err := s.querySessionSummariesAttempt(ctx, stage, sqlText, args, attempt, sessionSummaryQueryMaxAttempts) + maxAttempts := positiveInt(s.config.SessionSummaryMaxAttempts, sessionSummaryQueryMaxAttempts) + for attempt := 1; attempt <= maxAttempts; attempt++ { + attemptResult, err := s.querySessionSummariesAttempt(ctx, stage, sqlText, args, attempt, maxAttempts) if err == nil { return attemptResult, nil } lastErr = err - if attempt == sessionSummaryQueryMaxAttempts || !retryableSessionSummaryQueryError(err) || ctx.Err() != nil { + if attempt == maxAttempts || !retryableSessionSummaryQueryError(err) || ctx.Err() != nil { break } - backoff := sessionSummaryRetryBackoff(attempt) + backoff := s.sessionSummaryRetryBackoff(attempt) timer := time.NewTimer(backoff) select { case <-ctx.Done(): @@ -1465,7 +1469,7 @@ func (s *Server) querySessionSummaries(ctx context.Context, stage string, sqlTex func (s *Server) querySessionSummariesAttempt(ctx context.Context, stage string, sqlText string, args []any, attempt int, maxAttempts int) ([]any, error) { result := []any{} - attemptCtx, cancel := context.WithTimeout(ctx, sessionSummaryQueryAttemptTimeout) + attemptCtx, cancel := context.WithTimeout(ctx, positiveDuration(s.config.SessionSummaryAttemptTimeout, sessionSummaryQueryAttemptTimeout)) defer cancel() attrs := s.dbQueryAttrs("workbench_sessions", len(args), map[string]any{ "db.index.expected": "idx_workbench_sessions_owner_updated|idx_workbench_sessions_updated", @@ -1473,7 +1477,7 @@ func (s *Server) querySessionSummariesAttempt(ctx context.Context, stage string, "retryMax": int64(maxAttempts), }) if attempt > 1 { - attrs["retryBackoffMs"] = sessionSummaryRetryBackoff(attempt - 1).Milliseconds() + attrs["retryBackoffMs"] = s.sessionSummaryRetryBackoff(attempt - 1).Milliseconds() } err := s.withOtelInternalSpan(attemptCtx, stage, attrs, func(spanCtx context.Context) error { rows, err := s.db.QueryContext(spanCtx, sqlText, args...) @@ -1513,11 +1517,26 @@ func (s *Server) querySessionSummariesAttempt(ctx context.Context, stage string, return result, err } -func sessionSummaryRetryBackoff(attempt int) time.Duration { +func (s *Server) sessionSummaryRetryBackoff(attempt int) time.Duration { + base := positiveDuration(s.config.SessionSummaryRetryBackoff, sessionSummaryQueryRetryBackoff) if attempt <= 1 { - return sessionSummaryQueryRetryBackoff + return base } - return time.Duration(attempt) * sessionSummaryQueryRetryBackoff + return time.Duration(attempt) * base +} + +func positiveDuration(value time.Duration, fallback time.Duration) time.Duration { + if value > 0 { + return value + } + return fallback +} + +func positiveInt(value int, fallback int) int { + if value > 0 { + return value + } + return fallback } func retryableSessionSummaryQueryError(err error) bool { @@ -2310,12 +2329,15 @@ func normalizeProjectionHealth(value string, projectionStatus string) string { func configFromEnv() Config { port := first(os.Getenv("HWLAB_WORKBENCH_RUNTIME_PORT"), os.Getenv("PORT"), "6671") return Config{ - Addr: ":" + port, - DatabaseURL: first(os.Getenv("HWLAB_WORKBENCH_RUNTIME_DB_URL"), os.Getenv("HWLAB_CLOUD_DB_URL")), - QueryTimeout: time.Duration(envInt("HWLAB_WORKBENCH_RUNTIME_QUERY_TIMEOUT_MS", 10000)) * time.Millisecond, - ReadinessDelay: time.Duration(envInt("HWLAB_WORKBENCH_RUNTIME_READY_TIMEOUT_MS", 2000)) * time.Millisecond, - PoolMax: envInt("HWLAB_WORKBENCH_RUNTIME_DB_POOL_MAX", 8), - Cache: cacheConfigFromEnv(), + Addr: ":" + port, + DatabaseURL: first(os.Getenv("HWLAB_WORKBENCH_RUNTIME_DB_URL"), os.Getenv("HWLAB_CLOUD_DB_URL")), + QueryTimeout: time.Duration(envInt("HWLAB_WORKBENCH_RUNTIME_QUERY_TIMEOUT_MS", 10000)) * time.Millisecond, + ReadinessDelay: time.Duration(envInt("HWLAB_WORKBENCH_RUNTIME_READY_TIMEOUT_MS", 2000)) * time.Millisecond, + PoolMax: envInt("HWLAB_WORKBENCH_RUNTIME_DB_POOL_MAX", 8), + SessionSummaryMaxAttempts: envInt("HWLAB_WORKBENCH_SESSIONS_SUMMARY_QUERY_MAX_ATTEMPTS", sessionSummaryQueryMaxAttempts), + SessionSummaryAttemptTimeout: time.Duration(envInt("HWLAB_WORKBENCH_SESSIONS_SUMMARY_QUERY_ATTEMPT_TIMEOUT_MS", int(sessionSummaryQueryAttemptTimeout/time.Millisecond))) * time.Millisecond, + SessionSummaryRetryBackoff: time.Duration(envInt("HWLAB_WORKBENCH_SESSIONS_SUMMARY_QUERY_BACKOFF_MS", int(sessionSummaryQueryRetryBackoff/time.Millisecond))) * time.Millisecond, + Cache: cacheConfigFromEnv(), } } diff --git a/scripts/gitops-render.mjs b/scripts/gitops-render.mjs index 2f8019b5..fae9e9b3 100644 --- a/scripts/gitops-render.mjs +++ b/scripts/gitops-render.mjs @@ -941,6 +941,7 @@ function deployServicesForProfile(deploy, profile) { runtimeStoreEnvForProfile(deploy, profile, service.serviceId), workbenchRuntimeClientEnvForProfile(deploy, profile, service.serviceId), workbenchRuntimeFactsQueryEnvForProfile(deploy, profile, service.serviceId), + workbenchRuntimeSessionsSummaryEnvForProfile(deploy, profile, service.serviceId), workbenchRuntimePostgresEnvForProfile(deploy, profile, service.serviceId), workbenchRuntimeRedisEnvForProfile(deploy, profile, service.serviceId) ); @@ -957,6 +958,7 @@ function deployServicesForProfile(deploy, profile) { runtimeStoreEnvForProfile(deploy, profile, override.serviceId), workbenchRuntimeClientEnvForProfile(deploy, profile, override.serviceId), workbenchRuntimeFactsQueryEnvForProfile(deploy, profile, override.serviceId), + workbenchRuntimeSessionsSummaryEnvForProfile(deploy, profile, override.serviceId), workbenchRuntimePostgresEnvForProfile(deploy, profile, override.serviceId), workbenchRuntimeRedisEnvForProfile(deploy, profile, override.serviceId) ) @@ -1075,6 +1077,17 @@ function workbenchRuntimeFactsQueryEnvForProfile(deploy, profile, serviceId) { return env; } +function workbenchRuntimeSessionsSummaryEnvForProfile(deploy, profile, serviceId) { + if (!isRuntimeLane(profile) || serviceId !== "hwlab-workbench-runtime") return {}; + const sessionsSummary = runtimeLaneConfig(deploy, profile)?.workbenchRuntime?.sessionsSummary; + if (!sessionsSummary || typeof sessionsSummary !== "object" || Array.isArray(sessionsSummary)) return {}; + const env = {}; + if (Number.isInteger(sessionsSummary.maxAttempts)) env.HWLAB_WORKBENCH_SESSIONS_SUMMARY_QUERY_MAX_ATTEMPTS = String(sessionsSummary.maxAttempts); + if (Number.isInteger(sessionsSummary.attemptTimeoutMs)) env.HWLAB_WORKBENCH_SESSIONS_SUMMARY_QUERY_ATTEMPT_TIMEOUT_MS = String(sessionsSummary.attemptTimeoutMs); + if (Number.isInteger(sessionsSummary.backoffMs)) env.HWLAB_WORKBENCH_SESSIONS_SUMMARY_QUERY_BACKOFF_MS = String(sessionsSummary.backoffMs); + return env; +} + function workbenchRuntimePostgresEnvForProfile(deploy, profile, serviceId) { if (!isRuntimeLane(profile) || serviceId !== "hwlab-workbench-runtime") return {}; const postgres = runtimeLaneConfig(deploy, profile)?.workbenchRuntime?.postgres;