본문 바로가기

Proj/구구모

스프링 스케쥴러를 이용한 게시글 마감 처리

1. 개요

구구모 프로젝트를 진행하던 중 게시글 마감시간을 처리해야 했다. 내용은 다음과 같다.

  • 모집 마감일, 모임 날짜(단기 모임)을 과거의 날짜로 입력하지 못하도록 제약
  • 매일 자정이 지나면 게시글의 마감 날짜가 지난 모임의 모임 상태를 모집마감으로 변경

첫번째 이슈는 spring validation으로 처리했는데, 두번째 이슈는 구현 방법을 찾아야 했다. 그렇게 선택한 것이 스프링 스케쥴러이다.

2. Spring scheduler vs Quartz scheduler

스프링부트 프로젝트에서 가장 많이 사용하는 두 스케쥴러이다. 이 중 하나를 선택하여 사용해야 했다.

2-1 Quartz scheduler

자바에서 사용할 수 있는 오픈 Job Scheduling 라이브러리이다. 스프링 스케쥴러에 비해 스케쥴러의 세부적인 요소들을 구현할 수 있다. 구성 요소와 구현 방법을 간단히 보면 다음과 같다

  1. 실제로 수행할 Job 인터페이스 구현
  2. 언제 Job를 수행할 지에 대한 Trigger 구현
  3. 위의 클래스를 Scheduler에 구현

2-2 Spring scheduler

스프링에 built in으로 제공하는 기능이다. 쿼츠에 비해 간단하게 사용할 수 있다는 장점이 있다.

 

현재로서는 간단히도 구현할 수 있기 때문에 스프링 스케쥴러를 사용하기로 했다.

 

3. 구현

@EnableScheduling
@SpringBootApplication
public class GugumoApplication {

	public static void main(String[] args) {
		SpringApplication.run(GugumoApplication.class, args);
	}

}

 스프링 스케쥴러를 사용하기 위해서는 메인 메서드에 @EnableScheduling을 붙여줘야 한다. 이후 스케쥴러들을 관리할 클래스를 만들어준다.

 

/scheduler/MeetingScheduler.java

@Component
@RequiredArgsConstructor
public class MeetingScheduler {

    private final MeetingRepository meetingRepository;

    //매일 마감일이 지난 모임 정보를 모집마감으로 변경
    @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul")
    @Transactional
    public void setExpiredMeetingStatus() {

        LocalDate today = LocalDate.now();

        List<Meeting> targetMeeting = meetingRepository.findByMeetingDeadlineBeforeAndStatus(today, MeetingStatus.RECRUIT);


        for (Meeting meeting : targetMeeting) {
            meeting.expireStatus();
        }


    }

}

 

https://docs.spring.io/springframework/docs/current/javadocapi/org/springframework/scheduling/annotation/Scheduled.html

 

Scheduled (Spring Framework 6.1.8 API)

A cron-like expression, extending the usual UN*X definition to include triggers on the second, minute, hour, day of month, month, and day of week.

docs.spring.io

@Scheduled 애노테이션을 붙이고, Optional Element를 설정하여 스케쥴링 주기를 설정할 수 있는데, 이 기능은 매일 자정마다 실행되어야 하므로 cron 표현식을 사용하였다.

  3-1 cron 표현식

https://docs.aws.amazon.com/ko_kr/eventbridge/latest/userguide/eb-cron-expressions.html

 

cron 표현식 참조 - 아마존 EventBridge

'#' 문자를 사용하는 경우 요일(day-of-week) 필드에 하나의 표현식만 정의할 수 있습니다. 예를 들어 "3#1,6#3"은(는) 두 개의 표현식으로 해석되기 때문에 유효하지 않습니다.

docs.aws.amazon.com

 

 

필드 와일드카드
0~59 , - * /
0~59 , - * /
시간 0~23 , - * /
날짜 1~31 , - * ? / L W
1-12 또는 JAN-DEC , - * /
요일 0-6 또는 SUN-SAT , - * ? L #
연도 1970~2199 , - * /

 

 

* : 모든 값

? : 해당 항목 미사용

- : 기간 설정

, : 특정 기간 설정

/ : 시작시간/반복간격

L : 마지막 기간

W : 가장 가까운 평일

# : 몇째주의 무슨 요일을 표현 

 

위에서 아래 순으로 작성하여 표현할 수 있다.또, 연도는 생략 가능하므로 매일 자정을 나타내는 표현식은 다음과 같다

0 0 0 * * *

이를 애노테이션에 Optional Element로 적용하고, zone을 설정하여 한국 시간 기준으로 동작함을 명시했다. 마감일이 오늘 날짜보다 이전의 모집중인 모임 정보를 모두 조회하여 모집마감 상태로 변경하였다. Meeting.expireStatus() 코드는 다음과 같다.

 public void expireStatus() {
        this.status = MeetingStatus.END;
    }

 

 

고민사항

조건에 해당하는 모임 정보의 상태를 갱신하는 방법이 두 가지가 떠올랐다. 이 중 어떤 방식이 더 나을지 생각해보았다.

  • JPA의 dirty checking를 사용
  • querydsl을 사용한 벌크 수정 연산

크게 고민할 문제는 아니었는데, 첫 번째 방법을 사용할 경우 다음과 같은 이점들이 있다.

  1. querydsl의 update는 영속성 컨텍스트에 반영되지 않고 db에 직접 쿼리를 날리므로 영속성 컨텍스트를 따로 초기화 해주어야 함, dirty checking은 그럴 필요가 없음
  2. dirty checking은 트랜잭션의 커밋 시점에 다른 쿼리와 같이 db로 전송되므로 벌크 연산에 비해 성능 향상의 이점이 있음

위 두 가지를 이유로 첫 번째 방법을 사용하기로 하였다. 조회 후 수정하는 작업이 어색했지만, 위 근거를 토대로 이 방법이 더 맞다는 결론을 도출했다

 

추후 확인 및 개선해야 할 사항

 위의 구현은 가장 간단한 방법이다. 해야 할 일이 하나밖에 없기 때문이다. 여러개가 된다면 한 개의  쓰레드로 동작하므로 문제가 된다. job1과 2가 있고 두 작업 모두 매일 자정에 실행되어야 한다고 가정하자. 자정이 되었지만 실행해야 할 쓰레드는 하나밖에 없으므로 자정이 되자마자 실행되어야 할 두 작업이 job1->job2의 순으로 실행될 수 있다. 처리할 일이 늘어난다면 Thread Pool을 사용해 여러 쓰레드를 실행시켜, 첫번째 thread가 job1, 두번째 thread가 job2를 실행한다면 두 가지 일을 동시에 처리할 수 있을 것이다.

'Proj > 구구모' 카테고리의 다른 글

댓글 알림 구현(feat. sse)  (0) 2024.06.25
EC2 한국 시간으로 변경  (0) 2024.06.08
추천 게시글 기능 구현  (2) 2024.06.04
댓글 및 대댓글 기능 구현  (0) 2024.05.29
AWS EC2를 이용한 배포  (0) 2024.05.23