Files
Automata/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts

112 lines
3.1 KiB
TypeScript

import { EventEmitter } from 'node:events';
import config from '@/config';
import { Service } from 'typedi';
import { TIME } from '@/constants';
import { getRedisPrefix } from '@/services/redis/RedisServiceHelper';
import { ErrorReporterProxy as EventReporter } from 'n8n-workflow';
import { Logger } from '@/Logger';
import { RedisServicePubSubPublisher } from '@/services/redis/RedisServicePubSubPublisher';
@Service()
export class MultiMainSetup extends EventEmitter {
constructor(
private readonly logger: Logger,
private readonly redisPublisher: RedisServicePubSubPublisher,
) {
super();
}
get instanceId() {
return config.getEnv('redis.queueModeId');
}
private readonly leaderKey = getRedisPrefix() + ':main_instance_leader';
private readonly leaderKeyTtl = config.getEnv('multiMainSetup.ttl');
private leaderCheckInterval: NodeJS.Timer | undefined;
async init() {
await this.tryBecomeLeader(); // prevent initial wait
this.leaderCheckInterval = setInterval(
async () => {
await this.checkLeader();
},
config.getEnv('multiMainSetup.interval') * TIME.SECOND,
);
}
async shutdown() {
clearInterval(this.leaderCheckInterval);
const isLeader = config.getEnv('multiMainSetup.instanceType') === 'leader';
if (isLeader) await this.redisPublisher.clear(this.leaderKey);
}
private async checkLeader() {
const leaderId = await this.redisPublisher.get(this.leaderKey);
if (leaderId === this.instanceId) {
this.logger.debug(`[Instance ID ${this.instanceId}] Leader is this instance`);
await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
return;
}
if (leaderId && leaderId !== this.instanceId) {
this.logger.debug(`[Instance ID ${this.instanceId}] Leader is other instance "${leaderId}"`);
if (config.getEnv('multiMainSetup.instanceType') === 'leader') {
config.set('multiMainSetup.instanceType', 'follower');
this.emit('leadershipChange'); // stop triggers, pollers, pruning
EventReporter.report('[Multi-main setup] Leader failed to renew leader key', {
level: 'info',
});
}
return;
}
if (!leaderId) {
this.logger.debug(
`[Instance ID ${this.instanceId}] Leadership vacant, attempting to become leader...`,
);
config.set('multiMainSetup.instanceType', 'follower');
this.emit('leadershipVacant'); // stop triggers, pollers, pruning
await this.tryBecomeLeader();
}
}
private async tryBecomeLeader() {
// this can only succeed if leadership is currently vacant
const keySetSuccessfully = await this.redisPublisher.setIfNotExists(
this.leaderKey,
this.instanceId,
);
if (keySetSuccessfully) {
this.logger.debug(`[Instance ID ${this.instanceId}] Leader is now this instance`);
config.set('multiMainSetup.instanceType', 'leader');
await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl);
this.emit('leadershipChange'); // start triggers, pollers, pruning
} else {
config.set('multiMainSetup.instanceType', 'follower');
}
}
async fetchLeaderKey() {
return await this.redisPublisher.get(this.leaderKey);
}
}