반응형

이전에 반복적인 작업이 필요한 상황에서 런덱이나 Linux의 crontab을 사용하여 주기적인 api호출을 했었던 적이 있는데, 모든 컴포넌트의 정산, 배치성 업무를 계속 등록하여 1. 관리가 안되고 2. 런덱서버의 OOM 및 과부하로 인해 필요한 job이 제 때 실행되지 않는 문제를 겪었었다.

그 후 spring-batch와 같은 라이브러리를 통해 전문적으로 배치를 관리할 수 있다는 것을 알았지만, 보통 배치성 업무는 한 컴포넌트에 두세 개 정도로 많지 않은 경우가 대부분인데 이를 위해 별도의 spring-batch 프로젝트를 생성하여 관리하는 게 번거롭게 느껴졌다.

한두 개의 심플한 api를 별도의 스케줄러 없이 하나의 프로젝트로 관리하려면? spring-boot에 들어있는 @Schueduled라는 어노테이션을 메서드 위에 붙여서 쉽게 해결할 수 있다.

 

본 글에서는 @Schueduled를 사용하여 인스턴스 별 로컬 캐싱을 하는 예시를 정리한다.


1부 @Schueduled

@Scheduled를 사용하기 위한 조건

  • 메서드의 return type이 void일 경우(return type이 있어도 무시됨) && 메서드의 input parameter가 없을 경우
@Scheduled(cron = "0/3 * * * * *")
public void sumStatisticsResponse() {
	...
}

 

그리고 @EnableScheduling 어노테이션을 프로젝트에 붙여야 한다.

@SpringBootApplication
@EnableScheduling
public class Application {
	...
}

 

@Scheduled 옵션

  • cron : cron표현식 사용 가능 ("초 분 시 일 월 주 (년)")
  • fixedDelay : 이전 작업이 끝난 시점으로부터 얼마나 쉬고 다시 작업할지를 설정(milliseconds 단위)
    • 한 개의 인스턴스가 작업해야 하는 경우 
    •  ex) fixedDelay = 5000
  • fixedDelayString : fixedDelay의 문자열 버전
    • ex) fixedDelay = "5000"
  • fixedRate : 이전 작업이 수행되기 시작한 시점으로부터 언제 다시 시작할지 고정된 시간을 설정(milliseconds 단위)
    • 주의: 기본값은 병렬 작업이 아니므로, 이전 작업이 안 끝났다면 fixedRate로 설정한 시간이 다가와도 작업하지 않음
    • ex) fixedRate = 3000
  • fixedRateString : fixedDelay의 문자열 버전
    • ex) fixedRate = "3000"
  • initialDelay : 스케줄러에서 메서드가 등록되자마자 수행하는 것이 아닌 초기 지연시간을 설정
    • 사전에 작업해야 할 내용이 있을 경우 대기시간 설정 가능
  • initialDelayString : initialDelay의 문자열 버전
  • zone : cron표현식을 사용했을 때 사용할 time zone(기본값: 서버의 타임존)
//파리 시간으로 매윌 15일 10:15:00에 실행
@Scheduled(cron = "0 15 10 15 * ?", zone = "Europe/Paris")

//프로퍼티로 값을 받아서 사용 가능
@Scheduled(fixedRateString = "${myscheduler.period}", initialDelay = 2000)
@Scheduled(cron = "${cron.expression}")

 

병렬 처리

기본값으로 스프링은  local single-threaded scheduler로 처리한다. 즉, 복수개의 @Scheduled가 설정되어 있어도 이전  작업이 다 끝나기를 기다린다는 뜻이다. 스레드 풀을 생성하여 빈으로 등록해주면 알아서 찾아서 해당 스레드 풀로 병렬 처리가 된다(혹은 비동기 처리를 추가할 수도 있다).

1. spring-boot properties(or)

spring.task.scheduling.pool.size=5

2. in code

@Bean
public TaskScheduler  taskScheduler() {
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(5);
    threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
    return threadPoolTaskScheduler;
}

2부 인스턴스 별 로컬 캐싱을 3초 주기로 설정하기

@Component
public class CacheEventService {

    @Autowired
    private CommonDataRepository commonDataRepository;

    private Long ival1;
    private Long ival2;
    private Long ival3;

    @Scheduled(cron = "0/3 * * * * *")
    @Transactional
    public void sumStatisticsResponse() {
    	//데이터 조회 준비
        //통계데이터와 같은 무거운(?)쿼리 실행 = 캐싱할 데이터
        //후처리(계산 등)
       
        ival1 = commonDataRepository.sumDailyBrandCampaignIval1(dailyDatePk) + (staticGlobalData != null ? staticGlobalData.getIval1() : 0);
        ival2 = commonDataRepository.sumDailyBrandCampaignIval2(dailyDatePk) + (staticGlobalData != null ? staticGlobalData.getIval2() : 0);
        ival3 = commonDataRepository.sumDailyBrandCampaignIval3(dailyDatePk) + (staticGlobalData != null ? staticGlobalData.getIval3() : 0);
}
////////////////////////

@Service
public class RetrieveEventServiceImpl {

    @Autowired
    private CacheEventService cacheService;
    
    public BaseResponse<StatusResponse> getCurrentInfo(String tno, MobileGameType gameType, EntityGachaConfig gachaConfig) {
    	//기타 로직
		...
    	//원래는 이 안에 함수를 호출할 때마다 디비 조회하는 로직이 있었는데,
        //3초 한번 캐싱된 데이터를 불러와서 세팅함
        Long ival1 = cacheService.getIval1();
        Long ival2 = cacheService.getIval2();
        Long ival3 = cacheService.getIval3();

		...
	}
}

사실  별거 없다. 단지 내게 생소했을 뿐..

class안의 필드를 3초 주기로 세팅해주고 그 값을 가져와 사용하는 방식이다. 메서드를 호출할 때마다 디비를 호출하지 않는 장점이 있다. 

위 방식의 단점은, 로컬 인스턴스 별로 캐싱이 이루어지기 때문에 디비가 받는 부하(조회)는 3초에 한 번씩 인스턴스 수만큼 생긴다. 8개의 인스턴스가 돌고 있다면 3초마다 저 쿼리다 8번 호출되겠지. 인스턴스와 무관하게 같은 결과가 나온다면 한 번의 조회로 관리되게 수정할 필요가 있어 보인다. 레디스나 별도 디비의 값으로 글로벌하게 관리하면 될 듯하다(그래서 내가 과거에 이 방식을 안 써본 듯, 어쨌든 다양한 방법을 알고 있는 건 좋은 것이니까).


3부 스프링 캐시

위와 같은 방법으로 캐싱을 할 수도 있지만, 스프링이 제공하는 캐싱도 있다.

별도의 항목이고 내용이 길어지므로 링크로 첨부한다.

2022.05.30 - [개발/spring] - [spring-cache] 스프링 캐시

 

[spring-cache] 스프링 캐시

스프링이 제공하는 캐싱이 있다. implementation 'org.springframework.boot:spring-boot-starter-cache' 위 의존성을 추가하고 프로젝트에 @EnableCaching 어노테이션을 추가한다. spring-boot를 사용하면 @Enabl..

bangpurin.tistory.com

 

 


원문: https://www.baeldung.com/spring-scheduled-tasks

 

The @Scheduled Annotation in Spring | Baeldung

How to use the @Scheduled annotation in Spring, to run tasks after a fixed delay, at a fixed rate or according to a cron expression.

www.baeldung.com

 

728x90
반응형

+ Recent posts