refactor: Meilisearch 봇을 단순 일일 동기화 방식으로 변경
- Watchtower 제외 라벨 추가하여 자동 업데이트 방지 - 버전 체크 방식 제거, 매일 12시 전체 동기화로 변경 - 봇 관리 UI를 다른 봇들과 동일하게 통일 (버전 → 업데이트 간격) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dc216a0f98
commit
83c955f8a9
5 changed files with 27 additions and 169 deletions
|
|
@ -3,7 +3,7 @@ export default [
|
||||||
id: 'meilisearch-sync',
|
id: 'meilisearch-sync',
|
||||||
type: 'meilisearch',
|
type: 'meilisearch',
|
||||||
name: 'Meilisearch 동기화',
|
name: 'Meilisearch 동기화',
|
||||||
cron: '0 4 * * *', // 4시부터 5분간 버전 체크, 변경 시 동기화
|
cron: '0 12 * * *', // 매일 12시 전체 동기화
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import fp from 'fastify-plugin';
|
import fp from 'fastify-plugin';
|
||||||
import cron from 'node-cron';
|
import cron from 'node-cron';
|
||||||
import bots from '../config/bots.js';
|
import bots from '../config/bots.js';
|
||||||
import { syncWithRetry, getVersion } from '../services/meilisearch/index.js';
|
import { syncAllSchedules } from '../services/meilisearch/index.js';
|
||||||
import { nowKST } from '../utils/date.js';
|
import { nowKST } from '../utils/date.js';
|
||||||
|
|
||||||
const REDIS_PREFIX = 'bot:status:';
|
const REDIS_PREFIX = 'bot:status:';
|
||||||
|
|
@ -48,121 +48,13 @@ async function schedulerPlugin(fastify, opts) {
|
||||||
return fastify.xBot.syncNewTweets;
|
return fastify.xBot.syncNewTweets;
|
||||||
} else if (bot.type === 'meilisearch') {
|
} else if (bot.type === 'meilisearch') {
|
||||||
return async () => {
|
return async () => {
|
||||||
const count = await syncWithRetry(fastify.meilisearch, fastify.db);
|
const count = await syncAllSchedules(fastify.meilisearch, fastify.db);
|
||||||
return { addedCount: count, total: count };
|
return { addedCount: count, total: count };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Meilisearch 버전 체크 및 동기화 (업데이트 감지용)
|
|
||||||
*/
|
|
||||||
async function startMeilisearchVersionCheck(botId, bot) {
|
|
||||||
const REDIS_VERSION_KEY = 'meilisearch:version';
|
|
||||||
const CHECK_INTERVAL = 60 * 1000; // 1분
|
|
||||||
const CHECK_DURATION = 5 * 60 * 1000; // 5분간 체크
|
|
||||||
|
|
||||||
// 체크 시작 cron (매일 4시 KST)
|
|
||||||
const task = cron.schedule(bot.cron, async () => {
|
|
||||||
fastify.log.info(`[${botId}] 버전 체크 시작 (5분간 1분 간격)`);
|
|
||||||
await updateStatus(botId, { status: 'running' });
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
let synced = false;
|
|
||||||
let checkCount = 0;
|
|
||||||
|
|
||||||
// 초기 버전 저장
|
|
||||||
const initialVersion = await getVersion(fastify.meilisearch);
|
|
||||||
if (!initialVersion) {
|
|
||||||
fastify.log.error(`[${botId}] Meilisearch 연결 실패`);
|
|
||||||
await updateStatus(botId, { status: 'error', errorMessage: 'Meilisearch 연결 실패' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const savedVersion = await fastify.redis.get(REDIS_VERSION_KEY);
|
|
||||||
fastify.log.info(`[${botId}] 현재 버전: ${initialVersion}, 저장된 버전: ${savedVersion || '없음'}`);
|
|
||||||
|
|
||||||
// 버전이 이미 다르면 즉시 동기화
|
|
||||||
if (savedVersion && savedVersion !== initialVersion) {
|
|
||||||
fastify.log.info(`[${botId}] 버전 변경 감지! ${savedVersion} → ${initialVersion}`);
|
|
||||||
await performSync(botId, initialVersion, REDIS_VERSION_KEY);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5분간 1분 간격으로 체크
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
checkCount++;
|
|
||||||
const elapsed = Date.now() - startTime;
|
|
||||||
|
|
||||||
if (synced || elapsed >= CHECK_DURATION) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
if (!synced) {
|
|
||||||
fastify.log.info(`[${botId}] 버전 변경 없음, 체크 종료`);
|
|
||||||
await updateStatus(botId, {
|
|
||||||
status: 'running',
|
|
||||||
lastCheckAt: nowKST(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentVersion = await getVersion(fastify.meilisearch);
|
|
||||||
fastify.log.info(`[${botId}] 체크 #${checkCount}: 버전 ${currentVersion}`);
|
|
||||||
|
|
||||||
if (currentVersion && currentVersion !== initialVersion) {
|
|
||||||
synced = true;
|
|
||||||
clearInterval(intervalId);
|
|
||||||
fastify.log.info(`[${botId}] 버전 변경 감지! ${initialVersion} → ${currentVersion}`);
|
|
||||||
await performSync(botId, currentVersion, REDIS_VERSION_KEY);
|
|
||||||
}
|
|
||||||
}, CHECK_INTERVAL);
|
|
||||||
}, { timezone: TIMEZONE });
|
|
||||||
|
|
||||||
tasks.set(botId, task);
|
|
||||||
await updateStatus(botId, { status: 'running' });
|
|
||||||
fastify.log.info(`[${botId}] 버전 체크 스케줄 시작 (cron: ${bot.cron})`);
|
|
||||||
|
|
||||||
// 초기 버전 저장 (최초 실행 시)
|
|
||||||
const currentVersion = await getVersion(fastify.meilisearch);
|
|
||||||
if (currentVersion) {
|
|
||||||
const savedVersion = await fastify.redis.get(REDIS_VERSION_KEY);
|
|
||||||
if (!savedVersion) {
|
|
||||||
await fastify.redis.set(REDIS_VERSION_KEY, currentVersion);
|
|
||||||
fastify.log.info(`[${botId}] 초기 버전 저장: ${currentVersion}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 동기화 실행 및 상태 업데이트
|
|
||||||
*/
|
|
||||||
async function performSync(botId, newVersion, versionKey) {
|
|
||||||
const startTime = Date.now();
|
|
||||||
try {
|
|
||||||
const count = await syncWithRetry(fastify.meilisearch, fastify.db);
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
await fastify.redis.set(versionKey, newVersion);
|
|
||||||
await updateStatus(botId, {
|
|
||||||
status: 'running',
|
|
||||||
lastCheckAt: nowKST(),
|
|
||||||
lastAddedCount: count,
|
|
||||||
lastSyncDuration: duration,
|
|
||||||
errorMessage: null,
|
|
||||||
});
|
|
||||||
fastify.log.info(`[${botId}] 동기화 완료: ${count}개, ${duration}ms, 새 버전: ${newVersion}`);
|
|
||||||
} catch (err) {
|
|
||||||
const duration = Date.now() - startTime;
|
|
||||||
await updateStatus(botId, {
|
|
||||||
status: 'error',
|
|
||||||
lastCheckAt: nowKST(),
|
|
||||||
lastSyncDuration: duration,
|
|
||||||
errorMessage: err.message,
|
|
||||||
});
|
|
||||||
fastify.log.error(`[${botId}] 동기화 오류: ${err.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 동기화 결과 처리 (중복 코드 제거)
|
* 동기화 결과 처리 (중복 코드 제거)
|
||||||
*/
|
*/
|
||||||
|
|
@ -199,12 +91,6 @@ async function schedulerPlugin(fastify, opts) {
|
||||||
tasks.delete(botId);
|
tasks.delete(botId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Meilisearch는 버전 체크 방식 사용
|
|
||||||
if (bot.type === 'meilisearch') {
|
|
||||||
await startMeilisearchVersionCheck(botId, bot);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncFn = getSyncFunction(bot);
|
const syncFn = getSyncFunction(bot);
|
||||||
if (!syncFn) {
|
if (!syncFn) {
|
||||||
throw new Error(`지원하지 않는 봇 타입: ${bot.type}`);
|
throw new Error(`지원하지 않는 봇 타입: ${bot.type}`);
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,17 @@ export default async function botsRoutes(fastify) {
|
||||||
for (const bot of bots) {
|
for (const bot of bots) {
|
||||||
const status = await scheduler.getStatus(bot.id);
|
const status = await scheduler.getStatus(bot.id);
|
||||||
|
|
||||||
// cron 표현식에서 간격 추출 (분 단위)
|
// cron 표현식에서 간격 추출 (분 단위, 일일 스케줄은 1440분)
|
||||||
let checkInterval = 2; // 기본값
|
let checkInterval = 2; // 기본값
|
||||||
const cronMatch = bot.cron.match(/^\*\/(\d+)/);
|
const cronMatch = bot.cron.match(/^\*\/(\d+)/);
|
||||||
if (cronMatch) {
|
if (cronMatch) {
|
||||||
checkInterval = parseInt(cronMatch[1]);
|
checkInterval = parseInt(cronMatch[1]);
|
||||||
|
} else if (/^0 \d+ \* \* \*$/.test(bot.cron)) {
|
||||||
|
// 매일 특정 시간 (예: 0 12 * * *)
|
||||||
|
checkInterval = 1440; // 24시간 = 1440분
|
||||||
}
|
}
|
||||||
|
|
||||||
const botData = {
|
result.push({
|
||||||
id: bot.id,
|
id: bot.id,
|
||||||
name: bot.name || bot.channelName || bot.username || bot.id,
|
name: bot.name || bot.channelName || bot.username || bot.id,
|
||||||
type: bot.type,
|
type: bot.type,
|
||||||
|
|
@ -81,15 +84,7 @@ export default async function botsRoutes(fastify) {
|
||||||
check_interval: checkInterval,
|
check_interval: checkInterval,
|
||||||
error_message: status.errorMessage,
|
error_message: status.errorMessage,
|
||||||
enabled: bot.enabled,
|
enabled: bot.enabled,
|
||||||
};
|
});
|
||||||
|
|
||||||
// Meilisearch 봇인 경우 버전 정보 추가
|
|
||||||
if (bot.type === 'meilisearch') {
|
|
||||||
const version = await redis.get('meilisearch:version');
|
|
||||||
botData.version = version || '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(botData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ services:
|
||||||
meilisearch:
|
meilisearch:
|
||||||
image: getmeili/meilisearch:latest
|
image: getmeili/meilisearch:latest
|
||||||
container_name: fromis9-meilisearch
|
container_name: fromis9-meilisearch
|
||||||
|
labels:
|
||||||
|
- "com.centurylinklabs.watchtower.enable=false"
|
||||||
environment:
|
environment:
|
||||||
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
- MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
|
|
@ -142,47 +142,22 @@ const BotCard = memo(function BotCard({
|
||||||
|
|
||||||
{/* 통계 정보 */}
|
{/* 통계 정보 */}
|
||||||
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-gray-50/50">
|
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-gray-50/50">
|
||||||
{bot.type === 'meilisearch' ? (
|
<div className="p-3 text-center">
|
||||||
<>
|
<div className="text-lg font-bold text-gray-900">{bot.schedules_added || 0}</div>
|
||||||
<div className="p-3 text-center">
|
<div className="text-xs text-gray-400">총 추가</div>
|
||||||
<div className="text-lg font-bold text-gray-900">
|
</div>
|
||||||
{bot.last_added_count?.toLocaleString() || '-'}
|
<div className="p-3 text-center">
|
||||||
</div>
|
<div
|
||||||
<div className="text-xs text-gray-400">동기화 수</div>
|
className={`text-lg font-bold ${bot.last_added_count > 0 ? 'text-green-500' : 'text-gray-400'}`}
|
||||||
</div>
|
>
|
||||||
<div className="p-3 text-center">
|
+{bot.last_added_count || 0}
|
||||||
<div className="text-lg font-bold text-gray-900">
|
</div>
|
||||||
{bot.last_sync_duration != null
|
<div className="text-xs text-gray-400">마지막</div>
|
||||||
? `${(bot.last_sync_duration / 1000).toFixed(1)}초`
|
</div>
|
||||||
: '-'}
|
<div className="p-3 text-center">
|
||||||
</div>
|
<div className="text-lg font-bold text-gray-900">{formatInterval(bot.check_interval)}</div>
|
||||||
<div className="text-xs text-gray-400">소요 시간</div>
|
<div className="text-xs text-gray-400">업데이트 간격</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 text-center">
|
|
||||||
<div className="text-lg font-bold text-gray-900">{bot.version || '-'}</div>
|
|
||||||
<div className="text-xs text-gray-400">버전</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="p-3 text-center">
|
|
||||||
<div className="text-lg font-bold text-gray-900">{bot.schedules_added}</div>
|
|
||||||
<div className="text-xs text-gray-400">총 추가</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-3 text-center">
|
|
||||||
<div
|
|
||||||
className={`text-lg font-bold ${bot.last_added_count > 0 ? 'text-green-500' : 'text-gray-400'}`}
|
|
||||||
>
|
|
||||||
+{bot.last_added_count || 0}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-400">마지막</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-3 text-center">
|
|
||||||
<div className="text-lg font-bold text-gray-900">{formatInterval(bot.check_interval)}</div>
|
|
||||||
<div className="text-xs text-gray-400">업데이트 간격</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 오류 메시지 */}
|
{/* 오류 메시지 */}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue