티스토리 뷰

반응형

자세한 건 Github를 참고해주시기 바랍니다.

배경

개인 프로젝트를 진행하면서 redis를 인증번호 저장을 목적으로 개발을 하고 테스트 하던 과정에서 저장이 되더라도 get을 하지 않는 이슈가 발생하였습니다. 이를 트러블 슈팅하기 위해서 포스팅을 하게 되었습니다.

원인

@Transactional
fun authorizePhoneNumber(phoneNumber: String, authNumber: Int): Boolean {

    val secureNumber = authNumberRepository.findByIdOrNull(phoneNumber.substring(4))
       ?: throw SecureNumberNotFoundException(phoneNumber)

    return secureNumber.toInt() == authNumber
}

위 코드에서 phoneNumber key에 저장된 secureNumber를 가져와 매칭 유무를 반환해줘야했습니다. 하지만 이를 확인하기 위한 테스트코드에서 계속해서 인증번호를 찾을 수 없다는 오류가 발생하였고 원인을 굉장히 오랫동안 찾았던 것 같습니다.

계속해서 찾아보던 중 단톡방에서 우연히 고수님이 redis에 Transactional을 의심해보라는 말씀을 해주셨고 다행히 여기서 원인을 찾을 수 있었습니다.

어떤 issue가 있을지 알아보기 위해 내부 코드를 살펴 보겠습니다.

    /**
     * Get value for given {@code hashKey} from hash at {@code key}.
     *
     * @param key must not be {@literal null}.
     * @param hashKey must not be {@literal null}.
     * @return {@literal null} when key or hashKey does not exist or used in pipeline / transaction.
     */

    @Nullable
    HV get(H key, Object hashKey);

핵심은 @return {@literal null} when key or hashKey does not exist or used in pipeline / transaction. 이 구문이었습니다. transaction 범위에 있어야 값을 반환한다고 되어있습니다.

하지만 Redis Transaction을 @Transactional 로 처리하게된다면 메서드 전체가 multi-exec 범위가 되고 중간에 get을 하더라도 exec 범위를 벗어나야 return이 되기 때문에 의미가 사라집니다. 그래서 계속 null을 반환했던 것입니다.

해결

먼저 Transaction 연산이 필요한 상황인가를 고민해봤습니다. 옛날에 보았던 우아한 Redis 세션에서 발표자가 Redis는 모든 명령어를 single-thread로 처리하며 원자성이 보장된다고 말했던 것이 생각났습니다. 그리고 단순히 저장 된 값을 key 기반으로 읽어오는 연산에서 transaction 연산이 필요하지 않다고 판단하였습니다.

 

@Transactional 어노테이션을 지우고 다시 테스트 해보니

테스트 모두 통과하였다 !!!

후기

모든 기술은 사용하게 된 목적을 알고 용도에 맞게 적절히 사용하는 것이 중요하다는 것을 요즘 더더욱 깨닫는거 같습니다. Redis의 특성을 알고 설계 단계에서 적절하게 도입하여 사용했다면 이런 문제를 겪지 않았을 것입니다.

마침.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함