Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code be40eb7484 Slow POST /metadata (GetCurrentUser) due to PostgreSQL connection pool cold start
https://sonarly.com/issue/12283?type=bug

The `GetCurrentUser` GraphQL query on `POST /metadata` takes ~2.9s during workspace creation because 6 concurrent `pg.connect` operations each take 1.1-1.4s to establish new TCP connections to PostgreSQL, indicating an exhausted or cold connection pool.

Fix: Added a `PG_POOL_MIN_CONNECTIONS` configuration variable (default: 5) to maintain a minimum number of warm PostgreSQL connections in the pool, preventing the cold-start latency observed in the Sentry trace.

The trace showed 6 concurrent `pg.connect` operations each taking 1.1-1.4 seconds — these were new TCP+SSL connection establishments because the pool had no available connections. By setting a minimum pool size, the `pg.Pool` will proactively maintain at least 5 connections, so burst queries during auth middleware execution (GetCurrentUser resolver) can reuse existing connections instead of waiting for new ones.

**Changes:**

1. **`config-variables.ts`**: Added `PG_POOL_MIN_CONNECTIONS` config variable with default value of 5, following the exact same pattern as `PG_POOL_MAX_CONNECTIONS` (same decorators, metadata group, type).

2. **`global-workspace-datasource.service.ts`**: Added `min` to the `extra` pool options for both primary and replica DataSource configurations, reading from `PG_POOL_MIN_CONNECTIONS`.

3. **`core.datasource.ts`**: Added `min` to the core DataSource's `extra` config, using `process.env.PG_POOL_MIN_CONNECTIONS` (matching the existing pattern of using raw env vars in this static config file).

The `min` option is a standard `pg.Pool` parameter that TypeORM passes through via the `extra` config. This ensures the pool eagerly creates and maintains connections rather than starting from zero.
2026-03-10 10:19:40 +00:00
Sonarly Claude Code dd265a5f0f Workflow error handler fails when workflow run record is already deleted
https://sonarly.com/issue/4404?type=bug

The catch block in RunWorkflowJob.handle() unconditionally calls endWorkflowRun() which requires the workflow run to exist, causing a cascading "Workflow run not found" error when the run record was deleted between job enqueue and execution.

Fix: When a workflow run is deleted (soft-deleted by user action or hard-deleted by the cleanup cron) while a job is still queued, the worker picks up the job and calls `getWorkflowRunOrFail()` which throws `WORKFLOW_RUN_NOT_FOUND`. The catch block then tries to call `endWorkflowRun()` to mark the run as failed, but `endWorkflowRun()` also calls `getWorkflowRunOrFail()` — causing the same error to cascade.

**Fix:** Added an early return in the catch blocks of both `RunWorkflowJob` and `ResumeDelayedWorkflowJob` when the error is `WORKFLOW_RUN_NOT_FOUND`. If the workflow run doesn't exist, there's nothing to mark as failed — the job should exit silently. This matches the team's existing pattern of early returns for expected workflow states (e.g., returning when status isn't ENQUEUED/NOT_STARTED in `startWorkflowExecution`).
2026-03-10 10:03:44 +00:00
3 changed files with 14 additions and 0 deletions
@@ -73,6 +73,7 @@ export const typeORMCoreModuleOptions: TypeOrmModuleOptions = {
: undefined,
extra: {
query_timeout: 15000,
min: parseInt(process.env.PG_POOL_MIN_CONNECTIONS ?? '5', 10),
},
};
@@ -927,6 +927,17 @@ export class ConfigVariables {
@IsOptional()
PG_POOL_MAX_CONNECTIONS = 10;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.SERVER_CONFIG,
description:
'Minimum number of clients maintained in pg connection pool to avoid cold start latency',
isEnvOnly: true,
type: ConfigVariableType.NUMBER,
})
@CastToPositiveNumber()
@IsOptional()
PG_POOL_MIN_CONNECTIONS = 5;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.SERVER_CONFIG,
description: 'Idle timeout in milliseconds for pg connection pool clients',
@@ -44,6 +44,7 @@ export class GlobalWorkspaceDataSourceService
query_timeout: this.twentyConfigService.get(
'PG_DATABASE_PRIMARY_TIMEOUT_MS',
),
min: this.twentyConfigService.get('PG_POOL_MIN_CONNECTIONS'),
idleTimeoutMillis: this.twentyConfigService.get(
'PG_POOL_IDLE_TIMEOUT_MS',
),
@@ -79,6 +80,7 @@ export class GlobalWorkspaceDataSourceService
query_timeout: this.twentyConfigService.get(
'PG_DATABASE_REPLICA_TIMEOUT_MS',
),
min: this.twentyConfigService.get('PG_POOL_MIN_CONNECTIONS'),
idleTimeoutMillis: this.twentyConfigService.get(
'PG_POOL_IDLE_TIMEOUT_MS',
),