📦 Redis : 개념부터 캐싱 전략까지

2025. 5. 20. 20:13·[내일배움캠프-Sparta]/Spring 6기
반응형

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는 내부적으로 이벤트 루프 구조를 사용해 클라이언트 요청을 처리합니다. 이벤트 루프는 다음과 같은 흐름으로 동작합니다.

  1. 클라이언트로부터 명령 수신 준비
  2. I/O 멀티 플렉싱(select, epoll 등)으로 이벤트 감시
  3. 이벤트가 발생한 소켓에 대해 처리 함수 실행
  4. 명령 처리 후 응답 전송
  5. 다시 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
'[내일배움캠프-Sparta]/Spring 6기' 카테고리의 다른 글
  • TIL 61 [ Spring 실전 프로젝트 ( Day 4 ) - Cache 💡, 오류 개선, Redis 기본세팅 + PostMan 웹 공유법 ]
  • TIL 60 [ Spring 실전 프로젝트 ( Day 3 ) - 기본 API 구현/병합/전체 테스트, Cache + Redis 세션 ]
  • TIL 59 [ Spring 실전 프로젝트 ( Day 2 ) - 도서 CRUD 구현 + 트러블 슈팅 ]
  • TIL 58 [ Spring 실전 프로젝트 ( Day 1 ) - 설계 ( ERD, API, 와이어프레임 ) ]
dimenshun
dimenshun
한 소년의 개발 일기
    반응형
  • dimenshun
    Dev Life Notes
    dimenshun
  • 전체
    오늘
    어제
    • 분류 전체보기 (268)
      • CS (23)
        • 자료구조 (0)
        • 알고리즘 (0)
        • 컴퓨터 구조 (8)
        • 네트워크 (6)
        • 운영체제 (3)
        • DB ( + SQLD ) (5)
        • SW공학 (1)
      • 프로그래밍 (3)
        • Java (0)
        • Spring (0)
        • HTML,CSS (3)
        • JavaScript (0)
      • 개발 툴 (7)
        • Git(버전관리) (1)
        • Docker (3)
        • AWS (2)
        • JSP (1)
      • 코딩테스트(Algorithm) (125)
        • 백준 (6)
        • 프로그래머스 (119)
      • [내일배움캠프-Sparta] (110)
        • Spring 6기 (106)
        • KPT 회고 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    세션
    Python
    db
    내일배움캠프
    개발자
    알고리즘
    spring
    web
    컴퓨터구조
    배포
    코딩테스트
    KPT
    CPU
    docker
    Java
    It
    cs
    Til
    SQLD
    메모리
    네트워크
    OS
    운영체제
    백엔드
    트랜잭션
    AWS
    network
    Testcode
    웹
    SQL
  • hELLO· Designed By정상우.v4.10.3
dimenshun
📦 Redis : 개념부터 캐싱 전략까지
상단으로

티스토리툴바