[백엔드] @Scheduled(Cron)를 프로젝트에 응용하기

1. Spring Scheduler에 대한 설명

Spring Scheduler는 백그라운드에서 주기적으로 특정 작업을 수행할 수 있도록 도와주는 강력한 도구입니다.

실시간 트래픽이 중요한 시스템에서도 추가적인 스레드를 사용해 성능 저하 없이 처리할 수 있습니다.

 

@Scheduled 어노테이션을 사용하면 별도의 스레드를 생성하여 애플리케이션 실행 중 특정 시간마다 반복적으로 작업을 실행할 수 있습니다.

이 기능을 사용하면 시스템 성능을 저하시키지 않으면서도 일정 주기마다 특정 작업을 자동으로 처리할 수 있습니다. 이때, cron 표현식을 사용하면 원하는 시간이나 날짜에 정확하게 작업이 실행되도록 정교하게 설정할 수 있습니다.

Cron은 초, 분, 시, 일, 월, 요일 등 다양한 기준으로 스케줄을 설정할 수 있어 매우 유연한 작업 관리가 가능합니다. Spring에서 기본적으로 제공하는 @Scheduled 어노테이션과 cron 표현식을 결합하면 다음과 같은 다양한 백엔드 기능을 자동화할 수 있습니다:

  • 주기적으로 데이터베이스 청소
  • 예약된 이메일 발송
  • 시간 기반의 데이터 분석 및 보고서 생성

 

2. 팀 가입 요청 상태 관리와 일정 시간 뒤 삭제 필요성

프로젝트에서 팀 관리 기능을 구현할 때, 팀에 가입을 요청한 사용자의 요청 상태를 관리하는 로직이 필요했습니다. 사용자가 팀에 가입을 요청하면, 요청의 상태는 기본적으로 "PENDING(대기 중)"으로 설정됩니다.

기존에는 관리자가 승인 또는 거절을 하면 아무런 조치 없이 팀에 가입을 시킨 후 DB에 값을 삭제를 하였습니다. 관리자가 수락이나 거절을 해도, 그 정보를 사용자가 확인할 수 없다는 점에서 불편함이 있었습니다.

하지만 아래와 같이 요구사항을 만족 하려면 값을 삭제하는 것이 아니라 승인여부를 사용자에게 알려야 했기 때문에 팀 관리자가 해당 요청을 수락하거나 거절하면 상태가 "ACCEPT(승인)" 또는 "REJECT(거절)"로 변경되도록 코드를 수정하였습니다.

문제는 이러한 요청들이 데이터베이스에 계속 남아 관리가 어렵다는 점이었습니다. 사용자가 승인 또는 거절된 상태에서 더 이상 요청이 유효하지 않음에도 불구하고 삭제되지 않으면 특히 대규모 사용자가 지속적으로 요청을 할 경우, 데이터베이스는 기하급수적으로 증가해 관리가 어려워집니다 . 이에 따라 상태가 "ACCEPT" 또는 "REJECT"로 변경된 후 7일이 지나면 해당 요청을 자동으로 데이터베이스에서 삭제하고 싶었습니다.

이를 해결하기 위해 Spring의 @Scheduled 어노테이션과 cron 표현식을 활용하여 7일이 지난 가입 요청을 자동으로 삭제하는 스케줄러를 구현했습니다.

 

3. 코드 적용 방법

다음은 @Scheduled와 cron 표현식을 사용해 구현한 팀 가입 요청 상태 관리 및 자동 삭제 로직입니다.

 

1) 엔티티 수정

먼저, JoinTeam 엔티티에 가입 요청의 상태와 생성 및 업데이트 시간을 기록할 수 있도록 createdDate와 updatedDate 필드를 추가했습니다.

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class JoinTeam {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long joinTeamId;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    private String status;

    private LocalDateTime createdDate;

    private LocalDateTime updatedDate;
}

 

2) 스케줄링 활성화

스케줄링 기능을 활성화하기 위해 메인 애플리케이션 클래스에 @EnableScheduling 어노테이션을 추가합니다.

@SpringBootApplication
@EnableScheduling // 스케줄링 활성화
public class YogijogiiApplication {
    public static void main(String[] args) {
        SpringApplication.run(YogijogiiApplication.class, args);
    }
}

 

3) 서비스 메서드 구현

@Scheduled 어노테이션을 사용해 3분마다 팀 가입 요청 중 ACCEPT나 REJECT 상태가 된 지 7일이 지난 요청을 찾아 삭제하는 작업을 실행합니다.

@Service
@RequiredArgsConstructor
@Slf4j
public class JoinTeamCleanupServiceImpl implements JoinTeamCleanupService {
    private final JoinTeamDao joinTeamDao;

    @Scheduled(cron = "0 * * * * ?")
    public void cleanUpJoinRequests() {
        try {
            // 현재 시간으로부터 7일 뒤에 삭제
        LocalDateTime onWeekAgo = LocalDateTime.now().minus(7, ChronoUnit.DAYS);

        // 상태가 ACCEPT, REJECT인 요청들만 리스트로 담아놓기
        List<JoinTeam> expiredRequests = joinTeamDao.findByStatusAndDate("ACCEPT", onWeekAgo);
        expiredRequests.addAll(joinTeamDao.findByStatusAndDate("REJECT", onWeekAgo));

            if (!expiredRequests.isEmpty()) {
                joinTeamDao.deleteAll(expiredRequests);
                log.info("Expired join requests deleted: {}", expiredRequests.size());
            } else {
                log.info("No expired join requests to delete.");
            }
        } catch (Exception e) {
            log.error("Error during join request cleanup: ", e);
        }
    }
}

 

4) Repository 및 Dao 구현

// Repository
public interface JoinTeamRepository extends JpaRepository<JoinTeam, Long> {
    ....................................................................
    List<JoinTeam> findAllByStatusAndUpdatedDateBefore(String status, LocalDateTime date);

}

// Dao
@Service
@RequiredArgsConstructor
public class JoinTeamDaoImpl implements JoinTeamDao {
    private final JoinTeamRepository joinTeamRepository;

    @Override
    public List<JoinTeam> findByStatusAndDate(String status, LocalDateTime date) {
        List<JoinTeam> joinTeamList = joinTeamRepository.findAllByStatusAndUpdatedDateBefore(status, date);
        return joinTeamList;
    }

}

 

4. 느낀점

이 기능을 구현하면서 느낀 점은, 백엔드에서 데이터를 주기적으로 관리하는 것이 시스템 유지보수와 성능 최적화에 얼마나 중요한지를 다시금 깨닫게 되었다는 것입니다.

특히 사용자가 생성한 데이터 중 일정 기간 이후 더 이상 유효하지 않은 데이터를 자동으로 정리함으로써, 데이터베이스의 효율적인 관리를 보장할 수 있었습니다.

또한 Spring의 @Scheduled 어노테이션을 통해 스케줄러를 간편하게 구현할 수 있다는 점도 큰 장점이었습니다.

스케줄링이 필요한 다양한 작업을 Spring 환경에서 쉽게 적용할 수 있었고, 복잡한 시간 관리 작업도 cron 표현식을 사용해 간편하게 설정할 수 있었습니다.

이와 같은 작업 자동화는 백엔드 시스템에서 매우 유용하며, 이후에도 다양한 관리 작업에 @Scheduled를 활용할 예정입니다.