Skip to content

Commit a99bcc9

Browse files
authored
infra: 스케줄러 mdc로깅 체계구성, alloy 설정 도입
* refactor: DraftTicketExpirationScheduler 작업 페이징 단위로 설정, 로깅 설정 * refactor: 스케줄러 공통 안전설정으로 shedlock도입 * refactor: 스케줄러 동작 식별용 runId mdc도입, shedlock으로 실패하는 스케줄러 테스트 리팩터링 * chore: scheduler internal메소드에 주석 추가 * feat: 스케줄러 공통, 작업단위 식별용 고유 runId mdc로깅 부여 * feat: 스케줄러 공통, 작업단위 식별용 고유 runId mdc로깅 부여 / 시작종료 계측 * feat: alloy 설정 적용 * feat: alloy md설명 수정 및 env.example추가
1 parent 6d3c06c commit a99bcc9

17 files changed

Lines changed: 793 additions & 79 deletions

File tree

backend/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ dependencies {
7979
// logstash logback encoder
8080
implementation("net.logstash.logback:logstash-logback-encoder:7.4")
8181

82+
// ShedLock (스케줄러 중복 실행 방지)
83+
implementation("net.javacrumbs.shedlock:shedlock-spring:5.10.0")
84+
implementation("net.javacrumbs.shedlock:shedlock-provider-redis-spring:5.10.0")
8285

8386
}
8487

backend/src/main/java/com/back/api/event/scheduler/EventOpenScheduler.java

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22

33
import java.time.LocalDateTime;
44
import java.util.List;
5+
import java.util.UUID;
56

67
import org.springframework.context.annotation.Profile;
78
import org.springframework.scheduling.annotation.Scheduled;
89
import org.springframework.stereotype.Component;
910

11+
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
12+
1013
import com.back.domain.event.entity.Event;
1114
import com.back.domain.event.entity.EventStatus;
1215
import com.back.domain.event.repository.EventRepository;
16+
import com.back.global.logging.MdcContext;
1317

1418
import lombok.RequiredArgsConstructor;
1519
import lombok.extern.slf4j.Slf4j;
@@ -23,32 +27,79 @@ public class EventOpenScheduler {
2327
private final EventRepository eventRepository;
2428

2529
@Scheduled(cron = "${event.scheduler.open.cron}", zone = "Asia/Seoul") // 매 분 실행
30+
@SchedulerLock(
31+
name = "EventOpen",
32+
lockAtMostFor = "2m",
33+
lockAtLeastFor = "10s"
34+
)
2635
public void openTicketing() {
36+
String runId = UUID.randomUUID().toString();
37+
long startAt = System.currentTimeMillis();
38+
39+
int processed = 0;
40+
int failed = 0;
41+
2742
try {
43+
// 시작로그
44+
MdcContext.putRunId(runId);
45+
log.info("SCHED_START job=EventOpen");
46+
2847
LocalDateTime now = LocalDateTime.now();
2948

3049
// QUEUE_READY 상태이면서 ticketOpenAt이 지난 이벤트 조회
3150
List<Event> events = eventRepository.findByStatus(EventStatus.QUEUE_READY);
3251

3352
if (events.isEmpty()) {
53+
log.info("SCHED_END job=QueueEntry processed=0 failed=0 durationMs={}",
54+
System.currentTimeMillis() - startAt);
3455
return;
3556
}
3657

3758
for (Event event : events) {
38-
// ticketOpenAt이 현재 시간보다 이전이거나 같으면 오픈
39-
if (event.getTicketOpenAt().isBefore(now)
40-
|| event.getTicketOpenAt().isEqual(now)) {
59+
try {
60+
MdcContext.putEventId(event.getId());
61+
62+
// ticketOpenAt이 현재 시간보다 이전이거나 같으면 오픈
63+
if (event.getTicketOpenAt().isBefore(now)
64+
|| event.getTicketOpenAt().isEqual(now)) {
4165

42-
// QUEUE_READY → OPEN 상태 변경
43-
event.changeStatus(EventStatus.OPEN);
44-
eventRepository.save(event);
66+
// QUEUE_READY → OPEN 상태 변경
67+
event.changeStatus(EventStatus.OPEN);
68+
eventRepository.save(event);
69+
processed++;
4570

46-
log.info("이벤트 상태 변경: QUEUE_READY → OPEN");
71+
log.info(
72+
"SCHED_EVENT_SUCCESS job=EventOpen eventId={} status=OPEN",
73+
event.getId()
74+
);
75+
}
76+
} catch (Exception ex) {
77+
failed++;
78+
log.error(
79+
"SCHED_EVENT_FAIL job=EventOpen eventId={} error={}",
80+
event.getId(), ex.toString(), ex
81+
);
82+
} finally {
83+
MdcContext.removeEventId();
4784
}
4885
}
4986

50-
} catch (Exception e) {
51-
log.error("티켓팅 오픈 스케줄러 실행 실패", e);
87+
// 종료 로그
88+
log.info(
89+
"SCHED_END job=EventOpen processed={} failed={} durationMs={}",
90+
processed,
91+
failed,
92+
System.currentTimeMillis() - startAt
93+
);
94+
} catch (Exception ex) {
95+
log.error(
96+
"SCHED_FAIL job=EventOpen durationMs={} error={}",
97+
System.currentTimeMillis() - startAt,
98+
ex.toString(),
99+
ex
100+
);
101+
} finally {
102+
MdcContext.removeRunId();
52103
}
53104
}
54105
}

backend/src/main/java/com/back/api/queue/scheduler/QueueEntryScheduler.java

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
package com.back.api.queue.scheduler;
22

33
import java.util.List;
4+
import java.util.UUID;
45

56
import org.springframework.context.annotation.Profile;
67
import org.springframework.scheduling.annotation.Scheduled;
78
import org.springframework.stereotype.Component;
89

10+
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
11+
912
import com.back.api.event.service.EventService;
1013
import com.back.api.queue.service.QueueEntryProcessService;
1114
import com.back.domain.event.entity.Event;
1215
import com.back.domain.event.entity.EventStatus;
16+
import com.back.global.logging.MdcContext;
1317

1418
import lombok.RequiredArgsConstructor;
1519
import lombok.extern.slf4j.Slf4j;
@@ -30,19 +34,61 @@ public class QueueEntryScheduler {
3034

3135
//대기열 자동 입장 처리
3236
@Scheduled(cron = "${queue.scheduler.entry.cron}", zone = "Asia/Seoul") //10초마다 실행
37+
@SchedulerLock(
38+
name = "QueueEntry",
39+
lockAtMostFor = "2m",
40+
lockAtLeastFor = "5s"
41+
)
3342
public void autoQueueEntries() {
43+
String runId = UUID.randomUUID().toString();
44+
long startAt = System.currentTimeMillis();
45+
46+
int processedEvents = 0;
47+
int failedEvents = 0;
48+
3449
try {
50+
// 시작로그
51+
MdcContext.putRunId(runId);
52+
log.info("SCHED_START job=QueueEntry");
53+
3554
List<Event> openEvents = eventService.findEventsByStatus((EventStatus.OPEN));
3655

3756
if (openEvents.isEmpty()) {
57+
log.info("SCHED_END job=QueueEntry processed=0 failed=0 durationMs={}",
58+
System.currentTimeMillis() - startAt);
3859
return;
3960
}
4061

4162
for (Event event : openEvents) {
42-
queueEntryProcessService.processEventQueueEntries(event);
63+
try {
64+
MdcContext.putEventId(event.getId());
65+
queueEntryProcessService.processEventQueueEntries(event);
66+
processedEvents++;
67+
} catch (Exception ex) {
68+
failedEvents++;
69+
// 실패 로그
70+
log.error("SCHED_EVENT_FAIL job=QueueEntry eventId={} error={}", event.getId(), ex.toString(), ex);
71+
} finally {
72+
MdcContext.removeEventId();
73+
}
4374
}
44-
} catch (Exception e) {
45-
log.error("자동 입장 스케줄러 실패", e);
75+
76+
// 종료 로그
77+
log.info(
78+
"SCHED_END job=QueueEntry processed={} failed={} durationMs={}",
79+
processedEvents,
80+
failedEvents,
81+
System.currentTimeMillis() - startAt
82+
);
83+
} catch (Exception ex) {
84+
log.error(
85+
"SCHED_FAIL job=QueueEntry durationMs={} error={}",
86+
System.currentTimeMillis() - startAt,
87+
ex.toString(),
88+
ex
89+
);
90+
} finally {
91+
MdcContext.removeRunId();
4692
}
4793
}
4894

backend/src/main/java/com/back/api/queue/scheduler/QueueExpireScheduler.java

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
import java.time.LocalDateTime;
44
import java.util.List;
5+
import java.util.UUID;
56

67
import org.springframework.context.annotation.Profile;
78
import org.springframework.scheduling.annotation.Scheduled;
89
import org.springframework.stereotype.Component;
910

11+
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
12+
1013
import com.back.api.queue.service.QueueEntryProcessService;
1114
import com.back.domain.queue.entity.QueueEntry;
1215
import com.back.domain.queue.entity.QueueEntryStatus;
1316
import com.back.domain.queue.repository.QueueEntryRepository;
17+
import com.back.global.logging.MdcContext;
1418

1519
import lombok.RequiredArgsConstructor;
1620
import lombok.extern.slf4j.Slf4j;
@@ -25,8 +29,22 @@ public class QueueExpireScheduler {
2529
private final QueueEntryProcessService queueEntryProcessService;
2630

2731
@Scheduled(cron = "${queue.scheduler.expire.cron}", zone = "Asia/Seoul")
32+
@SchedulerLock(
33+
name = "QueueExpire",
34+
lockAtMostFor = "5m",
35+
lockAtLeastFor = "10s"
36+
)
2837
public void autoExpireEntries() {
38+
String runId = UUID.randomUUID().toString();
39+
long startAt = System.currentTimeMillis();
40+
41+
int processed = 0;
42+
int failed = 0;
43+
2944
try {
45+
MdcContext.putRunId(runId);
46+
log.info("SCHED_START job=QueueExpire");
47+
3048
LocalDateTime now = LocalDateTime.now();
3149

3250
List<QueueEntry> expiredEntries = queueEntryRepository.findExpiredEntries(
@@ -35,16 +53,41 @@ public void autoExpireEntries() {
3553
);
3654

3755
if (expiredEntries.isEmpty()) {
56+
log.info(
57+
"SCHED_END job=QueueExpire processed=0 failed=0 durationMs={}",
58+
System.currentTimeMillis() - startAt
59+
);
3860
return;
3961
}
4062

41-
log.info("만료 대상 : {}명", expiredEntries.size());
63+
log.info(
64+
"SCHED_BATCH_FOUND job=QueueExpire candidates={}",
65+
expiredEntries.size()
66+
);
4267

43-
queueEntryProcessService.expireBatchEntries(expiredEntries);
68+
try {
69+
queueEntryProcessService.expireBatchEntries(expiredEntries);
70+
processed = expiredEntries.size();
71+
} catch (Exception ex) {
72+
failed = expiredEntries.size();
73+
log.error("SCHED_BATCH_FAIL job=QueueExpire error={}", ex.toString(), ex);
74+
}
4475

45-
log.info("만료 처리 완료 : {}명", expiredEntries.size());
46-
} catch (Exception e) {
47-
log.error("자동 만료 스케줄러 실패", e);
76+
log.info(
77+
"SCHED_END job=QueueExpire processed={} failed={} durationMs={}",
78+
processed,
79+
failed,
80+
System.currentTimeMillis() - startAt
81+
);
82+
} catch (Exception ex) {
83+
log.error(
84+
"SCHED_FAIL job=QueueExpire durationMs={} error={}",
85+
System.currentTimeMillis() - startAt,
86+
ex.toString(),
87+
ex
88+
);
89+
} finally {
90+
MdcContext.removeRunId();
4891
}
4992

5093
}

0 commit comments

Comments
 (0)