본문 바로가기
개발

Spring Boot(Kotlin) — 7편. 왜 Redis인가? Spring Boot 애플리케이션에서 Redis를 사용하는 이유와 설정

by 새싹 아빠 2025. 11. 25.

📚 Spring Boot(Kotlin) 서버 기본 셋팅 — 시리즈 안내

  1. 왜 멀티 모듈 구조인가? (아키텍처 철학 & 전체 설계 편)
  2. API Response 포맷 설계
  3. 글로벌 예외 처리(GlobalExceptionHandler)
  4. Swagger(OpenAPI) 설정
  5. Security(JWT) 기본 골격
  6. JWT TokenProvider
  7. Redis 설정 ← 현재 글
  8. Validation 설정
  9. Logging + MDC(traceId)
  10. application.yml 프로필 분리 (local/dev/prod)
  11. 멀티모듈 + JPA 기본 구조 정리
  12. 완성된 프로젝트 템플릿 git 공유

📌 Redis는 왜 필요할까?

API 서버를 만들면서 처음에는 “DB 하나 잘 붙여서 쓰면 되지”라고 생각하기 쉽습니다.
하지만 트래픽이 조금만 늘어나도 DB 커넥션 풀쿼리 처리 속도가 바로 한계로 다가옵니다.

✔ MySQL / RDB는 커넥션 풀 사이즈가 정해져 있음 (예: 151개)
✔ API 하나가 호출될 때마다 커넥션을 1개씩 빌려 사용했다가 반환
✔ 동시에 많은 요청이 몰리면 커넥션 풀이 꽉 차고, 나머지 요청은 대기
✔ 대기 시간이 타임아웃을 넘기면 에러 발생 → 서비스 장애로 이어질 수 있음

특히 조회 API가 많아지면, “단순히 목록 몇 개 가져오는 요청 때문에” DB 커넥션이 소모되고
버튼을 빠르게 여러 번 눌러도 순식간에 풀 사이즈를 채울 수 있습니다.

그래서 저는 모든 것을 DB 조회로 해결하기보다는, 읽기 부하를 Redis 캐시로 빼는 전략을 선택했습니다.

✔ Redis는 메모리 기반의 Key-Value 저장소
✔ 조회 속도가 매우 빠르며, 단순 조회 트래픽을 감당하기에 적합
✔ 자주 조회되지만 자주 바뀌지 않는 데이터 → Redis에 캐싱
✔ DB는 “정말 DB가 해야 할 일”에만 집중시킬 수 있음

이 시리즈에서는 이미 이전 글에서 RefreshToken 저장소를 Redis로 구현했습니다.
이번 글에서는 그 기반이 되는 Redis 설정 (RedisConfig)과 함께, Redis를 어떤 용도로 쓰면 좋은지 정리합니다.

📌 Redis를 어디에 쓸 수 있을까?

대표적으로 다음과 같은 용도로 많이 사용합니다.

1) 캐시(Cache)
- DB에서 자주 조회되는 데이터를 Redis에 저장
- 예: 메인 화면용 요약 정보, 공통 코드, 랭킹 목록, 설정 값 등
- 캐시 미스일 때만 DB 조회 → DB 부하 감소, 응답 속도 향상

2) 세션 대체
- 서버 메모리가 아닌 Redis에 세션/로그인 상태를 보관
- 여러 대의 서버가 같은 Redis를 바라보므로, 수평 확장(scale-out)이 쉬워짐
- JWT와 함께 사용하면, “추가 정보”나 “블랙리스트” 용도로도 활용 가능

3) 토큰/인증 정보 저장
- RefreshToken, 이메일 인증 코드, 비밀번호 초기화 토큰 등
- TTL(만료 시간)을 설정해 자동으로 정리

4) 분산 락(Distributed Lock)
- 여러 서버에서 동시에 같은 자원을 수정할 때, 동시성 문제를 제어하는 용도
- 예: 재고 감소, 중복 결제 방지, 순차 처리 등
- Redis의 setnx, Redisson 등을 사용해 “한 번에 한 명만 처리” 보장

5) 카운터 / 랭킹 / 실시간 집계
- 좋아요 수, 조회 수, 접속자 수, 실시간 순위 등
- 숫자 증가/감소 연산이 매우 빠름

정리하면, Redis는 “조금 더 빠르고, 조금 더 가볍게 처리해도 되는 데이터들”을 맡기는 저장소입니다.
RDB는 여전히 중요하지만, 모든 트래픽을 DB로만 처리하는 설계는 한계가 빠르게 온다는 점에서 Redis 도입을 고려했습니다.

📌 Redis 설정은 어디 모듈에 둘까?

이 프로젝트는 멀티 모듈 구조를 사용하고 있습니다.

✔ API 모듈 : 컨트롤러, 요청/응답 모델, 시큐리티, 예외 처리, Swagger 등
✔ Application 모듈 : 비즈니스 로직, 도메인 서비스, 인프라 접근 (Redis, DB 등)

Redis는 도메인/비즈니스 로직에서 사용하는 인프라에 가깝습니다.
RefreshTokenRepository, 캐시 레포지토리 등은 모두 애플리케이션 계층에서 사용되기 때문에,
저는 RedisConfig를 Application 모듈에 두는 구조를 선택했습니다.

즉,

✔ API 모듈은 “HTTP 요청과 응답”에만 집중
✔ Application 모듈은 “데이터를 어디에 어떻게 저장/조회할지”를 책임
→ Redis 설정도 Application 모듈에 포함

📌 필요한 의존성

Gradle 기준으로 Redis를 사용하기 위한 기본 의존성은 다음과 같습니다.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-redis")

    // 선택: 코틀린 + JSON 직렬화용
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
}
✔ spring-boot-starter-data-redis: RedisConnectionFactory, RedisTemplate, StringRedisTemplate 등 제공
✔ Spring Boot 3.x 기준 기본 클라이언트는 Lettuce (Netty 기반, 비동기 클라이언트)
✔ 별도 설정이 없다면 LettuceConnectionFactory를 사용하게 됨

📌 application.yml 설정 예시

Redis 접속 정보는 코드에 하드코딩하지 않고, 설정 파일로 분리합니다.

redis:
  host: localhost
  port: 6379

이후 운영 환경에서는 dev.yml, prod.yml 등에 각각 다른 host/port(또는 password, sentinel, cluster 설정 등)를 분리해서 관리하면 됩니다.

📌 RedisConfig 전체 코드 (Application 모듈)

package com.example.application.config

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.StringRedisTemplate

@Configuration
class RedisConfig(
    @Value("\${redis.host}") private val host: String,
    @Value("\${redis.port}") private val port: Int
) {

    @Bean
    fun redisConnectionFactory(): RedisConnectionFactory {
        return LettuceConnectionFactory(host, port)
    }

    @Bean
    fun stringRedisTemplate(connectionFactory: RedisConnectionFactory): StringRedisTemplate {
        return StringRedisTemplate(connectionFactory)
    }
}

📌 RedisConfig 코드 설명

1️⃣ @Configuration + @Bean

- @Configuration: 이 클래스가 Spring 설정 클래스라는 의미
- 내부의 @Bean 메서드들이 Spring 컨테이너에 등록됨
- 다른 서비스/리포지토리에서 StringRedisTemplate을 바로 주입받아 사용 가능

2️⃣ @Value("\${redis.host}") / @Value("\${redis.port}")

application.yml에 정의한 값을 주입받습니다.

- redis.host, redis.port를 환경별로 다르게 설정 가능
- 로컬에서는 localhost, 운영에서는 VPC 내부 Redis 주소 등을 지정
- CI/CD 시크릿, 환경변수 등과도 쉽게 연동

3️⃣ redisConnectionFactory()

fun redisConnectionFactory(): RedisConnectionFactory {
    return LettuceConnectionFactory(host, port)
}
- LettuceConnectionFactory는 Lettuce 클라이언트를 사용해 Redis에 연결하는 역할
- host, port 정보를 기반으로 Redis 서버와 커넥션을 관리
- Spring Data Redis가 내부적으로 이 ConnectionFactory를 사용해 Redis 명령을 수행

4️⃣ StringRedisTemplate

fun stringRedisTemplate(connectionFactory: RedisConnectionFactory): StringRedisTemplate {
    return StringRedisTemplate(connectionFactory)
}

StringRedisTemplateKey와 Value가 모두 String인 경우에 특화된 RedisTemplate입니다.

- 대부분의 토큰, 캐시 키, 카운터 등은 String - String 구조로도 충분
- 예: refresh:123 → "리프레시 토큰 값"
- 예: config:main-banner → "JSON 문자열"
- 복잡한 구조는 JSON으로 직렬화해 String으로 넣고, 꺼낼 때 역직렬화하는 방식 사용

📌 StringRedisTemplate 간단 사용 예시

실제 코드에서 StringRedisTemplate을 사용하면 대략 이런 느낌으로 사용할 수 있습니다.

package com.example.application.sample

import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit

@Service
class SampleCacheService(
    private val redis: StringRedisTemplate
) {

    fun putSampleData(key: String, value: String, ttlSeconds: Long) {
        redis.opsForValue().set(key, value, ttlSeconds, TimeUnit.SECONDS)
    }

    fun getSampleData(key: String): String? {
        return redis.opsForValue().get(key)
    }

    fun removeSampleData(key: String) {
        redis.delete(key)
    }
}

이 구조를 그대로 확장해서, RefreshTokenRepository, 캐시 레포지토리, 락 레포지토리 등을 설계할 수 있습니다.

📌 DB와 Redis의 역할 분리 관점에서 다시 보기

앞에서 이야기했던 DB 커넥션 풀 이야기를 Redis 관점에서 다시 정리해 보면:

✔ RDB (MySQL 등)는
- 트랜잭션이 중요한 데이터 (주문, 결제, 계정 정보 등)
- 강력한 일관성이 필요한 경우
- 정규화된 스키마, 조인, 복잡한 쿼리에 강함

✔ Redis는
- 빠른 읽기/쓰기, 단순 Key-Value 구조
- “잠깐 저장해도 되는 데이터”, “다시 만들 수 있는 데이터”에 적합
- 캐시, 토큰, 세션, 카운터, 락 등에 최적화

그래서 설계 기준을 이렇게 잡았습니다.

✔ DB는 “진짜 한 번만 쓰고 싶은 핵심 데이터”
✔ Redis는 “읽기 부하를 줄이거나 빠르게 처리하고 싶은 데이터”

이런 식으로 역할을 분리해 두면, 조회 트래픽이 늘어나도 DB가 먼저 죽지 않고 버틸 수 있는 구조를 만들 수 있습니다.
동시에, RefreshToken/세션/락 처리 등도 Redis 위에서 유연하게 구현할 수 있게 됩니다.

📌 결론

📌 Redis는 “빠르고 가벼운 데이터”를 담당하는 인메모리 저장소로, RDB의 부담을 줄여준다.
📌 캐시, 토큰 저장소, 세션 대체, 분산 락, 카운터 등 다양한 용도로 활용 가능하다.
📌 RedisConfig는 Application 모듈에 두어 비즈니스 로직에서 Redis를 자연스럽게 사용할 수 있게 했다.
📌 StringRedisTemplateRedisConnectionFactory만 설정해두면, 이후부터는 레포지토리/서비스에서 바로 Redis를 인프라로 활용할 수 있다.

결국 이 설정의 목표는 단순합니다.
“DB는 DB답게, Redis는 Redis답게, 각자 잘하는 일을 하게 하자.”
그렇게 해야 서비스가 커졌을 때도 구조를 크게 바꾸지 않고도 확장할 수 있다고 생각합니다.

📌 다음 편 예고

다음 편 — Validation 설정
컨트롤러 진입 전/후로 입력 값을 어떻게 검증할지, Bean Validation(@Valid)을 어디까지 활용할지,
그리고 GlobalExceptionHandler와 어떻게 자연스럽게 연결되는지를 다뤄볼 예정입니다.

 

https://jaemoi8.tistory.com/42

 

Spring Boot(Kotlin) — 8편. 멀티 모듈 환경에서 Spring Validation을 어떻게 적용할 것인가

이 글은 Spring Boot 멀티 모듈 기반 서버 개발 시, Validation 설정을 어떻게 구성해야 하는지 정리하는 글입니다.Validation은 DTO를 만들기 시작할 때부터 사용되는 기능이며, 클라이언트가 보낸 요청

jaemoi8.tistory.com