feat(core): Enable scheduled execution deduplication by default (#32533)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
mfsiega 2026-06-18 09:13:34 +02:00 committed by GitHub
parent e023e17f2f
commit dcbdb10a2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 5 additions and 48 deletions

View File

@ -154,8 +154,4 @@ export class ExecutionsConfig {
/** Whether to save execution data for manual executions. This default can be overridden at a workflow level. */
@Env('EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS')
saveDataManualExecutions: boolean = true;
/** Whether scheduled executions receive a deduplication key enforced by a unique DB index. */
@Env('N8N_SCHEDULED_EXECUTION_DEDUPLICATION_ENABLED')
scheduledExecutionDeduplicationEnabled: boolean = false;
}

View File

@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
import path from 'node:path';
import type { DatabaseConfig } from '../src/index';
import { ExecutionsConfig, GlobalConfig, SSRF_DEFAULT_BLOCKED_IP_RANGES } from '../src/index';
import { GlobalConfig, SSRF_DEFAULT_BLOCKED_IP_RANGES } from '../src/index';
const { readFileSyncMock } = vi.hoisted(() => ({
readFileSyncMock: vi.fn(),
@ -470,7 +470,6 @@ describe('GlobalConfig', () => {
saveDataOnSuccess: 'all',
saveExecutionProgress: false,
saveDataManualExecutions: true,
scheduledExecutionDeduplicationEnabled: false,
},
diagnostics: {
enabled: true,
@ -868,20 +867,4 @@ describe('GlobalConfig', () => {
expect(config.endpoints.health).toEqual('/api/v1/health');
});
});
describe('ExecutionsConfig', () => {
it('should default scheduledExecutionDeduplicationEnabled to false', () => {
process.env = {};
const config = Container.get(ExecutionsConfig);
expect(config.scheduledExecutionDeduplicationEnabled).toBe(false);
});
it('should enable scheduledExecutionDeduplicationEnabled when env var is set to true', () => {
process.env = {
N8N_SCHEDULED_EXECUTION_DEDUPLICATION_ENABLED: 'true',
};
const config = Container.get(ExecutionsConfig);
expect(config.scheduledExecutionDeduplicationEnabled).toBe(true);
});
});
});

View File

@ -1,5 +1,3 @@
import { ExecutionsConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import { sendAt } from 'cron';
import moment from 'moment-timezone';
import type {
@ -446,12 +444,6 @@ export class ScheduleTrigger implements INodeType {
const workflowId = this.getWorkflow().id;
const nodeId = this.getNode().id;
const configDedupEnabled =
Container.get(ExecutionsConfig).scheduledExecutionDeduplicationEnabled;
// The workflowId should always be defined, but if it isn't we skip
// the deduplication key.
const dedupEnabled = configDedupEnabled && Boolean(workflowId);
const executeTrigger = (
recurrence: IRecurrenceRule,
skipRecurrenceCheck = false,
@ -477,8 +469,10 @@ export class ScheduleTrigger implements INodeType {
Timezone: `${timezone} (UTC${momentTz.format('Z')})`,
};
// The workflowId should always be defined, but if it isn't we skip
// the deduplication key.
const deduplicationKey =
dedupEnabled && scheduledTime
workflowId && scheduledTime
? `${workflowId}:${nodeId}:${scheduledTime.toISOString()}`
: undefined;

View File

@ -1,5 +1,3 @@
import { ExecutionsConfig } from '@n8n/config';
import { Container } from '@n8n/di';
import * as n8nWorkflow from 'n8n-workflow';
import { testTriggerNode } from '@test/nodes/TriggerHelpers';
@ -222,19 +220,7 @@ describe('ScheduleTrigger', () => {
});
describe('deduplication key', () => {
const executionsConfig = Container.get(ExecutionsConfig);
beforeEach(() => {
executionsConfig.scheduledExecutionDeduplicationEnabled = false;
});
afterEach(() => {
executionsConfig.scheduledExecutionDeduplicationEnabled = false;
});
it('should emit a deduplication key when the feature flag is enabled', async () => {
executionsConfig.scheduledExecutionDeduplicationEnabled = true;
it('should emit a deduplication key for scheduled executions', async () => {
const workflowId = 'wf-123';
const nodeId = 'node-456';
const { emit } = await testTriggerNode(ScheduleTrigger, {
@ -269,8 +255,6 @@ describe('ScheduleTrigger', () => {
});
it('should not emit a deduplication key for manual executions', async () => {
executionsConfig.scheduledExecutionDeduplicationEnabled = true;
const { emit, manualTriggerFunction } = await testTriggerNode(ScheduleTrigger, {
mode: 'manual',
timezone,