From b7ff587b5e304ad6a1ba77a9f2e9589c7a36d09a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 10 Feb 2026 14:14:06 +0100 Subject: [PATCH] Query cache instead of database for event listener webhook, logicFunction, triggers (#17824) ## Fix Replaced direct database queries with existing flat entity map caches for the two that already have cache infrastructure: - **CallDatabaseEventTriggerJobsJob** - now uses flatLogicFunctionMaps cache via WorkspaceCacheService.getOrRecompute(), filtering in memory for non-deleted logic functions with databaseEventTriggerSettings. - **CallWebhookJobsJob** - now uses flatWebhookMaps cache via WorkspaceCacheService.getOrRecompute(), filtering in memory for webhooks matching the event's operations. - Note: **WorkflowDatabaseEventTriggerListener** - left as-is since WorkflowAutomatedTriggerWorkspaceEntity extends BaseWorkspaceEntity (not SyncableEntity) and has no flat entity map cache infrastructure yet. --- .../logic-function-trigger.module.ts | 2 ++ .../call-database-event-trigger-jobs.job.ts | 31 ++++++++++--------- ...transform-event-batch-to-event-payloads.ts | 5 ++- .../webhook/jobs/call-webhook-jobs.job.ts | 29 +++++++++-------- .../webhook/jobs/webhook-job.module.ts | 5 ++- ...transform-event-batch-to-webhook-events.ts | 2 +- 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/logic-function-trigger.module.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/logic-function-trigger.module.ts index e90d457776..f23e2b384e 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/logic-function-trigger.module.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/logic-function-trigger.module.ts @@ -10,12 +10,14 @@ import { CallDatabaseEventTriggerJobsJob } from 'src/engine/core-modules/logic-f import { RouteTriggerService } from 'src/engine/core-modules/logic-function/logic-function-trigger/triggers/route/route-trigger.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; @Module({ imports: [ TypeOrmModule.forFeature([LogicFunctionEntity, WorkspaceEntity]), TokenModule, WorkspaceDomainsModule, + WorkspaceCacheModule, ], providers: [ LogicFunctionTriggerJob, diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/call-database-event-trigger-jobs.job.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/call-database-event-trigger-jobs.job.ts index b8e89502cd..7c340de4f2 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/call-database-event-trigger-jobs.job.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/call-database-event-trigger-jobs.job.ts @@ -1,8 +1,5 @@ -import { InjectRepository } from '@nestjs/typeorm'; - import chunk from 'lodash.chunk'; import { isDefined } from 'twenty-shared/utils'; -import { IsNull, Not, Repository } from 'typeorm'; import type { ObjectRecordEvent } from 'twenty-shared/database-events'; @@ -16,7 +13,7 @@ import { LogicFunctionTriggerJob, LogicFunctionTriggerJobData, } from 'src/engine/core-modules/logic-function/logic-function-trigger/jobs/logic-function-trigger.job'; -import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; const DATABASE_EVENT_JOBS_CHUNK_SIZE = 20; @@ -26,20 +23,26 @@ export class CallDatabaseEventTriggerJobsJob { constructor( @InjectMessageQueue(MessageQueue.logicFunctionQueue) private readonly messageQueueService: MessageQueueService, - @InjectRepository(LogicFunctionEntity) - private readonly logicFunctionRepository: Repository, + private readonly workspaceCacheService: WorkspaceCacheService, ) {} @Process(CallDatabaseEventTriggerJobsJob.name) async handle(workspaceEventBatch: WorkspaceEventBatch) { - const logicFunctionsWithDatabaseEventTrigger = - await this.logicFunctionRepository.find({ - where: { - workspaceId: workspaceEventBatch.workspaceId, - databaseEventTriggerSettings: Not(IsNull()), - }, - select: ['id', 'databaseEventTriggerSettings', 'workspaceId'], - }); + const { flatLogicFunctionMaps } = + await this.workspaceCacheService.getOrRecompute( + workspaceEventBatch.workspaceId, + ['flatLogicFunctionMaps'], + ); + + const logicFunctionsWithDatabaseEventTrigger = Object.values( + flatLogicFunctionMaps.byUniversalIdentifier, + ) + .filter(isDefined) + .filter( + (logicFunction) => + !isDefined(logicFunction.deletedAt) && + isDefined(logicFunction.databaseEventTriggerSettings), + ); const logicFunctionsToTrigger = logicFunctionsWithDatabaseEventTrigger.filter((logicFunction) => diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/utils/transform-event-batch-to-event-payloads.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/utils/transform-event-batch-to-event-payloads.ts index fe62ed7bda..01ba149d5a 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/utils/transform-event-batch-to-event-payloads.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-trigger/triggers/database-event/utils/transform-event-batch-to-event-payloads.ts @@ -14,7 +14,10 @@ export const transformEventBatchToEventPayloads = ({ logicFunctions, }: { workspaceEventBatch: WorkspaceEventBatch; - logicFunctions: LogicFunctionEntity[]; + logicFunctions: Pick< + LogicFunctionEntity, + 'id' | 'workspaceId' | 'databaseEventTriggerSettings' + >[]; }): LogicFunctionTriggerJobData[] => { const result: LogicFunctionTriggerJobData[] = []; const { events, ...batchEventInfo } = workspaceEventBatch; diff --git a/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs.job.ts b/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs.job.ts index ea592dff2e..d93fd2aa00 100644 --- a/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs.job.ts +++ b/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/call-webhook-jobs.job.ts @@ -1,8 +1,7 @@ import { Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import chunk from 'lodash.chunk'; -import { ArrayContains, IsNull, Repository } from 'typeorm'; +import { isDefined } from 'twenty-shared/utils'; import type { ObjectRecordEvent } from 'twenty-shared/database-events'; @@ -16,7 +15,7 @@ import { type CallWebhookJobData, } from 'src/engine/metadata-modules/webhook/jobs/call-webhook.job'; import { transformEventBatchToWebhookEvents } from 'src/engine/metadata-modules/webhook/utils/transform-event-batch-to-webhook-events'; -import { WebhookEntity } from 'src/engine/metadata-modules/webhook/entities/webhook.entity'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; const WEBHOOK_JOBS_CHUNK_SIZE = 20; @@ -27,8 +26,7 @@ export class CallWebhookJobsJob { constructor( @InjectMessageQueue(MessageQueue.webhookQueue) private readonly messageQueueService: MessageQueueService, - @InjectRepository(WebhookEntity) - private readonly webhookRepository: Repository, + private readonly workspaceCacheService: WorkspaceCacheService, ) {} @Process(CallWebhookJobsJob.name) @@ -42,20 +40,25 @@ export class CallWebhookJobsJob { const [nameSingular, operation] = workspaceEventBatch.name.split('.'); - const operations = [ + const operationsToMatch = [ `${nameSingular}.${operation}`, `*.${operation}`, `${nameSingular}.*`, '*.*', ]; - const webhooks = await this.webhookRepository.find({ - where: operations.map((op) => ({ - workspaceId: workspaceEventBatch.workspaceId, - operations: ArrayContains([op]), - deletedAt: IsNull(), - })), - }); + const { flatWebhookMaps } = await this.workspaceCacheService.getOrRecompute( + workspaceEventBatch.workspaceId, + ['flatWebhookMaps'], + ); + + const webhooks = Object.values(flatWebhookMaps.byUniversalIdentifier) + .filter(isDefined) + .filter((webhook) => + operationsToMatch.some((operationToMatch) => + webhook.operations.includes(operationToMatch), + ), + ); const webhookEvents = transformEventBatchToWebhookEvents({ workspaceEventBatch, diff --git a/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/webhook-job.module.ts b/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/webhook-job.module.ts index 1d7d90e619..f8976544b9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/webhook-job.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/webhook/jobs/webhook-job.module.ts @@ -1,19 +1,18 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module'; -import { WebhookEntity } from 'src/engine/metadata-modules/webhook/entities/webhook.entity'; import { CallWebhookJobsJob } from 'src/engine/metadata-modules/webhook/jobs/call-webhook-jobs.job'; import { CallWebhookJob } from 'src/engine/metadata-modules/webhook/jobs/call-webhook.job'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; @Module({ imports: [ - TypeOrmModule.forFeature([WebhookEntity]), AuditModule, MetricsModule, SecureHttpClientModule, + WorkspaceCacheModule, ], providers: [CallWebhookJobsJob, CallWebhookJob], }) diff --git a/packages/twenty-server/src/engine/metadata-modules/webhook/utils/transform-event-batch-to-webhook-events.ts b/packages/twenty-server/src/engine/metadata-modules/webhook/utils/transform-event-batch-to-webhook-events.ts index 495605b3c4..fcbe34879b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/webhook/utils/transform-event-batch-to-webhook-events.ts +++ b/packages/twenty-server/src/engine/metadata-modules/webhook/utils/transform-event-batch-to-webhook-events.ts @@ -10,7 +10,7 @@ export const transformEventBatchToWebhookEvents = ({ webhooks, }: { workspaceEventBatch: WorkspaceEventBatch; - webhooks: WebhookEntity[]; + webhooks: Pick[]; }): CallWebhookJobData[] => { const result: CallWebhookJobData[] = [];