1️⃣ Redis란 무엇인가?


- Remote Dictionary Server의 약자이며, 메모리 기반의 Key - Value 저장소 이다.
- 데이터구조 서버 : 문자열(String) , 리스트(List), 셋(Set), 해시(Hash), 정렬된 셋(Sorted Set) 등을 지원
- 비동기식 복제, Lua 스크립트 실행, LRU 기반 캐시, Pub/Sub 지원
- 다양한 언어의 클라이언트 라이브러리 제공 ( Java, Python, Node.js 등 )
- NoSQL, In-memory, 단일스레드
2️⃣ Redis가 빠른 이유


✅ 메모리 기반 저장소
- 모든 데이터를 메모리에 저장하고, 디스크는 백업용으로만 사용 ➡ 빠른 접근 속도
- Memory Access 시간은 Random disk I/O보다 몇 배는 더 빠르다.
- 메모리 엑세스는 높은 Read / Write 처리량과 낮은 지연이라는 장점이 있다.
- 단점으로는 데이터 저장 크기가 메모리라서 크지 않다는 점이다.
✅ 단일 스레드 이벤트 루프 기반
- Redis는 단일 스레드로 작동 ➡ 락(Lock) 경합이 없음
- 요청은 순차적으로 처리, 컨텍스트 스위칭 비용이 적다.
- 단일 스레드지만 빠른 이유는 - [ I/O 멀티플렉싱 모델 ] ⭐⭐ 을 적용했기 때문이다.
[ I/O Multiplexing ]
- Blocking I/O
I/O 작업이 완료 될때 까지 해당 작업을 요청한 프로세스 또는 스레드의 실행을 중지시키는 작업 방식

- Non-Blocking I/O
프로세스 또는 스레드가 I/O 작업을 수행하는 동안 대기 상태에 빠지지 않고, 다른 작업을 계속 진행할 수 있도록 하는 모델

- I/O Multiplexing
I/O Multiplexing은 하나의 스레드가 여러 클라이언트 소켓을 비동기적으로 감시하고, 이벤트가 발생한 소켓에 대해서만 입출력 작업을 수행하는 방식입니다. 대표적으로 select, poll, epoll, kqueue 시스템 호출이 있다.

- select: 모든 소켓 상태를 매번 검사 ➡ O(n)
- poll: select와 유사하지만 매번 최대 fd까지 loop를 도는 select과 달리 poll 실제 fd 개수 만큼만 loop만 돌게 끔 구현할 수 있어 select 보다 시스템 콜 호출이 적다 ➡ O(n)
- epoll (Linux) / kqueue (FreeBSD, macOS): 변경사항이 있는 소켓만 알림 ➡ Q(1)
⭐ Redis가 왜 빠른가요? 질문이 왔을때 I/O Multiplexing인 epoll 방식을 선택하기 때문이다 ! !
Redis는 플랫폼에 따라 적절한 I/O 멀티플렉싱 방식을 선택합니다.
- Linux: epoll
- macOS / FreeBSD: kqueue
- Solaris: export
이러한 리엑터
- Event Loop와의 관계

Redis는 내부적으로 이벤트 루프 구조를 사용해 클라이언트 요청을 처리합니다. 이벤트 루프는 다음과 같은 흐름으로 동작합니다.
- 클라이언트로부터 명령 수신 준비
- I/O 멀티 플렉싱(select, epoll 등)으로 이벤트 감시
- 이벤트가 발생한 소켓에 대해 처리 함수 실행
- 명령 처리 후 응답 전송
- 다시 1번으로 루프 반복
즉, Redis의 이벤트 루프는 epoll 등의 I/O 멀티플렉싱 메커니즘과 밀접하게 연동되어 있으며, 이로 인해 동시에 수천 개의 커넥션을 하나의 스레드로도 처리할 수 있게 됩니다.
Node.js나 Netty가 사용하는 리액터 패턴과 동일한 방식으로, 단일 스레드로도 높은 동시성과 처리량을 보장할 수 있는 설계다.
- Redis의 최적화 전략
- O(n) 연산 회피: Redis는 O(n) 연산이 성능에 큰 영향을 미친다는 철학을 가짐. CPU 연산이 길어지면, 요청의 응답도 지연되기 때문이다.
- KEYS * ➡ ❌ : 전체 탐색 O(n), 운영환경에서 비권장
- SCAN ➡ ✅ : 커서 기반, 점진적 탐색
- DELETE ➡ ❌ : 동기 삭제로 블로킹 발생
- UNLINK ➡ ✅ : 비동기 삭제로 메모리 해제를 다른 스레드가 수행
- 다중 CPU 활용: Redis는 기본적으로 싱글 스레드지만, 멀티 코어를 활용하고 싶을 경우 여러 개의 Redis 인스턴스를 띄우는 방식으로 우회할 수 있다. (포트 다르게 설정).
✅ Redis의 효율적인 자료구조

Redis는 단순한 Key - Value 저장소를 넘어, 내부적으로 다양한 용도에 최적화된 저수준 자료구조(encoding)를 사용하여 성능과 메모리 효율을 극대화함. Redis는 저장된 값의 크니나 항목 수에 따라 자동으로 가장 적절한 자료구조를 선택.
ziplist (압축 리스트)
- 사용 위치: 작은 크기의 Hash, List, ZSet
- 구조: 연속된 메모리 블록에 데이터를 압축하여 저장
- 장점: 메모리 사용량이 작고 CPU 캐시 친화적
- 전환 조건: 설정된 임계치를 넘으면 다른 구조로 전환됨 (ex. list-max-ziplist-entries)
intset (정수 집합)
- 사용 위치: 작은 Set 중 모든 값이 정수일 때
- 장점: 정수로만 구성된 Set을 메모리 효율적으로 저장
- 예시: SADD myset 1 2 3 → intset으로 저장됨
quicklist (ziplist + linked list)
- 사용 위치: Redis 3.2 이후 List 타입의 기본 구조
- 구조: 연결 리스트의 각 노드에 ziplist가 저장된 형태
- 장점: 순차 접근과 push/pop 연산에 강함 + 메모리 효율성 유지
hashtable
- 사용 위치: 큰 Hash, Set
- 구조: 일반적인 해시 테이블 구조
- 장점: O(1)에 가까운 성능, 대량의 키-값 저장에 적합
skiplist (스킵 리스트)
- 사용 위치: ZSet의 내부 구현 (score 순 정렬용)
- 장점: O(log n) 탐색, 삽입, 삭제 가능 / 범위 검색에 최적화
- 예시: 랭킹 시스템, 리더보드 등에서 효율적 사용
embstr / raw (문자열 저장 구조)
- embstr: 44바이트 이하의 문자열은 heap + redisObject를 함께 한 블록에 저장 → 메모리 할당 횟수 감소
- raw: 긴 문자열은 redisObject와 문자열을 따로 저장 → 큰 데이터에 적합
✅ 구조 전환은 자동
Redis는 구조 선택을 자동으로 처리합니다. 예를 들어, 작은 해시는 ziplist로 시작하지만, 일정 항목 수나 값 크기를 넘으면 hashtable로 자동 전환됩니다. 이는 사용자가 따로 신경 쓰지 않아도 Redis가 적절한 균형을 잡아준다는 것을 의미
✅ 실무 설계시 고려사항
- 데이터 크기와 구조에 따라 성능 차이가 매우 큽니다.
- 저장되는 값이 작고 개수가 적을 경우 압축 구조(ziplist, intset)가 유리
- 대용량 연산이 필요한 경우 기본 구조(hashtable, skiplist)가 더 빠름
- CONFIG GET * 또는 INFO memory 명령어로 Redis 인스턴스 상태를 확인 가능
3️⃣ Redis를 활용한 캐시 전략
✅ [Spring Local Cache 설정 및 @Cacheable 활용 정리]
Spring 에서 @Cacheable을 사용하기 위해서는 몇 가지 사전 설정이 필요하다.
@EnableCaching 추가
Spring에서 @Cacheable과 같은 어노테이션 기반의 캐시 기능을 사용하기 위해서는 먼저 별도의 선언이 필요하고, 설정 클래스에 @EnableCaching 어노테이션을 추가.
@EnableCaching
@Configuration
public class CacheConfig {
// 캐시 관련 설정
}
CacheManager 빈 추가
캐시를 관리하기 위해 CacheManager를 빈으로 등록해야 합니다. Spring은 여러 종류의 CacheManager를 제공합니다
- ConcurrentMapCacheManager: Java의 ConcurrentHashMap 기반의 간단한 메모리 캐시
- SimpleCacheManager: 직접 캐시 인스턴스를 등록하여 사용하는 구조
- EhCacheCacheManager: EhCache 프레임워크 기반
- CompositeCacheManager: 다중 CacheManager 혼합 사용
- CaffeineCacheManager: Java8 기반의 고성능 Caffeine 캐시
- JCacheCacheManager: JSR-107 기반의 캐시 구현체
@Cacheable 기본 사용
@Cacheable 을 메소드에 붙이면, 해당 메소드의 결과값을 캐시에 저장하고 다음 호출 부터는 캐시된 값을 반환함.
@Cacheable("bestSeller")
public Book getBestSeller(String bookNo) {
return bookService.findBook(bookNo);
}
- SpEL 기반 Key 지정
파라미터를 기준으로 캐시 key를 지정하고 싶을 때는 key 속성을 활용한다.
@Cacheable(value = "bestSeller", key = "#bookNo")
public Book getBestSeller(String bookNo, User user, Date dateTime) {
return bookService.findBook(bookNo);
}
- 객체의 하위 속성을 키로 사용하고 싶을 경우
@Cacheable(value = "bestSeller", key = "#book.bookNo")
public Book getBestSeller(Book book, User user, Date dateTime) {
return bookService.findBook(book.getBookNo());
}
- 조건부 캐싱
특정 조건에만 캐시를 적용하고 싶을 경우 condition 속성을 사용할 수 있다.
@Cacheable(value = "bestSeller", key = "#book.bookNo", condition = "#user.type == 'ADMIN'")
public Book getBestSeller(Book book, User user, Date dateTime) {
return bookService.findBook(book.getBookNo());
}
✅ Redis 캐싱
캐싱의 개요
- 자주 조회되는 데이터를 Redis에 저장해 DB/서버 부하 감소
캐시 패턴


1️⃣ Look - Aside ( Lazy Loading )
- 데이터를 먼저 Redis에서 조회
- 없으면 DB에서 조회 후 Redis에 저장

2️⃣ Read - Through
- 캐시에 조회 요청 ➡ 캐시가 내부적으로 DB 조회 및 저장
- 일반적인 캐시 라이브러리가 지원

3️⃣ Write - (Around, Through, Back)
- 애플리케이션이 DB에 곧바로 데이터를 쓰는 방식 ( Around )
- 애플리케이션이 DB와 Redis에 동시에 데이터를 쓰는 방식 ( Through )
- Redis에만 우선 쓰고, 주기적으로 DB에 반영, 성능에 유리하나, 장애 시 데이터 유실 가능성 ( Back )
- ex) 좋아요가 급격히 늘어날때 마다, 상당히 성능에 문제가 생길수 있음



TTL 설정
- 캐시 만료 시간을 설정해 Redis 메모리 사용 최적화
- 적절한 TTL은 최신 데이터 유지 및 메모리 효율성 확보
캐시 무효화 전략
- TTL 기반 만료
- 수동 삭제 ( ex) DB 업데이트시 Redis 키 삭제 )
- LRU, LFU 기반 자동 제거 정책
4️⃣ Redis의 내구성

RDB ( Redis Database )
- 주기적으로 전체 데이터를 스냅샷 형태로 디스크에 저장
- 설정 예시: save 900 1 ➡ 900초마다 최소 1개의 변경사항이 있을 경우 저장
- 장점: 빠른 속도, 저장 파일 용량 적음
- 단점: 백업 주기 사이의 데이터는 유실될 수 있음
AOF ( Append Only File )
- 모든 write 연산을 append log로 기록
- 설정 예시: appendfsync always | everysec | no
- always : 가장 안전하나 느림
- everysec : 성능과 안정성의 균형 ( 권장 )
- no : 빠르지만 신뢰도 낮음
- 장점: 데이터 손실 최소화, 재시작 시 복원 정확도 높음
- 단점: 파일 크기 증가 가능성 ➡ 재작성 필요 (BGREWRITEAOF)
RDB + AOF 병행 전략
- appendonly yes + save 옵션 병행 설정 가능
- Redis는 재시작 시 AOF 우선 적용
- 실시간성과 안정성을 동시에 확보 가능
5️⃣ Redis 고가용성 및 확장성
1️⃣ Replication (Master-Slave 복제)

- Master에 쓰고, Slave는 읽기만 가능 → 읽기 부하 분산
- 복제는 비동기로 수행되어 약간의 지연 발생 가능
2️⃣ Sentinel

- Redis 서버를 감시하고 자동 장애 조치(Failover)를 수행하는 시스템
- 주요 역할:
- 장애 감지
- 자동 failover
- 클라이언트에게 새로운 master 정보 전달
- Sentinel은 자체적으로도 클러스터링됨 (Quorum 필요)
3️⃣ Cluster

- 데이터를 slot(0~16383) 단위로 분산하여 저장 (해시 슬롯 기반 분산)
- 각 노드는 일부 슬롯을 담당
- 수평 확장 가능
- 자동 failover, 일부 키 기반 트랜잭션 지원
6️⃣ 실전 예시: Spring Boot + Redis
✅ 의존성 주입, properties 설정
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.data:spring-data-redis'
implementation 'org.apache.commons:commons-pool2'
}
spring:
cache:
type: redis
redis:
host: localhost
port: 6379
timeout: 2000
✅ 기본 캐시 적용 ( @Cacheable )
@Cacheable(value = "user", key = "#id")
public User getUser(Long id) {
return userRepository.findById(id).orElseThrow();
}
✅ 캐시 무효화 ( @CacheEvict )
@CacheEvict(value = "user", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
✅ 조건부 캐싱
@Cacheable(value = "user", key = "#id", condition = "#id > 100")
public User getUser(Long id) {
return userRepository.findById(id).orElseThrow();
}
✅ 직접 RedisTemplate 사용
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void saveCustomData(String key, Object value) {
redisTemplate.opsForValue().set(key, value, Duration.ofMinutes(5));
}
public Object getCustomData(String key) {
return redisTemplate.opsForValue().get(key);
}
✅ Redis Hash 구조 예시
HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
hashOps.put("user:1001", "name", "John");
hashOps.put("user:1001", "email", "john@example.com");
String name = hashOps.get("user:1001", "name");
✅ TTL 설정
redisTemplate.opsForValue().set("temp-key", "value", 30, TimeUnit.SECONDS);
7️⃣ Redis 사용시 주의사항
- 너무 많은 키 저장 → 메모리 부족, eviction 정책 필요
- 큰 value 저장 → Redis는 작은 단위 데이터 캐시에 적합
- persistence 기능 사용 시 디스크 I/O 및 snapshot 주기 조정 필요
- 데이터 일관성 고려:
- 캐시 무효화가 DB 변경과 비동기적으로 동작 가능성 존재
- 변경 이벤트 기반 캐시 삭제 전략 고려 (예: Spring Event + CacheEvict)
- 클러스터 환경에서 multi-key 연산은 동작하지 않을 수 있음 (해시 태그 활용 필요)
8️⃣ Redis 다양한 데이터 타입 활용
Redis는 단순한 Key-Value 저장소 그 이상으로, 다양한 자료구조를 지원하며 상황에 따라 적절한 구조를 선택하여 활용



✅ String
- 상황: 로그인 시 마지막 로그인 시간 기록
- 활용 예시
// 마지막 로그인 시간 저장
redisTemplate.opsForValue().set("user:last_login:1001", LocalDateTime.now().toString());
// 조회
String lastLogin = redisTemplate.opsForValue().get("user:last_login:1001");
✅ List
- 상황: 최근 검색어 10개 저장 (FIFO)
- 활용 예시
String key = "user:search:history:1001";
redisTemplate.opsForList().leftPush(key, "Redis 강의자료");
redisTemplate.opsForList().trim(key, 0, 9); // 10개 유지
List<String> recentSearches = redisTemplate.opsForList().range(key, 0, -1);
✅ Set
- 상황: 유저가 좋아요를 누른 게시물 ID 집합
- 활용 예시
String key = "user:likes:1001";
redisTemplate.opsForSet().add(key, "post:200", "post:201");
boolean hasLiked = redisTemplate.opsForSet().isMember(key, "post:200");
✅ Sorted Set ( = Zset )
- 상황: 게시글 조회수 기반 인기 랭킹 (score 기준 정렬)
- 활용 예시
String key = "board:ranking";
redisTemplate.opsForZSet().incrementScore(key, "post:200", 1);
Set<String> top5 = redisTemplate.opsForZSet().reverseRange(key, 0, 4);
✅ Hash
- 상황: 유저 프로필 속성 저장 및 조회 (복합 정보)
- 활용 예시
String key = "user:profile:1001";
HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
hashOps.put(key, "nickname", "devgyu");
hashOps.put(key, "email", "gyu@example.com");
String nickname = hashOps.get(key, "nickname");
✅ HyperLog
- 상황: 하루 방문자 수 추정 (대용량 유니크 값)
- 활용 예시
String key = "visit:2025-05-19";
redisTemplate.opsForHyperLogLog().add(key, "user1001");
Long estimate = redisTemplate.opsForHyperLogLog().size(key);
✅ Bitmap
- 상황: 출석 체크 기능 (0~N번 유저의 출석 여부를 비트로 표현)
- 활용 예시
String key = "attendance:2025-05-19";
redisTemplate.opsForValue().setBit(key, 1001, true); // 출석 체크
boolean attended = redisTemplate.opsForValue().getBit(key, 1001);
9️⃣ Redis를 활용한 로그아웃 및 블랙리스트 처리
Redis는 JWT 기반 인증 시스템에서 토큰 무효화(블랙리스트) 및 로그아웃 처리에 적합한 메커니즘을 제공
'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글
| TIL 61 [ Spring 실전 프로젝트 ( Day 4 ) - Cache 💡, 오류 개선, Redis 기본세팅 + PostMan 웹 공유법 ] (0) | 2025.05.21 |
|---|---|
| TIL 60 [ Spring 실전 프로젝트 ( Day 3 ) - 기본 API 구현/병합/전체 테스트, Cache + Redis 세션 ] (0) | 2025.05.20 |
| TIL 59 [ Spring 실전 프로젝트 ( Day 2 ) - 도서 CRUD 구현 + 트러블 슈팅 ] (0) | 2025.05.19 |
| TIL 58 [ Spring 실전 프로젝트 ( Day 1 ) - 설계 ( ERD, API, 와이어프레임 ) ] (0) | 2025.05.16 |
| TIL 57 [ AWS( EC2, ELB, RDS ) ] (0) | 2025.05.15 |