💡 오늘 학습 키워드
- Session Clustering
- Leaderboard
- Cache
🎯 학습 내용 정리
웹 서비스를 운영하다 보면 단순히 데이터를 저장하는 것보다 빠르게 조회하고, 여러 서버가 동일한 데이터를 공유하며, 반복되는 조회를 줄이는 것이 더 중요한 순간이 많다.
Redis는 이러한 문제를 해결하기 위해 가장 많이 사용하는 인메모리 데이터베이스이다.
1. Session
HTTP는 Stateless Protocol이다.
즉, 하나의 요청이 끝나면 서버는 이전 요청에 대한 상태를 기억하지 않는다.
예를 들어 사용자가 로그인했다고 가정해 보자.
로그인 요청
↓
로그인 성공
↓
상품 조회 요청
상품 조회 요청만 봤을 때 서버는 다음과 같은 사실을 알 수 없다.
- 누가 요청했는지
- 로그인한 사용자인지
- 이전 요청이 무엇이었는지
따라서 클라이언트는 매 요청마다 자신을 식별할 수 있는 정보를 함께 보내야 한다.
사용자를 식별하기 위해 사용하는 대표적인 방법이 Cookie와 Session이다.
로그인 과정은 다음과 같이 동작한다.
Client
↓ Login
Server
↓
세션 생성
↓
Session ID 생성
↓
Cookie(JSESSIONID) 발급
↓
Client 저장
이후 요청에서는 브라우저가 자동으로 Cookie를 함께 보낸다.
Cookie
JSESSIONID=ABC123
서버는 이 값을 이용해 기존 세션을 찾아 사용자를 식별한다.
Spring Boot에서는 내장 Tomcat이 세션을 관리한다.
컨트롤러에서는 HttpSession을 그대로 사용할 수 있다.
@GetMapping("/set")
public String set(
@RequestParam String q,
HttpSession session
) {
session.setAttribute("q", q);
return "saved";
}
조회는 다음과 같다.
@GetMapping("/get")
public String get(HttpSession session) {
return (String) session.getAttribute("q");
}
브라우저에는 JSESSIONID 쿠키가 생성되고, 서버는 해당 ID를 이용하여 세션 데이터를 관리한다.
서비스 사용자가 증가하면 서버를 여러 대 운영하게 된다.
이를 Scale-Out이라고 한다.
Load Balancer
/ | \
Server A Server B Server C
문제는 세션이 각 서버 내부에 저장된다는 점이다.
예를 들어
- 로그인은 Server A
- 다음 요청은 Server B
로 전달되면,
Server B에는 해당 세션이 존재하지 않는다.
결국 로그인 상태가 유지되지 않는다.
가장 단순한 해결 방법은 Sticky Session이다.
로드 밸런서가 사용자를 특정 서버에 고정한다.
User A
↓
Server A
(항상 Server A)
장점은 구현이 간단하다는 것이다.
하지만 다음과 같은 문제가 있다.
- 특정 서버에 요청이 몰릴 수 있다.
- 서버가 다운되면 세션도 함께 사라진다.
- Scale-Out의 장점을 제대로 활용하지 못한다.
따라서 보다 일반적으로 사용하는 방식은 Session Clustering이다.
세션을 서버 내부가 아니라 외부 저장소에 저장한다.
Load Balancer
/ | \
Server A Server B Server C
\ | /
Redis
이제 어떤 서버가 요청을 받아도 동일한 세션을 조회할 수 있다.
장점은 다음과 같다.
- 서버 추가가 자유롭다.
- 서버 제거가 자유롭다.
- 세션이 유지된다.
- Load Balancing이 가능하다.
단점은 Redis와 통신해야 하므로 약간의 네트워크 비용이 발생한다.
하지만 Redis는 메모리 기반이기 때문에 이러한 비용이 매우 작다.
Spring Boot에서는 매우 쉽게 Session Clustering을 적용할 수 있다.
다음과 같이 의존성을 추가한다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.session:spring-session-data-redis'
이것만 추가해도 Tomcat Session 대신 Redis Session을 사용하게 된다.
기존
JSESSIONID
대신
SESSION
쿠키가 생성된다.
Redis를 확인하면 세션 정보가 저장되어 있는 것을 확인할 수 있다.
기본적으로 Spring Session은 Java 직렬화를 사용한다.
JSON 형태로 저장하고 싶다면 Serializer를 등록한다.
@Bean
public RedisSerializer<Object>
springSessionDefaultRedisSerializer() {
return RedisSerializer.json();
}
다만 Spring Security의 SecurityContext처럼 기본 생성자가 없는 객체는 JSON 직렬화 과정에서 문제가 발생할 수 있다.
실무에서는 Jackson 설정이나 별도의 Serializer를 추가하여 해결하는 경우가 많다.
2. Leaderboard
Redis의 가장 강력한 자료구조 중 하나가 Sorted Set(ZSet) 이다.
각 데이터는
Member + Score
형태로 저장된다.
예를 들어
상품 A → 100
상품 B → 70
상품 C → 30
처럼 저장된다.
RDB로 구현하면?
인기 상품을 구하려면 보통 다음과 같은 SQL이 필요하다.
SELECT item_id,
SUM(count)
FROM orders
GROUP BY item_id
ORDER BY SUM(count) DESC
LIMIT 10;
주문 데이터가 많아질수록
- Join
- Group By
- Sum
- Order By
연산 비용이 증가한다.
Redis에서는 구매할 때마다 점수만 증가시키면 된다.
rankOps.incrementScore(
"soldRanks",
ItemDto.fromEntity(item),
1
);
내부적으로는 다음 명령이 수행된다.
ZINCRBY soldRanks 1 item
조회도 간단하다.
rankOps.reverseRange(
"soldRanks",
0,
9
);
이는
ZREVRANGE soldRanks 0 9
과 동일하다.
Redis는 점수를 기준으로 자동 정렬하기 때문에 별도의 SQL이나 정렬 로직이 필요하지 않다.
3. Cache
캐시는 자주 사용하는 데이터를 빠르게 가져오기 위한 저장소이다.
예를 들어 상품 상세 조회가 매우 많다고 가정해 보자.
Client
↓
Database
↓
응답
매번 DB를 조회하면 부하가 커진다.
캐시를 추가하면
Client
↓
Redis Cache
↓
(없으면)
Database
순서로 조회한다.
이를 통해 응답 속도와 DB 부하를 크게 줄일 수 있다.
캐싱에서는 두 가지 용어를 자주 사용한다.
1) Cache Hit
원하는 데이터가 캐시에 존재한다.
Client
↓
Redis
↓
응답
2) Cache Miss
캐시에 데이터가 없다.
Redis
↓
Database
↓
Redis 저장
↓
응답
대표적인 캐싱 전략은 다음과 같다.
1) Cache Aside (Lazy Loading)
가장 많이 사용하는 방식이다.
Cache 조회
↓
있다 → 반환
or
없다
↓
DB 조회
↓
Cache 저장
장점
- 필요한 데이터만 저장한다.
- 메모리 사용량이 적다.
단점
- 최초 조회는 느리다.
- 캐시와 DB가 일시적으로 불일치할 수 있다.
2) Write Through
쓰기 작업을 할 때
- Cache
- DB
둘 다 즉시 저장한다.
항상 최신 데이터를 유지할 수 있지만 쓰기 비용이 증가한다.
3) Write Behind
먼저 Cache에 저장한다.
이후 일정 주기로 DB에 반영한다.
쓰기 성능은 매우 좋지만 장애 발생 시 데이터 유실 가능성이 존재한다.
Spring에서는 캐시 기능을 매우 간단하게 적용할 수 있다.
먼저 캐시 기능을 활성화한다.
@Configuration
@EnableCaching
public class CacheConfig {
}
Redis를 사용할 경우 CacheManager를 등록한다.
@Bean
public RedisCacheManager cacheManager(
RedisConnectionFactory factory
) {
RedisCacheConfiguration configuration =
RedisCacheConfiguration
.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(10));
return RedisCacheManager
.builder(factory)
.cacheDefaults(configuration)
.build();
}
여기서 TTL(Time To Live)을 설정하면 캐시가 일정 시간이 지나면 자동으로 삭제된다.
@Cacheable
조회 메서드에 적용한다.
@Cacheable(
cacheNames = "itemCache",
key = "#id"
)
public ItemDto readOne(Long id) {
...
}
동작 방식은 다음과 같다.
Cache 조회
↓
Hit
↓
캐시 반환
↓
Miss
↓
메서드 실행
↓
캐시 저장
동일한 요청은 DB를 조회하지 않는다.
@CachePut
항상 메서드를 실행한 뒤 캐시를 갱신한다.
@CachePut(
cacheNames = "itemCache",
key = "#result.id"
)
생성(Create)이나 수정(Update) 작업에서 많이 사용한다.
@CacheEvict
캐시를 제거한다.
@CacheEvict(
cacheNames = "itemAllCache",
allEntries = true
)
예를 들어 상품 정보가 변경되면
상품 목록 캐시
는 더 이상 유효하지 않다.
따라서 전체 삭제 후 다시 생성하도록 만드는 것이 일반적이다.
검색은 같은 요청이 반복되는 경우가 많다.
예를 들어
검색어
페이지 번호
페이지 크기
를 Key로 만들 수 있다.
@Cacheable(
cacheNames = "itemSearchCache",
key = "{#query, #pageable.pageNumber, #pageable.pageSize}"
)
예를 들어
노트북
0페이지
20개
검색은 하나의 캐시를 사용한다.
같은 요청이 들어오면 DB를 조회하지 않는다.
Redis는 단순히 캐시 서버가 아니다.
다양한 자료구조를 활용하여 여러 기능을 쉽게 구현할 수 있다.
| 기능 | Redis 자료구조 |
| 로그인 세션 | Hash |
| Session Clustering | Hash |
| Refresh Token | String |
| 인증번호 | String + TTL |
| 조회수 | String(INCR) |
| 장바구니 | Hash |
| 좋아요 | Set |
| 인기 상품 | Sorted Set |
| 실시간 랭킹 | Sorted Set |
| 검색 결과 캐시 | String(JSON) |
Redis는 단순히 빠른 데이터 저장소가 아니라 확장성과 성능을 동시에 확보하기 위한 핵심 인프라이다.
Session Clustering을 통해 여러 서버가 동일한 세션을 공유할 수 있고, Sorted Set을 활용하면 실시간 랭킹을 효율적으로 구현할 수 있다. 또한 Spring Cache를 적용하면 반복적인 데이터베이스 조회를 줄여 응답 속도를 높이고 시스템 부하를 크게 감소시킬 수 있다.
Spring Boot에서는 spring-session-data-redis, RedisTemplate, Spring Cache를 통해 이러한 기능들을 비교적 간단하게 적용할 수 있으며, 실제 운영 환경에서도 가장 널리 사용되는 Redis 활용 방식이다.
📚 한줄 정리
Redis는 Session Clustering, 실시간 랭킹(Leaderboard), 또는 Cache를 통해 분산 환경에서 세션을 공유하고, 빠른 데이터 조회와 효율적인 캐싱을 구현할 수 있는 핵심 인메모리 데이터베이스이다.
'Bootcamp > Fundamentals' 카테고리의 다른 글
| 대규모 시스템 설계와 RabbitMQ (0) | 2026.07.27 |
|---|---|
| Redis (0) | 2026.07.23 |
| CI/CD와 GitLab CI + AWS ECS 자동배포 (1) | 2026.07.22 |
| Docker와 Docker Compose (0) | 2026.07.21 |
| Event Driven Architecture와 Kubernetes (0) | 2026.07.05 |