Scheduler 중복 실행
💬 Scale-out을 적용하고 보니 경매 시작, 경매 종료, 경매 시작 전 알림을 처리하기 위한 Scheduler가 각 인스턴스마다 실행이 된다는 것을 알았습니다. 이것은 불필요한 실행이 일어나게 했습니다.
✔ 문제 해결 고민
💬 실행되는 서버 Instance가 늘어나더라도 Scheduler는 한 번만 실행할 수 있는 방법이 없는지 고민했습니다. 고민해 본 바로 3가지 방법을 고려해 볼 수 있다고 판단했습니다.
➡ Scheduler를 위한 별도의 서버를 띄우는 것이었습니다.
➡ 일정 주기마다 Scheduler가 들어있는 jar를 실행하는 것이었습니다.
➡ Scheduler에 Lock을 걸어 먼저 실행된 것이 있다면 다른 Scheduler는 실행되지 않게 하는 것이었습니다.
✔ 해결 방법 선정
✅ Scheduler에 Lock을 걸어 사용
➡ 별도의 Instance를 추가적으로 만들지 않고, 기존에 돌아가고 있는 서버에서 바로 처리할 수 있습니다. Scheduler에 Lock을 걸기 위한 별도의 공유 자원이 필요했지만, 그것은 DB를 사용하면 문제가 없다고 판단을 했습니다.
💬 Scheduler에 대한 별도의 서버
➡ 별도의 서버로 관리하기에는 Scheduler에서 수행하는 작업이 크지 않았으며, 새로운 Instance 생성 및 CI/CD 구축을 해야 하는 추가적인 자원이 발생합니다.
💬 일정 주기마다 Scheduler가 들어있는 jar를 실행
➡ Scheduler 실행되는 주기가 길다면 이러한 방법을 고려해 볼 수 있겠지만, 이 프로젝트에서 Scheduler는 1분 단위로 실행이 됩니다. 그래서 jar를 실행이 너무 자주 일어나게 됩니다.
✔ 해결 방법 기술
✅ Scheduler에 Lock을 걸기 위한 방법을 찾아봤습니다. ShedLock이라는 DB를 사용해 Lock을 거는 라이브러리를 알게 되었습니다. 다양한 DB를 지원했으며, 이번 프로젝트에서 사용하고 있는 PostgreSQL를 통해서도 적용해 볼 수 있었습니다. 그리고 설정 또한 어렵지 않았기에 프로젝트에 바로 적용해 보기로 했습니다.
GitHub - lukas-krecan/ShedLock: Distributed lock for your scheduled tasks
Distributed lock for your scheduled tasks. Contribute to lukas-krecan/ShedLock development by creating an account on GitHub.
github.com
✔ 해결 방법 적용
1. DB(PostgreSQL)에 ShedLock을 위한 테이블을 생성합니다.
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL, locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
2. Dependency를 추가합니다.
implementation 'net.javacrumbs.shedlock:shedlock-spring:5.7.0' implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:5.7.0'
3. 최상단 Application 클래스에 어노테이션을 추가합니다.
➡ 1분 단위 작업들만 있기 때문에 기본 최대 Lock 시간을 50초로 설정을 했습니다.
@EnableSchedulerLock(defaultLockAtMostFor = "50s") public class IdeaRushApplication {
4. ShedLock Config 클래스를 생성해 줍니다.
➡ 주의해야 할 점은 Lock을 걸 때 사용하는 시간을 DB 기준으로 사용한다는 것입니다. DB 기준으로 시간을 설정하지 않으면 각 서버 시간을 사용하게 되는데, 이것으로 인해 서버 간의 시간 싱크를 맞춰야 하는 추가적인 작업이 필요하게 될 수도 있습니다.@Configuration public class ShedLockConfig { @Bean public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider( JdbcTemplateLockProvider.Configuration.builder() .withJdbcTemplate(new JdbcTemplate(dataSource)) .usingDbTime() .build() ); } }
5. Lock이 필요한 Scheduler에 어노테이션을 추가합니다.
➡ 1분 단위 작업이기에 기본 최소, 최대 Lock 시간을 50초로 설정했습니다.@Scheduled(cron = "0 * * * * *") @SchedulerLock(name = "beforeTimeBidOfIdea", lockAtMostFor = "50s", lockAtLeastFor = "50s") public void beforeTimeBidOfIdea() {
✔ 해결 방법 적용 결과
💬 8080, 8083 포트로 두 개의 서버를 실행시켜 본 결과 정상적으로 Scheduler가 한 번만 실행이 되는 것을 확인할 수 있었습니다. 그리고 아래 결과처럼 항상 동일한 서버에서만 실행이 되는 것이 아닌, 먼저 Lock을 획득한 서버에서 실행되는 것도 확인할 수 있습니다.
'개발일지 > TIL' 카테고리의 다른 글
[231008] JWT는 어디에 저장을 해야할까? (0) | 2023.10.08 |
---|---|
[230923] Java PriorityQueue와 Comparator 사용하기 (1) | 2023.09.23 |
[230901] 간단한 화면 구현 (0) | 2023.09.01 |
[230830] 서버간 통신을 위해 Kafka 적용 (0) | 2023.08.30 |
[230829] SSE 서버 분리하기 (0) | 2023.08.29 |