영호
[Spring] TaskScheduler 를 활용해 만료된 인증코드 제거하기 본문
들어가면서
프로젝트를 진행하면서 인증코드를 redis 로 관리했습니다. 인증코드는 3분이란 유효시간이 있고 유효시간이 지난 인증코드로 인증 요청을 하면 실패해야 합니다.
redis 는 메모리 기반이기 때문에 유효하지 않은 데이터는 제거하여 유효한 데이터만 저장하고 싶었기 때문에 만료된 인증코드를 어떻게 바로바로 제거할지 고민한 과정을 포스팅 할 예정입니다.
redis 의 Set ex 파라미터 활용
redis 에는 다양한 command 가 있고, 그 중 key 를 저장하면서 만료시간을 설정할 수 있습니다.
redis 의 ttl 만료된 키 삭제 메커니즘
redis 는 만료된 키를 삭제하는 2가지의 메커니즘이 있습니다.
- 만료된 key 접근 시 삭제
- 일정 주기로 ttl 이 설정된 키를 선정해 만료된 key 를 제거하고, 선정된 key 중 25% 이상이 만료되었으면 다시 반복
- 자세한 내용은 공식문서에 있습니다.
개선하고 싶은 부분
redis ttl 만료 키 삭제 메커니즘으로 인해 만료된 key 가 redis 에 만료된 인증코드 데이터가 남아있을 수 있습니다.
저는 이 부분이 마음에 들지 않았고 인증코드의 유효시간이 지나면 바로 삭제시키고 싶었습니다. 그래서 Spring 의 스케줄링 관련 내용을 찾아봤고, TaskScheduler 를 활용해 인증코드의 유효시간이 지나면 바로 삭제하도록 구현했습니다.
만료된 key 순회하면서 제거하기
keys 명령어를 통해 현재 존재하는 모든 key 를 조회하고, 이를 순회하며 접근해 만료되었다면 삭제시키는 방법을 고려했습니다.
그러나, keys 명령어는 O(N) 의 시간 복잡도를 가지기 때문에 싱글 스레드 기반인 redis 에서는 주의해야 되는 명령어입니다. 물론 현재 인증코드 데이터가 많지 않기 때문에 상관 없겠지만, 이왕이면 더 나은 방법으로 해결하고 싶어서 선택하지 않았습니다.
TaskScheduler 활용
인증코드가 생성되었을 때, 만료시간 이후 삭제 코드를 실행시키면 keys 명령어 없이도 제가 원하는 개선을 이룰 수 있을 것 같았습니다.
Spring 의 TaskScheduler 문서를 보면 schedule() 메서드의 다양한 파라미터를 통해 스케줄링 태스크를 추가할 수 있습니다.
그 중 schedule(Runnable task, Instant startTime); 메서드를 활용하면 스케줄링 태스크가 추가되고 startTime 에 해당 태스크가 실행되도록 할 수 있습니다.
이를 통해, 인증코드 생성 이후, 만료시간이 되면 해당 인증코드를 삭제하는 명령어를 동적으로 실행시킬 수 있습니다.
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void scheduledAuthCodeRemove(AuthCodeCreateEvent authCodeCreateEvent) {
String authCode = authCodeCreateEvent.getAuthCode();
String destination = authCodeCreateEvent.getDestination();
String authCodePlatform = authCodeCreateEvent.getAuthCodePlatform();
String authCodeCategory = authCodeCreateEvent.getAuthCodeCategory();
String authCodeKey = AuthCodeKeyConverter.convert(authCode, destination, authCodePlatform, authCodeCategory);
// 스케줄 task 등록
taskScheduler.schedule(() -> redisTemplate.delete(authCodeKey), Instant.now().plusSeconds(authCodeExpired));
}
이처럼 인증코드 생성 후 이를 저장하는 트랜잭션이 종료되면 해당 이벤트를 구독하는 쪽에서 인증코드 key 를 삭제하는 스케줄링 태스크를 현재 시간 + 유효기간 에 실행되도록 추가하여 문제를 해결했습니다.
최종 구조
'Spring' 카테고리의 다른 글
[spring] Transactional outbox pattern 을 활용해 이벤트 유실 개선하기 (0) | 2024.04.27 |
---|---|
[Spring] 역할에 따른 멀티모듈 구성으로 프로젝트 개선하기 (0) | 2024.04.11 |
스탬프 중복 적립 개선기 (0) | 2024.02.23 |
[Spring] @TransactionalEventListener 에서 CUD 가 안되는 이유 (0) | 2023.10.15 |
[Spring] 외부 API 와 비즈니스 로직 분리하기 (0) | 2023.10.15 |