Skip to content

Commit 4204738

Browse files
authored
Merge pull request #193 from prography/develop
feat: Prometheus 히스토그램 메트릭 수집을 활성화했습니다
2 parents 3d622af + 007e3ab commit 4204738

File tree

8 files changed

+154
-528
lines changed

8 files changed

+154
-528
lines changed

build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ dependencies {
6565
implementation 'org.jsoup:jsoup:1.17.2'
6666

6767
implementation 'com.rometools:rome:2.1.0'
68+
69+
// Spring Retry
70+
implementation 'org.springframework.retry:spring-retry'
71+
implementation 'org.springframework.boot:spring-boot-starter-aop'
72+
73+
// Apache HttpClient 5 for RestTemplate Connection Pool
74+
implementation 'org.apache.httpcomponents.client5:httpclient5:5.2.1'
6875
}
6976

7077
tasks.named('test') {

src/main/java/com/example/cherrydan/CherrydanApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.retry.annotation.EnableRetry;
56
import org.springframework.scheduling.annotation.EnableScheduling;
67

78
@SpringBootApplication
89
@EnableScheduling
10+
@EnableRetry
911
public class CherrydanApplication {
1012

1113
public static void main(String[] args) {
Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,26 @@
11
package com.example.cherrydan.campaign.scheduler;
22

3+
import com.example.cherrydan.campaign.service.CampaignSearchSyncService;
34
import lombok.RequiredArgsConstructor;
45
import lombok.extern.slf4j.Slf4j;
5-
import org.springframework.jdbc.core.JdbcTemplate;
66
import org.springframework.scheduling.annotation.Scheduled;
77
import org.springframework.stereotype.Component;
88

99
import java.time.LocalDate;
10-
import java.time.LocalDateTime;
1110
import java.time.ZoneId;
1211

1312
@Component
1413
@RequiredArgsConstructor
1514
@Slf4j
1615
public class CampaignSearchSyncScheduler {
1716

18-
private final JdbcTemplate jdbcTemplate;
17+
private final CampaignSearchSyncService syncService;
1918

2019
@Scheduled(cron = "0 30 6 * * ?", zone = "Asia/Seoul")
2120
public void syncDailySearchTable() {
22-
try {
23-
LocalDate yesterday = LocalDate.now(ZoneId.of("Asia/Seoul")).minusDays(1);
21+
LocalDate yesterday = LocalDate.now(ZoneId.of("Asia/Seoul")).minusDays(1);
22+
log.info("campaigns_daily_search 동기화 시작 - 날짜: {}", yesterday);
2423

25-
log.info("campaigns_daily_search 동기화 시작 - 날짜: {}", yesterday);
26-
27-
// 1. TRUNCATE
28-
jdbcTemplate.execute("TRUNCATE TABLE campaigns_daily_search");
29-
30-
// 2. INSERT ... SELECT (한 방 쿼리, 인덱스 활용)
31-
String sql = """
32-
INSERT INTO campaigns_daily_search (id, title, created_at)
33-
SELECT id, title, created_at
34-
FROM campaigns
35-
WHERE is_active = 1
36-
AND created_at >= ?
37-
AND created_at < ?
38-
""";
39-
40-
LocalDateTime startOfDay = yesterday.atStartOfDay();
41-
LocalDateTime startOfNextDay = yesterday.plusDays(1).atStartOfDay();
42-
43-
int count = jdbcTemplate.update(sql, startOfDay, startOfNextDay);
44-
45-
log.info("campaigns_daily_search 동기화 완료 - 날짜: {}, 건수: {}", yesterday, count);
46-
47-
} catch (Exception e) {
48-
log.error("campaigns_daily_search 동기화 실패: {}", e.getMessage(), e);
49-
}
24+
syncService.performSyncWithRetry(yesterday);
5025
}
51-
}
26+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.example.cherrydan.campaign.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.dao.DataAccessException;
6+
import org.springframework.jdbc.core.JdbcTemplate;
7+
import org.springframework.retry.annotation.Backoff;
8+
import org.springframework.retry.annotation.Recover;
9+
import org.springframework.retry.annotation.Retryable;
10+
import org.springframework.stereotype.Service;
11+
12+
import java.time.LocalDate;
13+
import java.time.LocalDateTime;
14+
15+
@Service
16+
@RequiredArgsConstructor
17+
@Slf4j
18+
public class CampaignSearchSyncService {
19+
20+
private final JdbcTemplate jdbcTemplate;
21+
22+
private static final String MAIN_TABLE = "campaigns_daily_search";
23+
private static final String TEMP_TABLE = "campaigns_daily_search_temp";
24+
private static final int RETRY_MAX_ATTEMPTS = 3;
25+
private static final long RETRY_BACKOFF_DELAY = 180000L;
26+
private static final int LOCK_WAIT_TIMEOUT_SECONDS = 60;
27+
28+
@Retryable(
29+
retryFor = {DataAccessException.class, RuntimeException.class},
30+
maxAttempts = RETRY_MAX_ATTEMPTS,
31+
backoff = @Backoff(delay = RETRY_BACKOFF_DELAY),
32+
recover = "recoverSync"
33+
)
34+
public void performSyncWithRetry(LocalDate targetDate) {
35+
log.info("동기화 시도 - 날짜: {}", targetDate);
36+
37+
prepareTemporaryTable();
38+
int count = performSync(targetDate);
39+
swapTables();
40+
41+
log.info("동기화 성공 - 날짜: {}, 건수: {}", targetDate, count);
42+
}
43+
44+
private void prepareTemporaryTable() {
45+
try {
46+
jdbcTemplate.execute(String.format("DROP TABLE IF EXISTS %s", TEMP_TABLE));
47+
48+
String createTempTableSql = String.format("""
49+
CREATE TABLE %s (
50+
id BIGINT PRIMARY KEY,
51+
title VARCHAR(255),
52+
created_at DATETIME,
53+
FULLTEXT INDEX idx_title (title)
54+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
55+
""", TEMP_TABLE);
56+
57+
jdbcTemplate.execute(createTempTableSql);
58+
59+
log.debug("임시 테이블 준비 완료: {}", TEMP_TABLE);
60+
} catch (Exception e) {
61+
log.error("임시 테이블 준비 실패", e);
62+
throw new RuntimeException("임시 테이블 준비 실패", e);
63+
}
64+
}
65+
66+
private int performSync(LocalDate targetDate) {
67+
try {
68+
String insertSql = String.format("""
69+
INSERT INTO %s (id, title, created_at)
70+
SELECT id, title, created_at
71+
FROM campaigns
72+
WHERE is_active = 1
73+
AND created_at >= ?
74+
AND created_at < ?
75+
""", TEMP_TABLE);
76+
77+
LocalDateTime startOfDay = targetDate.atStartOfDay();
78+
LocalDateTime startOfNextDay = targetDate.plusDays(1).atStartOfDay();
79+
80+
int count = jdbcTemplate.update(insertSql, startOfDay, startOfNextDay);
81+
log.debug("임시 테이블 데이터 삽입 완료 - 건수: {}", count);
82+
83+
return count;
84+
} catch (Exception e) {
85+
log.error("데이터 동기화 실패 - 날짜: {}", targetDate, e);
86+
throw new RuntimeException("데이터 동기화 실패", e);
87+
}
88+
}
89+
90+
private void swapTables() {
91+
try {
92+
jdbcTemplate.execute(String.format("SET SESSION lock_wait_timeout = %d", LOCK_WAIT_TIMEOUT_SECONDS));
93+
94+
String renameSql = String.format("""
95+
RENAME TABLE
96+
%s TO %s_old,
97+
%s TO %s,
98+
%s_old TO %s
99+
""",
100+
MAIN_TABLE, MAIN_TABLE,
101+
TEMP_TABLE, MAIN_TABLE,
102+
MAIN_TABLE, TEMP_TABLE
103+
);
104+
105+
jdbcTemplate.execute(renameSql);
106+
log.debug("테이블 교체 완료: {} <-> {}", MAIN_TABLE, TEMP_TABLE);
107+
} catch (Exception e) {
108+
log.error("테이블 교체 실패", e);
109+
throw new RuntimeException("테이블 교체 실패", e);
110+
}
111+
}
112+
113+
@Recover
114+
public void recoverSync(Exception e, LocalDate targetDate) {
115+
log.error("campaigns_daily_search 동기화 복구 메서드 호출 - {}회 재시도 모두 실패, 날짜: {}",
116+
RETRY_MAX_ATTEMPTS, targetDate, e);
117+
}
118+
}

src/main/resources/application-dev.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ management:
139139
enabled: true
140140
livenessstate:
141141
enabled: true
142+
prometheus:
143+
metrics:
144+
export:
145+
enabled: true
146+
metrics:
147+
distribution:
148+
percentiles-histogram:
149+
http.server.requests: true
142150

143151
springdoc:
144152
swagger-ui:

src/main/resources/application-prod.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,12 @@ management:
140140
readinessstate:
141141
enabled: true
142142
livenessstate:
143-
enabled: true
143+
enabled: true
144+
prometheus:
145+
metrics:
146+
export:
147+
enabled: true
148+
metrics:
149+
distribution:
150+
percentiles-histogram:
151+
http.server.requests: true

src/main/resources/application.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ management:
1919
metrics:
2020
export:
2121
enabled: true
22+
metrics:
23+
distribution:
24+
percentiles-histogram:
25+
http.server.requests: true

0 commit comments

Comments
 (0)