Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 0a1ac48eea fix: serialize metadata cache recomputation to prevent connection pool exhaustion
https://sonarly.com/issue/19536?type=bug

A `CreateManyViewFields` mutation took 18.4 seconds due to 6 parallel `pg.connect` calls each waiting ~10.5 seconds for a database connection, caused by connection pool exhaustion during parallel metadata cache recomputation.

Fix: Replaced `Promise.all` with a sequential `for...of` loop in `WorkspaceCacheService.recomputeDataFromProvider()` to serialize cache provider database queries.

**Why:** When the workspace metadata cache is cold (after pod restart or cache TTL expiration), `recomputeDataFromProvider` is called with 7+ cache key names. The previous code used `Promise.all` to fire all provider `computeForCache()` calls in parallel. Each provider makes a DB query requiring a connection from the pg pool (default max: 10). Combined with 3 auth queries already running, this exceeds the pool capacity, causing `pg.connect` to block for ~10.5 seconds waiting for a free connection.

**Fix:** Sequential execution ensures only 1 cache provider query runs at a time, keeping total concurrent DB connections well within pool limits. The individual queries are fast (2-464ms each), so serialization adds negligible wall-clock time (~2s total) compared to the 10.5s pool exhaustion stall it prevents.

The type annotation on the `computed` array preserves the existing type safety from the original `Promise.all` pattern.
2026-03-30 11:16:57 +00:00
@@ -324,15 +324,29 @@ export class WorkspaceCacheService implements OnModuleInit {
return result;
}
const computePromises = cacheKeyNames.map(async (keyName) => {
if (cacheKeyNames.length > 3) {
this.logger.warn(
`Recomputing ${cacheKeyNames.length} cache keys from DB for workspace ${workspaceId}: ${cacheKeyNames.join(', ')}`,
);
}
// Serialize provider computations to avoid exhausting the pg connection
// pool when many cache keys need recomputation at once (e.g. after pod
// restart). With Promise.all, 7+ parallel DB queries can saturate a pool
// of 10 connections, causing ~10s pg.connect stalls.
const computed: Array<{
keyName: WorkspaceCacheKeyName;
data: CacheDataType;
hash: string;
}> = [];
for (const keyName of cacheKeyNames) {
const provider = this.getProviderOrThrow(keyName);
const data = await provider.computeForCache(workspaceId);
const hash = crypto.randomUUID();
return { keyName, data, hash };
});
const computed = await Promise.all(computePromises);
computed.push({ keyName, data, hash });
}
const redisEntries: Array<{ key: string; value: unknown }> = [];