From b397e1cd0c4dd24581ae851e61855e1755e34751 Mon Sep 17 00:00:00 2001
From: passionryu
Date: Thu, 24 Jul 2025 23:45:23 +0900
Subject: [PATCH] =?UTF-8?q?=20=F0=9F=9A=91=EF=B8=8F=20[=EB=B0=B0=ED=8F=AC?=
=?UTF-8?q?=20=ED=9B=84=20=EA=B8=B4=EA=B8=89=20=EC=88=98=EC=A0=95]=20:=20?=
=?UTF-8?q?=EB=89=B4=EC=8A=A4=20=EB=AA=A8=EB=8B=88=ED=84=B0=EB=A7=81=20?=
=?UTF-8?q?=EC=8B=9C=EC=8A=A4=ED=85=9C=EC=9D=98=20Count=20=EB=AC=B8?=
=?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20+=20=ED=95=AB=ED=86=A0?=
=?UTF-8?q?=ED=94=BD=EC=9D=98=20=EB=89=B4=EC=8A=A4=20=EB=8D=94=EB=B3=B4?=
=?UTF-8?q?=EA=B8=B0=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=9D=98=20=EC=B5=9C?=
=?UTF-8?q?=EC=8B=A0=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ElasticSearch/service/NewsEsService.java | 70 +++++++++-
.../IntermediateBatchRedisService.java | 8 ++
.../Service/NewsMonitoringService.java | 129 ++++++++++++++----
3 files changed, 170 insertions(+), 37 deletions(-)
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/service/NewsEsService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/service/NewsEsService.java
index f5e02f9..c46daf3 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/service/NewsEsService.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/service/NewsEsService.java
@@ -169,24 +169,38 @@ private boolean bulkInsert(List docs, String section) throws IOE
}
/**
- * 키워드로 뉴스 기사 검색
+ * 어제 날짜 기준으로 뉴스 기사 검색을 수행합니다.
*
* {@code combinedTokens} 필드에 대해 match 쿼리를 수행하고,
- * 점수(score) 기준으로 내림차순 정렬된 상위 결과를 반환합니다.
+ * {@code publishedAt} 필드가 어제(00:00~24:00)인 데이터만 필터링합니다.
+ * 결과는 점수(score) 기준으로 내림차순 정렬되어 반환됩니다.
*
* @param keyword 검색 키워드
- * @param size 최대 검색 결과 수
- * @return 검색된 뉴스 도큐먼트 리스트
+ * @param size 최대 검색 결과 수
+ * @return 어제 날짜 중 키워드를 포함하는 뉴스 도큐먼트 리스트
* @throws IOException Elasticsearch 검색 실패 시 발생
*/
public List searchByKeyword(String keyword, int size) throws IOException {
+ LocalDate today = LocalDate.now();
+ LocalDate yesterday = today.minusDays(1);
+
+ String from = yesterday.atStartOfDay().toString(); // 어제 00:00:00
+ String to = today.atStartOfDay().toString(); // 오늘 00:00:00
+
SearchResponse response = elasticsearchClient.search(s -> s
.index("news-index-nori")
.size(size)
.query(q -> q
- .match(m -> m
- .field("combinedTokens")
- .query(keyword)
+ .bool(b -> b
+ .must(m -> m.match(mm -> mm
+ .field("combinedTokens")
+ .query(keyword)
+ ))
+ .filter(f -> f.range(r -> r
+ .field("publishedAt")
+ .gte(JsonData.of(from))
+ .lt(JsonData.of(to))
+ ))
)
)
.sort(sort -> sort
@@ -235,4 +249,46 @@ public List getTopKeywordsForDateRange(LocalDate gte, LocalDa
.buckets()
.array();
}
+
+ // ======================= Deprecated =========================
+
+ /**
+ * Hot Fix
+ *
+ * Deprecated At 2025-07-24
+ * By 김원중
+ *
+ */
+// /**
+// * 키워드로 뉴스 기사 검색
+// *
+// * {@code combinedTokens} 필드에 대해 match 쿼리를 수행하고,
+// * 점수(score) 기준으로 내림차순 정렬된 상위 결과를 반환합니다.
+// *
+// * @param keyword 검색 키워드
+// * @param size 최대 검색 결과 수
+// * @return 검색된 뉴스 도큐먼트 리스트
+// * @throws IOException Elasticsearch 검색 실패 시 발생
+// */
+// public List searchByKeyword(String keyword, int size) throws IOException {
+// SearchResponse response = elasticsearchClient.search(s -> s
+// .index("news-index-nori")
+// .size(size)
+// .query(q -> q
+// .match(m -> m
+// .field("combinedTokens")
+// .query(keyword)
+// )
+// )
+// .sort(sort -> sort
+// .score(sc -> sc.order(SortOrder.Desc))
+// ),
+// NewsEsDocument.class
+// );
+//
+// return response.hits().hits().stream()
+// .map(Hit::source)
+// .filter(Objects::nonNull)
+// .toList();
+// }
}
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/IntermediateBatchRedisService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/IntermediateBatchRedisService.java
index 2499c3c..c55003b 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/IntermediateBatchRedisService.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/IntermediateBatchRedisService.java
@@ -22,6 +22,14 @@ public IntermediateBatchRedisService(@Qualifier("redisSession1Template") RedisTe
private static final String BATCH_KEY_PREFIX = "IntermediateBatch:";
+ public boolean isBatchAlreadyDone(String key) {
+ return redisSession1Template.hasKey(key);
+ }
+
+ public void markBatchDone(String key) {
+ redisSession1Template.opsForValue().set(key, "done", Duration.ofHours(12)); // 12
+ }
+
/**
* 중간 배치 실행 횟수를 조회 메서드
*
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/NewsMonitoringService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/NewsMonitoringService.java
index d890e3a..441ac5e 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/NewsMonitoringService.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/NewsMonitoring/Service/NewsMonitoringService.java
@@ -27,63 +27,132 @@ public class NewsMonitoringService {
private final JobLauncher jobLauncher;
private final Job newsDataSaveJob_Monitoring;
- /**
- * 매 정각마다 실행 되는 모니터링 스케줄러
- *
- * 각 섹션 별 현재 몇개의 데이터가 모였는지 모니터링
- * 9000개가 넘는 섹션 발견 시, 즉시 DB에 Batch 실시
- *
- */
@Scheduled(cron = "0 0 * * * *") // 매 시간 정각
//@Scheduled(cron = "0 */5 * * * *") // 5분
- // @Scheduled(cron = "0 */3 * * * *") // 3분
+ //@Scheduled(cron = "0 */3 * * * *") // 3분
//@Scheduled(cron = "0 */2 * * * *") // 2분
//@Scheduled(cron = "0 * * * * *") // 1분
- public void monitoring(){
+ public void monitoring() {
/* 날짜 & 시간 확인 */
LocalDate day = LocalDate.now(); // 오늘의 날짜
LocalDateTime time = LocalDateTime.now(); // 현재 날짜와 시간(분 단위 포함)
- log.info("// ======================= 오늘의 날짜 : {} =========================", day); // 오늘의 날짜 로그 확인
+ log.info("// ======================= 오늘의 날짜 : {} =========================", day);
log.info("현재 시간 : {}", time);
+ // 자정(00시)이면 스킵
+ if (time.getHour() == 0) {
+ log.info("[SKIP] 자정 00시에는 모니터링 작업을 실행하지 않습니다.");
+ return;
+ }
+
/* 날짜 포매팅 작업 */
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); //원하는 포맷 지정
- String dateTo = day.format(formatter); //문자열로 변환
- String dateFrom = day.format(formatter); //문자열로 변환
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ String dateStr = day.format(formatter);
/* 섹션 별 누적 데이터 수 모니터링 로직 */
for (String section : sections) {
- // 현재 섹션의 현 시각 누적 Total_Item 수 확인
- int total_items = newsMonitoringManager.getTotalItems(section, dateFrom, dateTo);
+ int total_items = newsMonitoringManager.getTotalItems(section, dateStr, dateStr);
+ String batchDoneKey = "batch_done:" + dateStr + ":" + section; // 하루 1회 체크용
+ String batchCountKey = "batch_count:" + dateStr + ":" + section; // 하루 횟수 제한용
- /* 뉴스 데이터 9000개 이상 시 중간 배치 + Redis 기록 */
- if(total_items >= 9000 && total_items <= 18000){
+ /* =================== 9000 이상 18000 이하일 경우 =================== */
+ if (total_items >= 9000 && total_items <= 18000) {
- // 해당 섹션이 이전에도 사전 Batch 작업이 실행 되었는지 확인 (Redis에서 횟수 조회)
- int n = intermediateBatchRedisService.getBatchCount(section);
- log.info("{} 섹션 기존 중간 배치 현황 : 총 {}회 중간 배치 ",section, n);
+ // 하루 한 번만 수행: 이미 했는지 Redis에 확인
+ if (intermediateBatchRedisService.isBatchAlreadyDone(batchDoneKey)) {
+ log.info("[SKIP] {} 섹션은 이미 {}에 중간 배치가 수행됨", section, dateStr);
+ continue;
+ }
- // 현재 total_item이 기준(9000개)을 넘으면 Job Launcher로 Batch 작업 진행 메서드 호출
- runNewsBatch(section);
+ int prevCount = intermediateBatchRedisService.getBatchCount(batchCountKey);
+ log.info("{} 섹션 기존 중간 배치 현황 : 총 {}회 중간 배치", section, prevCount);
- // 중간 배치가 진행된 섹션 배치 현황 기록 (Redis에 횟수 증가)
- intermediateBatchRedisService.incrementBatchCount(section);
+ runNewsBatch(section); // 배치 실행
- } /* 2번째 중간 배치 부터 주의 메시지 호출 */
- else if(total_items >= 18000){
+ intermediateBatchRedisService.incrementBatchCount(batchCountKey); // 횟수 증가
+ intermediateBatchRedisService.markBatchDone(batchDoneKey); // 하루 1회 완료 기록
- // Job Launcher로 Batch 작업 진행 메서드 호출
- runNewsBatch(section);
+ }
- // 현재 total_item이 기준(18000개)을 넘으면 Job Launcher로 Batch 작업 진행 메서드 호출
- log.info("[#주의#] {}섹션의 total_items수 : {}",section,total_items); // 추후, 하루에 3번 이상의 중간 호출이 일어나는 것에 대한 방어코드도 작성할 것
+ /* =================== 18000 초과일 경우 =================== */
+ else if (total_items > 18000) {
+ int count = intermediateBatchRedisService.getBatchCount(batchCountKey);
+
+ if (count >= 3) {
+ log.warn("[SKIP] {} 섹션은 이미 {}에 3회 이상 배치됨", section, dateStr);
+ continue;
+ }
+
+ runNewsBatch(section); // 배치 실행
+
+ intermediateBatchRedisService.incrementBatchCount(batchCountKey); // 횟수 증가
+ log.warn("[#주의#] {} 섹션의 total_items 수 : {} (누적 {}회 배치됨)", section, total_items, count + 1);
}
}
}
+ // ======================= deprecated =========================
+
+ /**
+ * 매 정각마다 실행 되는 모니터링 스케줄러
+ *
+ * 각 섹션 별 현재 몇개의 데이터가 모였는지 모니터링
+ * 9000개가 넘는 섹션 발견 시, 즉시 DB에 Batch 실시
+ *
+ */
+// @Scheduled(cron = "0 0 * * * *") // 매 시간 정각
+// //@Scheduled(cron = "0 */5 * * * *") // 5분
+// // @Scheduled(cron = "0 */3 * * * *") // 3분
+// //@Scheduled(cron = "0 */2 * * * *") // 2분
+// //@Scheduled(cron = "0 * * * * *") // 1분
+// public void monitoring(){
+//
+// /* 날짜 & 시간 확인 */
+// LocalDate day = LocalDate.now(); // 오늘의 날짜
+// LocalDateTime time = LocalDateTime.now(); // 현재 날짜와 시간(분 단위 포함)
+// log.info("// ======================= 오늘의 날짜 : {} =========================", day); // 오늘의 날짜 로그 확인
+// log.info("현재 시간 : {}", time);
+//
+// /* 날짜 포매팅 작업 */
+// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); //원하는 포맷 지정
+// String dateTo = day.format(formatter); //문자열로 변환
+// String dateFrom = day.format(formatter); //문자열로 변환
+//
+// /* 섹션 별 누적 데이터 수 모니터링 로직 */
+// for (String section : sections) {
+//
+// // 현재 섹션의 현 시각 누적 Total_Item 수 확인
+// int total_items = newsMonitoringManager.getTotalItems(section, dateFrom, dateTo);
+//
+// /* 뉴스 데이터 9000개 이상 시 중간 배치 + Redis 기록 */
+// if(total_items >= 9000 && total_items <= 18000){
+//
+// // 해당 섹션이 이전에도 사전 Batch 작업이 실행 되었는지 확인 (Redis에서 횟수 조회)
+// int n = intermediateBatchRedisService.getBatchCount(section);
+// log.info("{} 섹션 기존 중간 배치 현황 : 총 {}회 중간 배치 ",section, n);
+//
+// // 현재 total_item이 기준(9000개)을 넘으면 Job Launcher로 Batch 작업 진행 메서드 호출
+// runNewsBatch(section);
+//
+// // 중간 배치가 진행된 섹션 배치 현황 기록 (Redis에 횟수 증가)
+// intermediateBatchRedisService.incrementBatchCount(section);
+//
+// } /* 2번째 중간 배치 부터 주의 메시지 호출 */
+// else if(total_items >= 18000){
+//
+// // Job Launcher로 Batch 작업 진행 메서드 호출
+// runNewsBatch(section);
+//
+// // 현재 total_item이 기준(18000개)을 넘으면 Job Launcher로 Batch 작업 진행 메서드 호출
+// log.info("[#주의#] {}섹션의 total_items수 : {}",section,total_items); // 추후, 하루에 3번 이상의 중간 호출이 일어나는 것에 대한 방어코드도 작성할 것
+//
+// }
+// }
+// }
+
// ======================= Spring Batch Job Launcher (Batch Starter Method) =========================
/**