DevYGwan
조회 성능 향상을 위한 캐시 처리 With Redis 본문
프로젝트를 진행하다보니, 사용자 데이터 조회시 최근 사용자의 데이터를 조회할 일이 많다는 것을 알게 되었습니다. 이를 매번 DB를 통해 조회하는 것보다 캐싱해 조회하는 것이 성능상 이점이 있겠다 라는 생각이 들었습니다. 또한 사용자 데이터는 자주 바뀌지 않기 때문에 캐시를 도입하는게 유리하다고 생각했습니다. 정리하자면, 제가 캐시를 도입한 이유는,
- 최근 사용자 데이터를 조회할 일이 많다. (캐시 히트율이 높다.)
- 사용자 데이터는 수정이 빈번하게 일어나지 않는다.
- 데이터가 손실돼도 큰 서비스 장애를 발생시키지 않는다. (다시 DB에서 가져오면 되기 때문에)
입니다. 그렇다면 캐시를 도입했을때의 기대 효과는 어떤게 있을까요? 제가 생각하는 캐시를 도입했을 때의 주 효과는 캐시 히트가 됐을 경우 DB에 접근하지 않고 캐시에서 바로 데이터를 추출하기 때문에 조회 성능이 향상됩니다. 또한 DB를 접근하지 않기 때문에 네트워크 비용 또한 절감됩니다. 따라서 저는 이러한 이유로 캐시를 도입하기로 결정했습니다.
< Long Tail 법칙 >
20%의 요구가 시스템 리소스의 대부분을 사용한다.
※ 지역 캐시(Local Cache) vs 전역 캐시(Global Cache)
지역 캐시는 서버마다 캐시를 따로 저장하는 형태로, 서버 내부의 리소스를 사용하는 캐시 방식을 말합니다. 서버 내에서 동작하는 캐시기 때문에 속도가 빠르다는 장점이 있지만 여러 WAS를 사용해 부하/분산 처리하는 환경에서는 캐시 데이터 간의 싱크를 맞춰야 하는 문제가 발생하기 때문에 이에 대한 처리가 필수적입니다.
전역 캐시는 별도의 캐시 서버에 캐시를 저장하는 형태를 말합니다. 외부 서버에 캐시를 위한 서버를 따로 두고 이에 접근하기 때문에 지역 캐시보다는 느리지만, 여러 서버에서 처리해야하는 경우 유리합니다. 지역 캐시의 대표적인 예로는 EhCache, Guava Cache, Caffeine Cache 등이 있고 전역 캐시의 대표적인 예를 Redis, MemCache 등이 있습니다.
제가 사용한 캐시는 전역 캐시입니다. 현재는 WAS가 하나만 띄워져 있지만 이후 트래픽이 증가하면 WAS을 여러개 띄울 생각도 하고 있기 때문에 확장성 면에서 전역 캐시를 사용했습니다. 아래는 제가 사용하려고 하는 서버 구조를 도식화 한 그림입니다.
LB 서버를 통해 WAS 별 로드밸런싱 처리를 진행하였고 여러 WAS 들이 하나의 캐시 서버에 접근하여 데이터를 가져오는 흐름으로 서버 구조를 정했습니다.
※ 전역 캐시에 사용한 DB
전역 캐시에는 주로 Redis, MemCache를 사용합니다. 저같은 경우에는 Redis를 사용해서 전역 캐시를 구현했습니다. Redis를 사용한 이유는,
- 이전에 사용해 본 적 있기 때문에 개발 난의도가 높지 않다.
- 공식 문서가 잘 나와있다. (https://redis.io/docs/)
- 기타 자료가 많다. ("Spring으로 캐시 구현하기" 라는 키워드로 검색해보면 Redis에 관한 구현 내용이 많다.)
등이 있습니다. 이번에는 Redis를 사용해서 전역 캐시를 구현해보았지만 기회가 된다면 이후에는 MemCache를 사용한 구현도 해볼 생각입니다.
※ PART 1 : 구현 과정에서 겪은 문제점
저는 Redis를 사용해서 최근 사용자 데이터를 캐싱했습니다. Redis를 캐싱해 사용하기 위해선 RedisCacheManager 클래스를 커스터마이징 해서 등록해야 합니다. 이 과정에서 문제가 발생했습니다. 다른 자료형은 딱히 문제가 되지 않았는데 날짜 데이터가 있는 사용자 DB의 경우 기본적인 objectMapper를 사용해서 처리했을때 아래와 같은 오류가 발생했습니다.
Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.everyTing.member.domain.Member]
해당 에러를 요약하자면 "Member 객체를 직렬화할 수 없어 직렬화 실패로 인해 발생한 예외 상황" 입니다. 해당 에러가 발생한 후, Member 데이터를 확인해본 결과 LOCALDATETIME으로 설정된 데이터로 인해 발생한 에러였습니다. 해당 데이터를 직렬화할때 에러가 발생하기 때문입니다. 따라서 데이터 직렬화 & 역직렬화를 담당하는 ObjectMapper를 커스터마이징 해 RedisCacheManager에 연결해 문제를 해결할 수 있었습니다. 아래는 커스터마이징 한 ObjectMapper입니다.
private ObjectMapper objectMapper() {
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new JavaTimeModule());
mapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);
return mapper;
}
위의 코드를 간단히 설명하자면,
- BasicPolymorphicTypeValidator를 사용하여 다형성 유형의 유효성을 검사하는 ptv 객체를 생성합니다.
- WRITE_DATES_AS_TIMESTAMPS를 비활성화하여 날짜를 타임스탬프가 아닌 사람이 읽을 수 있는 형식으로 직렬화합니다.
- FAIL_ON_UNKNOWN_PROPERTIES를 비활성화하여 알 수 없는 속성이 포함된 JSON을 역직렬화할 때 실패하지 않도록 합니다.
- JavaTimeModule을 등록하여 mapper가 Java 8의 날짜 및 시간 API를 지원하도록 합니다.
- ptv를 사용하여 mapper에 기본 타입 정보를 활성화합니다. 이는 객체를 직렬화하거나 역직렬화할 때 객체의 유형 정보를 유지하는 데 도움이 됩니다.
이렇게 됩니다. 이를 RedisCacheConfiguration에 등록하고 다시 실행하면 제대로 캐시에 데이터가 저장되는 것을 확인할 수 있습니다. 혹시라도 저와 같이 날짜 데이터를 캐시에 저장할때, 직렬화하는 과정에서 문제가 생길 경우 위의 코드를 참고하시면 문제를 해결할 수 있을 것입니다.
※ PART 2 : JPA 1차 캐시 적용
@CacheEvict(value = "member", key = "#memberId")
public Member modifyUsername(Long memberId, Username newUsername) {
final Member member = memberQueryService.findMemberById(memberId);
member.modifyUsername(newUsername);
return member;
}
멤버의 유저네임을 수정하는 로직을 위와 같이 구현했는데, 이상하게 수정이 안되는 문제가 발생했습니다. 확인해보니 유저 이름도 제대로 들어가고 최종적으로 member.modifyUsername 메서드를 통해 수정이 완료되는대도 불구하고 다시 유저 이름 조회해보면 바뀌지 않는 문제가 발생했습니다. 처음에는 캐시 처리에 관한 문제인줄 알았는데 아니였습니다.
어찌보면 당연한 거일수도 있는데, 캐시 히트가 나 DB를 거치지 않고 데이터 조회가 완료됐다면, DB에 접근하지 않기 때문에 JPA을 타지 않습니다. JPA을 타지 않기 때문에 1차 캐시로 등록되지 않고, 이로 인해 더티 체킹이 처리되지 않아 데이터가 수정이 자동으로 DB에 반영되지 않습니다. 이로 인해 생긴 문제였습니다. 그래서 save메서드를 추가하니 그제서야 제대로 DB에 수정사항이 반영됐습니다. 충분히 헷갈릴 수 있는 문제라는 생각이 들었습니다...
※ PART 3 : 캐시 사용 전략
캐시 사용 전략은 크게 읽기 전략과 쓰기 전략으로 나뉩니다. 읽기 전략의 대표적인 예는 Look aside, Read Through 전략이 있고 쓰기 전략은 Write back, Write through, Write around 전략이 있습니다. 상황에 따라 어떤 전략을 사용하느냐가 매우 중요합니다. 간단하게 어떤 전략이 있고 저는 어떠한 기준으로 선택했는지 설명드리도록 하겠습니다.
● 읽기 전략
Look aside 전략의 경우 데이터를 읽을 때(조회할 때) 캐시에 데이터가 있는지 우선적으로 확인하고 캐시에 데이터가 없다면 DB에서 조회하는 식으로 동작하는 방식입니다. 서버가 직접 캐시하고 DB에서 데이터를 조회하기 때문에 캐시가 다운되더라도 DB에서 데이터를 가져올 수 있다는 장점이 있습니다. (즉, 캐시가 다운되더라도 서비스 자체가 문제가 없다.)
Read through 전략의 경우 데이터를 읽을 때(조회할 때) 캐시에서만 데이터를 읽어오는 전략을 말합니다. 캐시에 데이터가 없을 경우 서버가 DB에 접근해서 데이터를 조회하는 것이 아닌, 캐시가 데이터에 접근해서 자체적으로 캐시를 업데이트하고 서버는 캐시에 데이터를 가져오는 방식으로 동작합니다. 따라서 전적으로 데이터 조회를 캐시에만 의존하기 때문에, redis가 다운될 경우 서비스가 중단되는 단점이 있습니다.
Look-Aside와 다른 점은 읽기에 대한 앱의 관점입니다. Look-Aside의 경우 Cache Miss가 나면 앱이 직접 DB에 데이터를 조회한 반면, Read-Through는 캐시에서 DB에 데이터를 직접 조회하여 로드합니다.
두 전략 모두, 초반에는 캐시가 비워져 있기 때문에 캐시 미스율이 높습니다. 만약 서비스 초기에 트래픽이 많이 발생한다면, DB 부하 및 Cache 업데이트 등에 많은 오버헤드가 발생할 수 있습니다. 따라서 이 경우, 미리 캐시에 DB 데이터를 저장하는 작업이 있는데, 이를 Cache Warming 이라고 합니다.
저는 캐시의 읽기 전략으로 Look aside 전략을 선택했습니다. 이 전략을 사용한 이유는 캐시가 다운되었을 경우에 서비스에 영향을 미치지 않게 하기 위해서였고 @Cacheable을 통해 간단하게 구현이 가능하기 때문에 해당 전략을 사용했습니다. 그리고, 전역 캐시를 도입했기 때문에 Cache Warming은 적용하지 않았습니다. Cache Warming을 스프링에서 구현하는 방법이 여러가지가 있었는데, 제가 찾은 방식은 스프링 서버가 시작할때 하는 방법, 스케쥴러를 통한 특정 시간대에 처리하는 방법이 있었습니다.
저같은 경우에는 특정 시간대에 처리하는 것은 최근 접속한 사용자들을 기반으로 캐싱 처리해야되니 의미가 없다고 생각했습니다. 그래서 스프링 서버가 시작할때 하는 방법으로 구현하려고 했습니다. 하지만 저는 전역 캐시로 캐시 처리를 했기 때문에 스프링 서버가 꺼진다고 redis가 꺼지지 않기 때문에 의미가 없었습니다. 둘의 생명주기가 같지 않기 때문입니다. 따라서 저는 이러한 이유로 Cache Warming을 적용하지 않았습니다. 만약 제가 지역 캐시를 통해 캐시를 구현한다면 캐시와 스프링 서버의 생명주기가 같기 때문에 Cache Warming을 구현하면 성능상 효과적일 것이라고 생각했습니다.
@Cacheable(value = "member", key = "#memberId")
public Member findMemberById(Long memberId) {
//데이터 조회
}
● 쓰기 전략
Write back 전략은 쓰기 리소스를 캐시에 모아놨다가 일정 주기로 배치 작업을 통해 DB에 반영하는 방식입니다. 캐시에 모아놨다가 DB에 한번에 쓰기 때문에 부하를 줄일 수 있다는 장점이 있지만 DB에 반영 전까지 캐시와 DB 데이터 간의 싱크가 안맞는다는 단점이 있습니다. DB에 반영되기 전에 캐시가 날라간다면, 데이터가 소실된다는 단점이 있습니다.
Write through 전략은 쓰기 리소스를 DB와 캐시에 동시에 반영하는 방식입니다. 이때 DB에서의 동기화 작업은 캐시가 담당합니다. 즉, DB 동기화 작업을 캐시에게 위임합니다. 데이터가 동시에 반영되기 때문에 DB와 캐시 데이터가 항상 최신 상태로 유지됩니다.(데이터 일관성이 유지됨) 하지만 데이터 저장 시 캐시와 DB에 동시에 반영되기 때문에 느리다는 단점이 있습니다.
Write around 전략은 쓰기 리소스를 DB에 직접 반영하는 방식입니다. 이때 캐시는 갱신하지 않습니다. 캐시의 갱신은 캐시 미스가 발생하는 경우에만 갱신합니다. 쓰기 내용을 캐시에 당장 반영하지 않기 때문에 쓰기 작업 이후 데이터가 바로 조회되지 않는다면 불필요한 캐시 메모리를 차지하지 않기 때문에 캐시 메모리를 아낄 수 있습니다. 하지만, 캐시 미스가 발생하기 전에 DB에 저장된 데이터가 수정되었을 경우 캐시와 DB간의 데이터 싱크가 안맞을 수 있습니다. 따라서 이것에 대한 축가적인 처리가 필요합니다.
저는 캐시의 쓰기 전략으로 Wrie around 전략을 선택했습니다. 저같은 경우 유저 데이터이기 때문에 DB와 캐시간의 싱크가 맞지 않으면 문제가 될 수 있습니다. 또한 DB에 반영되기 전에 캐시가 날라가 데이터가 소실되면 유저 데이터가 날라가는 것이기 때문에 위험하다고 생각했습니다. 또한, 데이터가 쓰고 난 이후에 바로 사용하는 경우가 필수적이지 않기 때문에 사용자 데이터를 조회할때의 캐시 미스를 통한 캐시 업데이트가 캐시 메모리를 낭비하지 않게 해줄 수 있을 것이라고 판단했습니다. 따라서 데이터가 만약 수정되었을 경우 캐시에 저장되어있는 데이터를 삭제하여 캐시 미스를 강제합니다. 이렇게 해서 해당 사용자 데이터의 조회 요청이 올 경우 캐시 미스가 되어 DB에서 데이터를 가져오는 식으로 DB와 캐시 간의 데이터 싱크를 맞출 수 있습니다.
@CacheEvict(value = "member", key = "#memberId")
public Member modifyUsername(Long memberId, Username newUsername) {
// 데이터 수정
}
※ PART 4 : Redis 설정
Redis의 설정을 관리하고 싶으면 redis.conf 파일을 작성해 적용하면 됩니다. 기본적으로 Redis는 내장된 기본 구성을 사용하여 구성 파일 없이 시작할 수 있습니다. 하지만 이 설정은 말 그대로 "기본"이기때문에 공식 문서에도 테스트 및 단순 개발 목적으로만 사용할 것을 권장하고 있습니다. 설정 파일 안에 여러 설정을 추가할 수 있는데, 제가 처리한 중요한 옵션들에 대해서 설명드리도록 하겠습니다.
1. 최대 메모리 제한 - max-memory 옵션
Redis의 메모리 사용량을 제한하는 옵션입니다. 기본적인 Redis의 max-memory 값은 0입니다. 즉, 메모리 사용량 제한이 없다는 말을 의미합니다. (32bit 환경은 3) Redis는 인 메모리 캐시 형태로, 데이터를 제한 없이 저장하는 것은 캐시 메모리 낭비가 될 수 있고 많은 오버 헤드를 발생시킬 수 있습니다. 따라서 max - memory 옵션을 통해 최대 메모리 양을 지정할 수 있습니다. 만약 설정한 최대 메모리 양보다 Redis에 저장되는 값이 많다면, 추가적으로 설정한 메모리 삭제 전략에 따라 삭제가 이루어집니다.
2. 메모리 Eviction 전략 - memory-policy 옵션
위에서 설정한 최대 메모리 양을 넘어서는 데이터가 추가로 들어오면 어떻게 처리해야할까? 이것에 대한 설정을 해주는 옵션입니다. 기본적으로는 "noeviction" 전략으로 동작합니다. 이 전략은 캐시를 지우지 않는 정책으로 메모리가 최대 메모리 이상을 사용하게 되면, 새로운 값이 저장되지 않습니다. 이 전략을 수정하고 싶다면, 해당 옵션을 통해 수정할 수 있습니다. 만약 max-memory 값을 설정하고 memory-policy 옵션을 수정하지 않는다면, Redis에 최대 메모리를 넘어선 데이터가 추가되면 캐시에 반영되지 않을 것입니다. 이는 서비스 장애를 발생시킬 수 있기 때문에 이에 대한 처리가 필수적입니다. 물론 ttl 설정을 통해 관리할 수 있겠지만, 트래픽이 한번에 몰릴 경우 이 또한 한계가 있기 때문에 필수적입니다. Redis의 eviction 전략은 여러개가 있는데 간단하게 설명하자면,
- no eviction : 메모리 제한에 도달하면 새 값이 저장되지 않습니다.
- allkeys-lru : 가장 최근에 사용한 키를 보관하고, 오랫동안 참조되지 않은 키(LRU)를 제거합니다
- allkeys-lfu : 자주 사용하는 키를 보관하고, 가장 자주 사용하지 않는 키(LFU)를 제거합니다
- volatile-rru : 만료 필드가 true로 설정된 상태에서 가장 최근에 사용된 키를 제거합니다.
- volatile-lfu : 만료 필드가 true로 설정된 상태에서 가장 자주 사용하지 않는 키를 제거합니다.
- allkeys-random : 키를 랜덤으로 제거하여 추가된 새 데이터를 위한 공간을 만듭니다.
- volatile-random : 만료 필드가 true로 설정된 키를 임의로 제거합니다
- volatile-ttl : 만료 필드가 true로 설정되고 남은 TTL(최소 잔여 시간) 값이 가장 짧은 키를 제거합니다
실제로 공식 문서에서도 Redis를 캐시로 사용할 경우 위의 2개의 옵션을 추가하는 것을 권장하고 있습니다.
3. 데이터 백업 정책 - RDB / AOF
Redis 백업 정책은 크게 4가지로 분류됩니다.
- RDB(Redis DataBase) : SnapShotting 방식이라고도 하며, 특정한 간격마다 메모리에 있는 데이터를 스냅샷 수행
- AOF(Append Only File) : 모든 쓰기 작업(Write / update)을 로그 파일로 저장
- No persistence : 백업 비활성화
- RDB + AOF : RDB와 AOF을 동시에 사용
RDB는 .rdb 확장자 파일에 Redis의 데이터를 저장합니다. 메모리에 있는 내용을 스냅샷을 생성하여 백업때 이를 사용하기 때문에 속도가 빠릅니다. 하지만, 특정 시점마다 스냅샷을 생성하기 때문에 스냅샷 도중에 서버가 꺼지면 데이터가 사라진다는 단점이 있고 스냅샷 추출에 시간이 오래 걸린다는 단점이 있습니다.
SAVE 60 1000 # 60초 이후 1000개의 쓰기 발생 시 SNAPSHOT 수행
- SAVE : 순간적으로 Redis 동작을 정지시켜 Blocking 방식으로 스냅샷을 디스크에 저장
- BESAVE : 백그라운드 SAVEf라는 의미로 자식 프로세스를 생성하여 Non - Blocking 방식으로 스냅샷 생성 ( fork을 통해 자식 프로세스를 생성하기 때문에 메모리 사용에 주의)
AOF는 .aof파일에 로그 파일로 Redis의 쓰기 관련 연산 자체를 저장합니다. 조회를 제회한 입력/수정/삭제 명령이 실행될때마다 기록되고 서버가 재시작될때 로그 파일에 기록된 연산을 재 실행하는 형태로 데이터가 복구됩니다. 실시간으로 명령어가 저장되기 때문에 데이터가 유실되지 않는다는 장점이 있지만 연산 자체를 로그 파일에 남기기 때문에 파일 크기가 크고, 복구 시 파일에 저장된 연산을 다시 실행하기 때문에 복구 시간이 느리다는 단점이 있습니다.
appendOnly yes # AOF 모드 킴
저같은 경우에는, 캐시가 꺼지거나 날아가도 캐시 미스가 날 뿐 서비스에 큰 장애를 가져오지 않습니다. 따라서 굳이 리소스를 낭비해가며 백업 정책을 가져갈 필요가 없다고 생각해 RDB와 AOF 모드를 껐습니다. 또한 최대 메모리의 경우 200mb, 교체 알고리즘은 LRU로 설정해 최대 메모리 제한과 교체 알고리즘을 정의했습니다.
추가적인 내용이 궁금하시다면
- Redis.conf 옵션 관련 내용 - https://redis.io/docs/management/config/
- Redis memory - policy 관련 내용 - https://redis.io/docs/reference/eviction/
- Redis 백업 정책 관련 내용 - https://redis.io/docs/management/persistence/
을 참고하시면 자세히 나와 있으니 이해에 도움이 되실 거라고 생각합니다.
※ Redis 의 Default Options
레디스의 Default 설정입니다. 보시는 바와 같이 백업 모드는 RDB 방식이 활성화되어있고 AOF 모드는 꺼져있는 것을 확인할 수 있습니다. 또한 최대 메모리 제한은 없고 메모리 정책 또한 no-eviction으로 설정되어 있는 것을 확인할 수 있습니다.
※ PART 5 : Redis 설정 파일 적용
1. redis - cli 에서 명령어를 통해 적용
위의 사진과 같이, CONFIG SET을 통해 실시간으로 변경이 가능합니다. 하지만, 이 경우 Redis가 꺼지면 다시 변경 전의 설정으로 돌아간다는 단점이 있습니다.
2. redis - cli 에서 명령어를 통해 적용
redis.conf 파일을 작성하고 redis 서버를 띄울때 해당 파일을 연결해서 서버를 띄우는 방법입니다. 보시는 바와 같이 redis.conf 파일에는 제가 원하는 설정 값을 입력하고 서버를 가동할때 설정값이 제가 원하는 값으로 설정된 것을 확인할 수 있습니다.
3. docker로 redis 사용시, dockerfile을 가지고 redis 이미지를 만들어서 적용하는 방법
위의 사진은 Dockerfile 작성시 내가 원하는 redis.conf 파일이 적용된 redis 이미지가 만들어집니다. 이 이미지를 가지고 컨테이너에 redis을 띄우게 되면 내가 원하는 설정으로 서버가 띄워집니다. 해당 Dockerfile의 내용을 간단히 설명하자면,
- redis 공식이미지를 사용해서 빌드
- 현재 디렉토리에 있는 redis.conf파일을 컨테이너 내부의 /user/local/etc/redis/redis.conf 경로로 복사
- 컨테이너가 시작될때 redis-server 명령을 실행하고, 구성파일로 /usr/local/etc/redis/redis.conf를 사용
COPY 대신 VOLUME을 통해 구현할 수도 있습니다.
VOLUME /usr/local/etc/redis/
Redis 컨테이너를 실행할 때 호스트의 디렉토리와 컨테이너의 /usr/local/etc/redis/ 경로가 공유됩니다. 따라서 호스트에서 Redis 구성 파일을 수정하면 컨테이너에서도 해당 변경 사항이 반영됩니다.
아니면 이미지를 가지고 컨테이너를 띄울때 커맨드에 직접 작성하는 방법도 있습니다. 하지만 개인적으로 dockerfile에 작성하는 것이 더 깔끔하다는 생각이 들어 이 방법보다는 위의 방법을 더 추천합니다.
docker run -v /myredis/conf:/usr/local/etc/redis --name myredis redis \
redis-server /usr/local/etc/redis/redis.conf
단순히 Redis를 캐시로 사용하는 것은 여러 블로그를 통해 금방 구현할 수 있을 것입니다. 하지만 공식 문서에 있는 내용처럼 그 밖에 보이지 않는 부가적인 처리를 하지 않는다면 처음에는 문제가 없겠지만 후에 문제가 생길 확률이 높습니다. 항상 의심하고 의심하고 또 의심해야 좋은 개발자가 될 수 있는 것 같습니다.
PS :
처음에는 단순히 "Redis로 캐시 기능을 구현하기만 하면 되겠다." 라는 생각이 들었습니다. 이에 대한 내용을 찾아보니, Redis 캐시 설정과 함께 @Cacheable, @CachePut, @CacheEvict를 사용하면 간단하게 구현할 수 있을 것이라고 예상했습니다. 실제로도 단순한 사용은 간단했구요. 근데 진환이형이랑 같이 이후 스텝을 고민하면서 "이런게 문제가 될 수 있겠다. 어떤 상황에서 문제가 생기고 어떻게 문제를 해결할 수 있을까?" 라는 고민을 하게 되면서 Redis 공식 문서를 보면서 서로 대화하고 한 과정들이 정말 재밌었던 것 같습니다. 고민하고 토론할 수 있는 사람이 있다는게 정말 중요하다고 생각합니다.
'Study > Spring' 카테고리의 다른 글
DB관련 Test 코드 작성 시 유의할 점 :: @DataJpaTest (0) | 2024.05.04 |
---|---|
JVM Memory의 3가지 메트릭 지표 - Committed Memory란? (0) | 2024.04.02 |
@Transactional 잘 모르고 쓰면 오히려 독이 될 수 있다. (0) | 2024.01.09 |
외부 API 사용 :: 마지막까지 깔끔하게 (2) | 2023.12.30 |
Spring Mail 인증 비동기 처리 & 비동기 retry 정책 적용 (1) | 2023.12.17 |