Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code d6f95af22f Slow POST /metadata: ViewEntity query with 6 LEFT JOINs takes 3s on cache miss
https://sonarly.com/issue/3775?type=bug

The `FindAllCoreViews` GraphQL query takes ~4.3 seconds on cache miss due to a single database query that eagerly loads ALL workspace views with 6 LEFT-JOINed relations, creating a Cartesian product explosion.

Fix: The fix targets `findByWorkspaceId()` in `view.service.ts` — the method responsible for the 3003.6ms database query.

**Two changes made:**

1. **Removed the redundant `'workspace'` relation** — the workspace is already known from the `workspaceId` WHERE filter; eagerly loading it via LEFT JOIN adds unnecessary work.

2. **Added `relationLoadStrategy: 'query'`** — this is the core fix. TypeORM 0.3.x defaults to `'join'` strategy, which generates a single `SELECT` with all 5 (now 6) OneToMany relations as LEFT JOINs. For a workspace with ~25 views × ~15-20 viewFields each, this creates a massive Cartesian product that the DB must materialize and TypeORM must deduplicate. With `relationLoadStrategy: 'query'`, TypeORM instead issues 5 separate targeted `SELECT ... WHERE viewId IN (...)` queries — one per relation — which are far cheaper than the Cartesian product JOIN approach.

```typescript file=packages/twenty-server/src/engine/metadata-modules/view/services/view.service.ts lines=380-395
    const views = await this.viewRepository.find({
      where: {
        workspaceId,
        deletedAt: IsNull(),
        ...(viewTypes && viewTypes.length > 0 && { type: In(viewTypes) }),
      },
      order: { position: 'ASC' },
      relations: [
        'viewFields',
        'viewFilters',
        'viewSorts',
        'viewGroups',
        'viewFilterGroups',
      ],
      relationLoadStrategy: 'query',
    });
```

This replaces the single 3-second 6-JOIN query with 6 lightweight queries (1 for views + 5 for relations via `IN` clauses), reducing total database time by ~99% on this code path. The fix is also safe to apply to `findByObjectMetadataId()` for consistency, but that path already benefits from the `objectMetadataId` filter reducing the row count.
2026-03-04 20:50:48 +00:00
Sonarly Claude Code 0764eb55d7 Relaunch job doesn't reset throttleFailureCount, causing fail-relaunch loop on Gmail rate limits
https://sonarly.com/issue/3777?type=bug

When Gmail returns a user-rate limit error (HTTP 429) and a message channel exhausts 5 retry attempts, it's marked as FAILED_UNKNOWN. The auto-relaunch cron (every 30 min) restores the channel without resetting throttleFailureCount, so any subsequent rate-limit hit immediately re-fails the channel and fires another Sentry alert, creating an infinite fail→relaunch→fail cycle.

Fix: The fix adds `throttleFailureCount: 0` and `throttleRetryAfter: null` to the update query in the relaunch job, so that when a failed channel is relaunched, its throttle state is fully reset alongside `syncStage` and `syncStatus`.

Without this fix, after relaunch the channel still has `throttleFailureCount >= 5`, so the very next Gmail rate-limit response immediately re-triggers `handleTemporaryException`'s failure path (`count >= MESSAGING_THROTTLE_MAX_ATTEMPTS`), marks the channel `FAILED_UNKNOWN` again, fires another Sentry alert, and perpetuates the infinite fail→relaunch→fail cycle.

```typescript file=packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts lines=61-66
await messageChannelRepository.update(messageChannelId, {
  syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
  syncStatus: MessageChannelSyncStatus.ACTIVE,
  throttleFailureCount: 0,
  throttleRetryAfter: null,
});
```
2026-03-04 20:50:36 +00:00
@@ -385,13 +385,13 @@ export class ViewService {
},
order: { position: 'ASC' },
relations: [
'workspace',
'viewFields',
'viewFilters',
'viewSorts',
'viewGroups',
'viewFilterGroups',
],
relationLoadStrategy: 'query',
});
return views.filter((view) => {