feat: Add instance ID and implement Redis lock for global command registration to support multi-instance deployments.

This commit is contained in:
이정수 2026-03-27 18:29:46 +09:00
parent 031a8b3146
commit 5e41bea74e
3 changed files with 22 additions and 5 deletions

View File

@ -1,6 +1,11 @@
import { config } from 'dotenv';
import { hostname } from 'os';
config();
const generateInstanceId = () => {
return process.env.INSTANCE_ID || hostname() || `kord-${Math.random().toString(36).substring(2, 7)}`;
};
export const env = {
NODE_ENV: process.env.NODE_ENV || 'development',
DISCORD_TOKEN: process.env.DISCORD_TOKEN || '',
@ -10,4 +15,5 @@ export const env = {
REDIS_PORT: parseInt(process.env.REDIS_PORT || '6379', 10),
VOICE_WAITING_ROOM_ID: process.env.VOICE_WAITING_ROOM_ID || '',
VOICE_CATEGORY_ID: process.env.VOICE_CATEGORY_ID || '',
INSTANCE_ID: generateInstanceId(),
};

View File

@ -5,6 +5,8 @@ import { InviteService } from '../services/InviteService';
import { VoiceService } from '../services/VoiceService';
import { PresenceService } from '../services/PresenceService';
import { auditLogService } from '../services/AuditLogService';
import { redis } from '../cache';
import { env } from '../config/env';
export default {
name: Events.ClientReady,
@ -16,9 +18,17 @@ export default {
PresenceService.startActivePresence(client);
try {
const lockKey = 'commands:sync:lock';
// EX 300 = 5 minutes lock. Only one instance needs to do this per boot cycle.
const acquired = await redis.set(lockKey, '1', 'EX', 300, 'NX');
if (acquired) {
const commandsData = Array.from(client.commands.values()).map(c => c.data.toJSON());
await client.application?.commands.set(commandsData);
logger.info(`Successfully registered ${commandsData.length} global application commands.`);
} else {
logger.info('Global commands registration skipped (already handled by another instance).');
}
} catch (e) {
logger.error('Failed to register global commands', e);
}
@ -28,7 +38,7 @@ export default {
category: 'BOOT',
severity: 'INFO',
title: 'Bot Online',
description: `Kord has successfully started or reconnected.`
description: `Kord instance **[${env.INSTANCE_ID}]** has successfully started or reconnected.`
}).catch(() => {});
});
},

View File

@ -1,5 +1,6 @@
import { Guild, EmbedBuilder, TextChannel, Colors } from 'discord.js';
import { prisma } from '../database';
import { env } from '../config/env';
export type AuditSeverity = 'INFO' | 'WARN' | 'ERROR';
export type AuditCategory = 'SYSTEM' | 'BOOT' | 'VOICE' | 'PERMISSION' | 'INVITE' | 'MIMIC';
@ -47,7 +48,7 @@ export class AuditLogService {
.setDescription(`**${payload.title}**\n\n${payload.description}`)
.setColor(color)
.setTimestamp()
.setFooter({ text: `${icon} ${payload.severity} · Kord System` });
.setFooter({ text: `${icon} ${payload.severity} · Kord System [${env.INSTANCE_ID}]` });
if (payload.fields && payload.fields.length > 0) {
embed.addFields(payload.fields);