Redis Session 이슈
작성일: 2025.06.20
서비스 운영 중 Redis 메모리 사용량이 비정상적으로 증가하는 이슈, 그리고 반대로 로그아웃을 해도 세션이 만료되지 않는 '좀비 세션' 현상을 겪었습니다. Spring Boot와 Redis를 연동하여 사용할 때 세션의 생성(Creation)과 소멸(Invalidation) 라이프사이클을 명확히 제어하지 못해 발생한 문제였습니다.
이슈 1. 비로그인 유저의 세션 무한 생성 (Memory Leak)
1-1) 문제 상황
로그인을 하지 않은 사용자(Guest)가 단순히 게시글 목록을 조회하는 API를 호출할 때마다, Redis에 새로운 spring:session:sessions:* 키가 계속해서 쌓이는 현상을 발견했습니다.
1-2) 원인 분석
Controller 메서드에서 HttpSession을 파라미터로 직접 주입받는 방식이 문제였습니다.
// 문제의 코드
@GetMapping("/wargames")
public ResponseEntity<?> getList(HttpSession session) {
// Spring은 HttpSession 파라미터를 보면, 세션이 없을 경우
// 자동으로 생성(create=true)하여 주입합니다.
...
}
Spring MVC는 HttpSession 파라미터가 선언되어 있으면 내부적으로 request.getSession(true)를 호출합니다. 이로 인해 세션이 필요 없는 단순 조회 요청에도 강제로 빈 세션이 생성되어 Redis 메모리를 낭비하고 있었습니다.
1-3) 해결: 세션 조회 옵션 제어
세션이 반드시 필요한 경우가 아니라면, HttpServletRequest를 통해 **"있으면 가져오고, 없으면 null"**을 반환하도록 로직을 변경했습니다.
// 개선된 코드
@GetMapping("/wargames")
public ResponseEntity<?> getList(HttpServletRequest request) {
// create: false 옵션을 주어 불필요한 생성을 방지
HttpSession session = request.getSession(false);
Long userId = (session != null) ? (Long) session.getAttribute("userId") : null;
...
}
이 조치로 비로그인 트래픽이 아무리 늘어나도 불필요한 Redis 키가 생성되지 않도록 막았습니다.
이슈 2. 로그아웃 시 세션이 죽지 않음 (Invalidation Failure)
2-1) 문제 상황
세션 생성 문제를 해결한 후, 이번에는 로그아웃 API를 호출해도 실제로 세션이 삭제되지 않는 현상이 발생했습니다. 클라이언트는 로그아웃 성공 응답을 받았지만, 해당 브라우저에서 요청을 보내면 여전히 로그인된 상태로 동작했습니다.
2-2) 원인 분석
로그아웃 로직에서 session.invalidate()를 호출하고 있었으나, 요청이 들어올 때 세션이 제대로 매핑되지 않은 상태에서 무효화를 시도했기 때문입니다.
가능성은 크게 세 가지였습니다.
- CORS/SameSite 이슈: 클라이언트(프론트엔드)가 로그아웃 요청 시 세션 쿠키(JSESSIONID)를 제대로 실어 보내지 못함.
- 세션 ID 불일치: 서버가 인식하는 세션 ID와 클라이언트가 가진 ID가 다름.
- 이미 만료됨: 타임아웃 등으로 인해 이미
null인 상태에서 invalidate 호출.
2-3) 해결: 로그 추적 및 명시적 검증
정확한 원인 파악을 위해 로그아웃 시점에 현재 요청의 세션 ID를 로그로 찍어 추적했습니다.
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
log.info("Logout requested. Invalidating session ID: {}", session.getId());
session.invalidate(); // 명시적 무효화
} else {
log.warn("Logout failed: No active session found.");
}
return ResponseEntity.ok().build();
}
로그 확인 결과, 일부 CORS 설정 미흡으로 인해 브라우저가 쿠키를 전송하지 않아 session이 null로 잡히는 상황을 발견했습니다. 이를 통해 WebConfig의 CORS 설정(AllowCredentials, AllowedOriginPatterns)을 수정하여 정상적으로 쿠키가 전달되도록 조치했습니다.
📝 결론 및 배운 점
Spring Security나 MVC가 제공하는 HttpSession 관련 기능들은 매우 편리하지만, 그 내부 동작(언제 생기고 언제 죽는지!)을 정확히 이해하지 못하면 리소스 낭비(이슈 1)나 보안 이슈(이슈 2)으로 이어질 수 있음을 깨달았습니다.
- 생성 제어: 조회용 API에서는
request.getSession(false)를 생활화하자. - 소멸 확인: 로그아웃 로직은 단순히
invalidate()만 믿지 말고, 요청에 쿠키가 제대로 실려왔는지 확인해야 한다.