※ 본 글은 실제 서비스에서 적용된 사례를 기반으로 작성하였으나, 서비스명·도메인·DB 구조 등 민감한 정보는 모두 익명 처리하였으며, 외부 공개를 위해 일부 구현 방식은 재구성하였습니다.
내가 개선하고자 했던 검색 기능은 프로젝트의 핵심 기능 중 하나로, 관리자들이 다양한 필터 조건을 기반으로 수시로 데이터를 조회하는 구조였다. 그러나 아래와 같은 제약으로 인해 성능 저하가 발생하고 있었다.
→ 단순한 쿼리 튜닝이나 페이징 처리만으로는 해결이 불가능했다.
단순 결과 캐싱은 조건 조합이 너무 다양하고, 실시간성이 중요한 검색 기능에 적합하지 않았다.
→ 캐시의 실효성이 없었고, 성능 개선 효과도 거의 없음
위와 같은 상황때문에 단순히 결과를 캐시하는 것이 아니라,
Redis를 보조 인덱스로 활용해 쿼리 대상 자체를 줄이는 전략을 설계하였다.
핵심 목적: 풀스캔 제거, 대상 건수 감소, 쿼리 병목 해소
사용자 행동 데이터 기반으로 분석
우선순위 | 조건명 |
---|---|
1 | dbSource |
2 | UniversityCode |
3 | masterUniversityCode |
4 | changeJobCount |
5 | adminId |
예시 키 구조
resume:search:{dbSource}:{bachelorUniversityCode}:{masterUniversityCode}:{adminId}
resume:search:DB2:null:null:7
모든 조건이 없어도 "null" 문자열로 명시하여 키 일관성을 유지함으로써 모든 요청이 캐시 hit가 가능하도록 설계하였다.
WHERE id IN (...)
쿼리 수행 💡 실제로 Redis 선 필터링으로 50% 이상 데이터가 제거되었으며, DB 조회량은 수백만 건에서 수만 건 이하로 축소됨
실측 데이터 기준
항목 | 개선 전 | 개선 후 | 비고 |
---|---|---|---|
평균 응답 시간 | 2,325ms | 450ms | Redis 선 필터링으로 쿼리 대상 축소, 풀스캔 제거 효과 반영 |
전체 대상 수 | 1,500,000건 | 필터링 후 평균 50~100K 건 | Redis 보조 인덱싱으로 검색 대상 범위 대폭 축소 |
Redis 캐시 hit율 | 없음 | 100% | null 포함 명시적 키 구성으로 모든 요청에 대해 캐시 hit 유도 |
GC 병목 | 빈번 | 없음 | 전체 객체 로딩 대신 ID 기반 조회로 메모리 사용량 최소화 |
Redis 캐시 hit율은 왜 100%인가?
- 모든 조건은
null
일 경우 명시적으로"null"
문자열로 치환되기 때문에,
모든 요청은 항상 유효한 Redis 키로 변환된다.- 이 구조상 Redis는 단순 결과 캐시가 아니라 조건 조합 인덱스 역할을 수행하며,
설계상 miss가 발생하지 않도록 되어 있다.- Redis에 해당 키가 없다면 최초 miss지만, 이후 요청부터는 항상 hit된다.
➡️ 캐시 hit율은 논리적으로 100%에 수렴한다.
null
처리와 adminId
기반 분기처리로 정합성과 보안까지 확보인덱스 못 박는 환경에서 버벅이는 쿼리 때문에 고생하고 있다면,
이 방식이 꽤 쓸만할 수도 있다.
Redis는 꼭 결과 캐싱에만 쓰라는 법 없다.
오히려 이렇게 "보조 인덱스"로 쓰는 전략이 실전에서 더 빛을 발하는 경우도 있다.
이 경험이 비슷한 문제를 겪는 팀들에게 작은 참고라도 되길...