diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2a07952e..26a01686 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,17 @@ -## ๐Ÿ› ๏ธ ์ž‘์—… ๋‚ด์šฉ +## ๐Ÿš€ ์ž‘์—… ๊ฐœ์š” +- +## ๐Ÿ› ๏ธ ์ž‘์—… ๋‚ด์šฉ +- ## โœ… PR ์œ ํ˜• +- [ ] ๋ฒ„๊ทธ ์ˆ˜์ • +- [ ] ์„ฑ๋Šฅ ๊ฐœ์„  - [x] ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€ -- [x] CSS ๋“ฑ ์‚ฌ์šฉ์ž UI ๋””์ž์ธ ๋ณ€๊ฒฝ - [x] ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง - [x] ํŒŒ์ผ ํ˜น์€ ํด๋”๋ช… ์ˆ˜์ • +- [ ] ๋ฌธ์„œ ์ˆ˜์ • +- [ ] ์„ค์ • ๋ณ€๊ฒฝ ## โœ… Check List - [x] ์ฝ”๋“œ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ปดํŒŒ์ผ๋˜๋‚˜์š”? @@ -14,6 +20,6 @@ - [x] Label์„ ์ง€์ •ํ–ˆ๋‚˜์š”? ## ๐Ÿ”— ๊ด€๋ จ ์ด์Šˆ -- + ## ๐Ÿ’ฌ ๊ธฐํƒ€ ์ฐธ๊ณ  ์‚ฌํ•ญ diff --git a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java index e6e4be31..452e30c3 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonApplyConsumer.java @@ -133,8 +133,7 @@ public Object execute(org.springframework.data.redis.core.RedisOperations operat } } } finally { - // ์ž‘์—… ์™„๋ฃŒ ํ›„ ๊ฐ ๋ ˆ์Šจ๋ณ„๋กœ ์ด๋ฒˆ ๋ฐฐ์น˜์—์„œ ์ฒ˜๋ฆฌํ•œ ๊ฐœ์ˆ˜๋งŒํผ Busy ์นด์šดํ„ฐ ๊ฐ์†Œ - // ์ž‘์—… ์™„๋ฃŒ ํ›„ ๊ฐ ๋ ˆ์Šจ๋ณ„๋กœ ๋งˆ์ง€๋ง‰ ์ฒ˜๋ฆฌ ์‹œ์  ๊ธฐ๋ก + // ์ž‘์—… ์™„๋ฃŒ ํ›„ ๊ฐ ๋ ˆ์Šจ๋ณ„๋กœ ์ด๋ฒˆ ๋ฐฐ์น˜์—์„œ ์ฒ˜๋ฆฌํ•œ ๊ฐœ์ˆ˜๋งŒํผ Busy ์นด์šดํ„ฐ ๊ฐ์†Œ, ๋งˆ์ง€๋ง‰ ์ฒ˜๋ฆฌ ์‹œ์  ๊ธฐ๋ก Map countsPerLesson = messages.stream() .collect(Collectors.groupingBy(ApplyMessage::lessonId, Collectors.counting())); diff --git a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonStockReconciliationScheduler.java b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonStockReconciliationScheduler.java index 29138c5e..d43c8ef4 100644 --- a/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonStockReconciliationScheduler.java +++ b/src/main/java/com/threestar/trainus/domain/lesson/issue/LessonStockReconciliationScheduler.java @@ -39,6 +39,12 @@ public class LessonStockReconciliationScheduler { @Scheduled(fixedRate = 30000) @SchedulerLock(name = "LessonStockReconciliation", lockAtMostFor = "25s", lockAtLeastFor = "20s") public void reconcileStock() { + // ๋Œ€๊ธฐ์—ด ์ž”์—ฌ ๋ฉ”์„ธ์ง€ ์กด์žฌ ์‹œ ์—ฐ๊ธฐ (Core Redis) + if (hasWaitingRoomMessages()) { + log.info("Waiting room still has messages. Postponing reconciliation."); + return; + } + // ๋ฏธ์ฒ˜๋ฆฌ ๋ฉ”์„ธ์ง€ ์กด์žฌ ์‹œ ์—ฐ๊ธฐ (MQ Redis) if (hasStreamLag()) { log.info("Stream still has pending messages or backlog in MQ. Postponing reconciliation."); @@ -75,7 +81,7 @@ public void reconcileStock() { // ์ผ์ • ์‹œ๊ฐ„ ๋ฌด์‘๋‹ต์‹œ ๊ต์ฐฉ์ƒํƒœ ๋ฐฉ์ง€ boolean isStale = (lastActiveStr != null && (now - Long.parseLong(lastActiveStr) > 600000)); // 10๋ถ„ ์ด์ƒ ๋ฌด์‘๋‹ต - boolean isRecent = (lastActiveStr != null && (now - Long.parseLong(lastActiveStr) < 5000)); // 5์ดˆ ์ด๋‚ด ํ™œ๋™ + boolean isRecent = (lastActiveStr != null && (now - Long.parseLong(lastActiveStr) < 30000)); // 30์ดˆ ์ด๋‚ด ํ™œ๋™ boolean isNegative = busyCount < 0; // busy ์นด์šดํ„ฐ ์กด์žฌ & ์‘๋‹ต 10๋ถ„ ๋ฏธ๋งŒ @@ -85,7 +91,7 @@ public void reconcileStock() { continue; } - // busy ์นด์šดํ„ฐ = 0 & 5์ดˆ ๋‚ด ํ™œ๋™์ด ์žˆ์—ˆ์Œ (DBํŠธ๋žœ์žญ์…˜ ์ž ์‹œ ๋Œ€๊ธฐ) + // busy ์นด์šดํ„ฐ = 0 & 30์ดˆ ๋‚ด ํ™œ๋™์ด ์žˆ์—ˆ์Œ (DBํŠธ๋žœ์žญ์…˜ ์ž ์‹œ ๋Œ€๊ธฐ) if (busyCount == 0 && isRecent) { log.info("Lesson [{}] recently active. Waiting for safety margin.", lessonId); continue; @@ -134,6 +140,37 @@ public void reconcileStock() { log.info("Finished Smart Stock Reconciliation for {} lessons.", processedCount); } + // ๋ฏธ์ฒ˜๋ฆฌ ๋ฉ”์„ธ์ง€ ํ™•์ธ ๋ฉ”์„œ๋“œ (Core Redis) + private boolean hasWaitingRoomMessages() { + String dirtySetKey = LessonApplyStreamConstant.DIRTY_SET_KEY; + + // Dirty Set ํ™•์ธ (Core Redis) + java.util.Set lessonIds = coreRedisTemplate.opsForSet().members(dirtySetKey); + if (lessonIds == null || lessonIds.isEmpty()) { + return false; + } + + for (String lessonIdStr : lessonIds) { + try { + Long lessonId = Long.valueOf(lessonIdStr); + + // ๋ ˆ์Šจ๋ณ„ ๋Œ€๊ธฐ์—ด ์ž”์—ฌ ๋ฉ”์„ธ์ง€ ํ™•์ธ (Core Redis) + String waitingRoomKey = String.format(LessonApplyStreamConstant.WAITING_ROOM_KEY, lessonId); + Long size = coreRedisTemplate.opsForZSet().size(waitingRoomKey); + if (size != null && size > 0) { + log.info("Waiting room still has messages. lessonId={}, size={}", lessonId, size); + return true; + } + } catch (Exception e) { + // ๋Œ€๊ธฐ์—ด ํ™•์ธ ์‹คํŒจ ์‹œ ๋ณด์ˆ˜์ ์œผ๋กœ ๋ณด์ • ์—ฐ๊ธฐ + log.warn("Failed to check waiting room for lesson [{}]: {}", lessonIdStr, e.getMessage()); + return true; + } + } + + return false; + } + // ๋ฏธ์ฒ˜๋ฆฌ ๋ฉ”์„ธ์ง€ ํ™•์ธ ๋ฉ”์„œ๋“œ (MQ Redis) private boolean hasStreamLag() { try { @@ -151,8 +188,9 @@ private boolean hasStreamLag() { return size != null && size > 0; } catch (Exception e) { - // ์ŠคํŠธ๋ฆผ์ด ์—†๊ฑฐ๋‚˜ ์ดˆ๊ธฐ ์ƒํƒœ์ผ ๊ฒฝ์šฐ - return false; + // ์ŠคํŠธ๋ฆผ ์ƒํƒœ ํ™•์ธ ์‹คํŒจ ์‹œ ๋ณด์ˆ˜์ ์œผ๋กœ ๋ณด์ • ์—ฐ๊ธฐ + log.warn("Failed to check stream lag. Postponing reconciliation: {}", e.getMessage()); + return true; } } }