From 83c955f8a95b1e32c2f734e8b1dbbc98599710cb Mon Sep 17 00:00:00 2001 From: caadiq Date: Tue, 27 Jan 2026 11:59:18 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20Meilisearch=20=EB=B4=87=EC=9D=84=20?= =?UTF-8?q?=EB=8B=A8=EC=88=9C=20=EC=9D=BC=EC=9D=BC=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Watchtower 제외 라벨 추가하여 자동 업데이트 방지 - 버전 체크 방식 제거, 매일 12시 전체 동기화로 변경 - 봇 관리 UI를 다른 봇들과 동일하게 통일 (버전 → 업데이트 간격) Co-Authored-By: Claude Opus 4.5 --- backend/src/config/bots.js | 2 +- backend/src/plugins/scheduler.js | 118 +----------------- backend/src/routes/admin/bots.js | 17 +-- docker-compose.yml | 2 + .../src/components/pc/admin/bot/BotCard.jsx | 57 +++------ 5 files changed, 27 insertions(+), 169 deletions(-) diff --git a/backend/src/config/bots.js b/backend/src/config/bots.js index 48445e9..a99d3d6 100644 --- a/backend/src/config/bots.js +++ b/backend/src/config/bots.js @@ -3,7 +3,7 @@ export default [ id: 'meilisearch-sync', type: 'meilisearch', name: 'Meilisearch 동기화', - cron: '0 4 * * *', // 4시부터 5분간 버전 체크, 변경 시 동기화 + cron: '0 12 * * *', // 매일 12시 전체 동기화 enabled: true, }, { diff --git a/backend/src/plugins/scheduler.js b/backend/src/plugins/scheduler.js index f7ed742..0de1257 100644 --- a/backend/src/plugins/scheduler.js +++ b/backend/src/plugins/scheduler.js @@ -1,7 +1,7 @@ import fp from 'fastify-plugin'; import cron from 'node-cron'; 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'; const REDIS_PREFIX = 'bot:status:'; @@ -48,121 +48,13 @@ async function schedulerPlugin(fastify, opts) { return fastify.xBot.syncNewTweets; } else if (bot.type === 'meilisearch') { return async () => { - const count = await syncWithRetry(fastify.meilisearch, fastify.db); + const count = await syncAllSchedules(fastify.meilisearch, fastify.db); return { addedCount: count, total: count }; }; } 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); } - // Meilisearch는 버전 체크 방식 사용 - if (bot.type === 'meilisearch') { - await startMeilisearchVersionCheck(botId, bot); - return; - } - const syncFn = getSyncFunction(bot); if (!syncFn) { throw new Error(`지원하지 않는 봇 타입: ${bot.type}`); diff --git a/backend/src/routes/admin/bots.js b/backend/src/routes/admin/bots.js index bc87438..e75980d 100644 --- a/backend/src/routes/admin/bots.js +++ b/backend/src/routes/admin/bots.js @@ -62,14 +62,17 @@ export default async function botsRoutes(fastify) { for (const bot of bots) { const status = await scheduler.getStatus(bot.id); - // cron 표현식에서 간격 추출 (분 단위) + // cron 표현식에서 간격 추출 (분 단위, 일일 스케줄은 1440분) let checkInterval = 2; // 기본값 const cronMatch = bot.cron.match(/^\*\/(\d+)/); if (cronMatch) { checkInterval = parseInt(cronMatch[1]); + } else if (/^0 \d+ \* \* \*$/.test(bot.cron)) { + // 매일 특정 시간 (예: 0 12 * * *) + checkInterval = 1440; // 24시간 = 1440분 } - const botData = { + result.push({ id: bot.id, name: bot.name || bot.channelName || bot.username || bot.id, type: bot.type, @@ -81,15 +84,7 @@ export default async function botsRoutes(fastify) { check_interval: checkInterval, error_message: status.errorMessage, enabled: bot.enabled, - }; - - // Meilisearch 봇인 경우 버전 정보 추가 - if (bot.type === 'meilisearch') { - const version = await redis.get('meilisearch:version'); - botData.version = version || '-'; - } - - result.push(botData); + }); } return result; diff --git a/docker-compose.yml b/docker-compose.yml index cbe6fd1..c797c07 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,8 @@ services: meilisearch: image: getmeili/meilisearch:latest container_name: fromis9-meilisearch + labels: + - "com.centurylinklabs.watchtower.enable=false" environment: - MEILI_MASTER_KEY=${MEILI_MASTER_KEY} volumes: diff --git a/frontend/src/components/pc/admin/bot/BotCard.jsx b/frontend/src/components/pc/admin/bot/BotCard.jsx index 5df1c23..85737b6 100644 --- a/frontend/src/components/pc/admin/bot/BotCard.jsx +++ b/frontend/src/components/pc/admin/bot/BotCard.jsx @@ -142,47 +142,22 @@ const BotCard = memo(function BotCard({ {/* 통계 정보 */}
- {bot.type === 'meilisearch' ? ( - <> -
-
- {bot.last_added_count?.toLocaleString() || '-'} -
-
동기화 수
-
-
-
- {bot.last_sync_duration != null - ? `${(bot.last_sync_duration / 1000).toFixed(1)}초` - : '-'} -
-
소요 시간
-
-
-
{bot.version || '-'}
-
버전
-
- - ) : ( - <> -
-
{bot.schedules_added}
-
총 추가
-
-
-
0 ? 'text-green-500' : 'text-gray-400'}`} - > - +{bot.last_added_count || 0} -
-
마지막
-
-
-
{formatInterval(bot.check_interval)}
-
업데이트 간격
-
- - )} +
+
{bot.schedules_added || 0}
+
총 추가
+
+
+
0 ? 'text-green-500' : 'text-gray-400'}`} + > + +{bot.last_added_count || 0} +
+
마지막
+
+
+
{formatInterval(bot.check_interval)}
+
업데이트 간격
+
{/* 오류 메시지 */}