ZSet(정렬된 집합)은 각 원소에 점수(score)를 부여하고 자동 정렬이 되는 Redis 자료구조입니다.
ZINCRBY
: 특정 키의 점수를 증가시킴 O(logN)
ZREVRANGE
: 높은 점수 순으로 정렬된 상위 항목 조회 O(logN + M) (M: 반환할 개수)
ZSet의 시간복잡도는 ZINCRBY
/ZREVRANGE
모두 평균 O(logN)입니다. 높은 조회 성능이 필요한 실시간 트래픽 환경에 적합합니다.
Redis는 단일 스레드 기반의 명령어 실행 모델을 사용하기 때문에, ZINCRBY
같은 연산도 동시에 요청되어도 race condition 없이 순차적으로 처리됩니다.
동시성이 필요한 상황에서도 Redis는 안전한 연산 보장을 해줍니다. 다만 트래픽 급증 시 Redis 성능 모니터링 및 클러스터 구성 고려는 필수입니다.
보통 인기 검색어 ZSet에는 TTL을 설정하지 않는 경우가 많습니다.
대신, 점수 자체에 기간 조건을 반영하는 방식으로 단기/장기 랭킹을 관리합니다.
TTL을 설정하면 인기 검색어 데이터가 갑자기 사라질 수 있으므로, 랭킹용 데이터에는 명시적 만료 대신 주기적 정리(batch 처리)가 더 안정적
Spring Cache 추상화는 @Cacheable
, @CachePut
, @CacheEvict
등의 어노테이션 기반으로 캐싱을 쉽게 적용할 수 있게 해줍니다.
장점:
RedisTemplate은 유연하지만 코드가 복잡해지며, 비즈니스 로직과 캐시 로직이 섞일 수 있습니다. 빠르게 캐시를 적용하고 유지보수를 쉽게 하기 위해 Spring Cache가 적합합니다.
Look-Aside(혹은 Lazy-Loading) 캐싱은 요청이 들어올 때 캐시에서 먼저 조회하고, 없으면 DB 등에서 조회한 후 캐시에 저장하는 방식
동작 흐름:
1. 캐시에 데이터 있는지 확인
2. 없으면 원본 데이터 소스에서 조회
3. 캐시에 저장하고 응답
Look-Aside는 실패 시 유연하게 대응 가능하며, 캐시 미스 시 fallback이 쉬운 장점이 있습니다. 반면, Read-through는 미스가 발생해도 내부적으로 자동으로 로딩되므로 설정은 더 복잡할 수 있습니다.
정제 기준:
일반적으로 고정 TTL(Time-To-Live)을 설정합니다. 예: 5분, 10분 등.
검색 결과는 빈번히 변경되지 않으므로 짧은 TTL로 캐싱하고, 만료 후 자동으로 재조회하는 방식이 적절합니다. Redis 메모리 보호 측면에서도 일정 TTL 설정은 필수입니다.
10분 TTL
로 통일한 이유:
데이터 정합성 유지와 TTL 관리 복잡도 최소화 때문입니다.
TTL을 분리하는 것도 가능하지만, TTL 차이로 인한 동기화 문제나 불일치 현상이 생길 수 있어 관리 복잡도가 증가합니다.
추가적으로, 자주 조회되는 검색어를 사전에 Pre-warm하거나 Hot 키로 분류하여 TTL을 연장하는 전략도 고려할 수 있습니다.
캐시 미스 발생이 잦은 키는 Access Log를 기반으로 식별하여 Preloading하거나 TTL을 유동적으로 조정하면 효과적입니다.
TTL 유형 | 문제점 |
---|---|
너무 짧은 TTL | 캐시 미스 빈번 → Redis/DB 부하 증가, 응답 지연 |
너무 긴 TTL | 오래된 데이터 제공 가능 → 최신성 저하, UX 악화 |
특히 인기 검색어와 같이 실시간성이 중요한 기능에서는 TTL이 너무 길 경우 트렌드 반영이 늦어지는 문제가 생깁니다.
트래픽 특성에 따라 TTL을 동적으로 조절하는 전략 (예: LFU 기반 가중 TTL) 도 고려할 수 있습니다.
ZINCRBY
로 점수를 증가시키고, ZREVRANGE
로 상위 N개의 검색어를 빠르게 추출할 수 있습니다.정렬된 데이터를 Top-N 형태로 자주 조회해야 하는 상황에서는 ZSet이 List나 Hash에 비해 성능/구현 면에서 매우 유리합니다.
ZREVRANGE popular_keywords 0 9
로 진행 (Top 10 예시)기준:
운영 환경에서는 시간대별 ZSet(예: popular_keywords:20250526
)을 분리하여 시간 기반 통계도 제공합니다.
인기 검색어가 너무 자주 바뀌면 사용자 혼란을 유발할 수 있으므로, 일정 주기마다 스냅샷을 저장하고 사용자에게 제공하는 데이터는 완화된 버전을 사용합니다.
조회수는 Redis에서 INCR
명령어를 통해 증가시키고 있습니다.
view:song:{id}
라는 키에 대해 INCR
을 호출하면 해당 곡의 조회수가 1씩 증가합니다.INCR
은 존재하지 않는 키에 대해 호출하면 자동으로 0에서 시작되므로 별도의 초기화 없이도 사용이 가능합니다.
INCR
은 Redis의 Atomic Integer Increment 연산으로, 지정된 키의 값을 정수 1만큼 증가시킵니다.
INCR
연산은 Race Condition 없이 동시성 안전하게 처리됩니다.멀티스레드 환경에서도 별도 Lock 없이 안전하게 사용할 수 있다는 점이 핵심
1시간 TTL은 다음 목적에 최적화된 시간입니다:
다른 TTL의 문제점:
두 키를 분리한 이유는 역할과 기능의 분리입니다:
view:song:{id}
: 전체 곡의 누적 조회수를 관리view:song:{id}:user:{userId}
: 해당 사용자의 중복 조회 여부를 체크이렇게 분리함으로써, 조회수 집계는 INCR로 빠르게 처리하고, 어뷰징 제어는 개별 사용자 키의 TTL 존재 여부로 판단할 수 있습니다.
하나의 키에 모든 정보를 넣으면 복잡해지고 성능 저하 위험이 있습니다. 역할 분리를 통해 관리성과 확장성을 높입니다.
키 구조 예: view:song:123:user:456
INCR
실행 후, 해당 키를 생성하고 TTL을 1시간 설정User ID 기반 중복 확인을 통해 서버 측에서 중복 여부를 실시간으로 체크합니다.
TTL 설정을 통한 자동 정리
→ 1시간 후 자동 삭제되어 메모리 점유 지속적으로 해소
Redis의 메모리 정책 활용
→ maxmemory-policy
를 volatile-ttl
등으로 설정하면 TTL이 있는 키 위주로 제거됨
Key Prefix 제한
→ view:song:*:user:*
키 수가 너무 많아질 경우, 인기 콘텐츠에만 제한 적용하거나 샘플링 적용 가능
LRU 캐시 정책 + 모니터링
→ Redis 메모리 상태를 실시간으로 확인하고, Keyspace notification 등을 활용한 자동 감시
자정(00:00)은 일일 통계 기준의 시작점으로, 대부분의 로그, 마케팅 분석, 통계 집계 기준 시점과 일치합니다.
자정을 기준으로 초기화하면 일별 조회수, 일일 인기 콘텐츠 순위 등을 계산할 때 기준이 명확해지고, UI에 노출되는 데이터도 사용자에게 익숙한 기준이 됩니다.
Spring Boot의 @Scheduled 애노테이션과 cron 표현식을 사용했습니다.
실행 주기를 설정하고, 조회수 관련 Redis 키 삭제 로직을 메서드로 분리하여 명확하게 관리했습니다.
@Scheduled(cron = "0 0 0 * * *")
public void clearViewCountCache() {
// Redis 키 삭제 로직
}
@EnableScheduling
설정이 누락되면 스케줄링이 동작하지 않기 때문에 반드시 Application 클래스에 설정이 필요합니다.
사용한 표현식: 0 0 0 * * *
크론 표현식의 시간 기준은 JVM이 실행 중인 서버의 로컬 타임존을 따르기 때문에, 시간대 문제가 발생하지 않도록 설정 확인이 필요합니다.
대응 방안:
1. 실패 로깅 및 모니터링
@Scheduled(fixedDelay = X)
등으로 일정 간격으로 재시도하거나, 실패 시 별도 알람이 울린 후 수동 실행 가능view:reset:YYYYMMDD
같은 키를 남겨, 하루에 한 번만 초기화되도록 제어Redis가 다운된 경우를 대비해 삭제 로직은 반드시 예외 처리를 감싸고, 실행 실패 여부를 기록해야 추후 대응이 쉬워집니다.
주요 단점:
KEYS view:song:*
는 Redis 전체 키 공간을 탐색하기 때문에 대량의 키가 존재할 경우 성능 저하 및 블로킹 발생KEYS
명령 사용은 권장되지 않습니다운영 환경에서는 SCAN
명령으로 패턴 탐색 후 batch로 삭제하는 방식(SCAN
+ DEL
)을 사용해야 성능과 안정성을 확보할 수 있습니다.
KEYS
+ DEL
조합은 데이터가 많을수록 Redis 블로킹 현상이 발생할 수 있어 위험합니다.SCAN
명령어를 반복 호출하여 배치로 삭제하거나, 비동기 삭제(SCAN + UNLINK) 방식으로 전환했습니다.Redis 4.0+에서는 UNLINK
명령어를 활용하면 키를 비동기적으로 삭제할 수 있어 성능 저하를 줄일 수 있습니다.
popular_keywords
)나 조회수(view:song:{id}
) 데이터를 별도 Redis 키 혹은 RDB에 저장합니다.하루 단위로 집계된 데이터를 RDB에 저장해두면 장기 통계, 주간/월간 랭킹 분석 등에도 활용 가능합니다.