🖥️ 컴퓨터 공부/Redis

Spring Data Redis 공식문서로 내부 동작 이해하기

le2donguk 2026. 3. 21. 13:52

Spring Boot에서 Redis를 사용할 때 우리는 보통 아래처럼 아주 간단하게 사용한다.

stringRedisTemplate.opsForValue().set("user", "donguk");

 

하지만 이 한 줄 뒤에서는 생각보다 많은 계층과 추상화가 동작하고 있다.

이 글에서는 Spring Data Redis 공식 문서 기반으로

  • RedisTemplate 내부 동작
  • Lettuce의 역할
  • RedisConnection 추상화
  • StringRedisTemplate 차이
  • HashMapper 동작
  • Redis 트랜잭션 구조
  • Spring Boot Auto Configuration

까지 전체 흐름을 깊게 정리해 본다.

 

참고 : 

https://docs.spring.io/spring-data/redis/reference/

 

Spring Data Redis :: Spring Data Redis

Costin Leau, Jennifer Hickey, Christoph Strobl, Thomas Darimont, Mark Paluch, Jay Bryant Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that

docs.spring.io

 


1. Lettuce란 무엇인가

Lettuce는 Redis에 접속하기 위한 Java Redis Client 라이브러리이다.

 

Spring Boot  → RedisTemplate → Lettuce → Redis 서버

 

더 자세히는 

Application 
  ↓ 
RedisTemplate 
  ↓ 
RedisConnection
  ↓ 
Lettuce 
  ↓ 
Redis Server

이렇게 중간에서 요청을 Redis로 보내주는 역할을 한다 

 


내부 호출 흐름

예를 들어

redisTemplate.opsForValue().set("user","donguk");

 

이 코드가 실행되면 내부에서는 다음과 같은 일이 발생한다.

  1. RedisTemplate이 Serializer(직렬화)를 이용하여 key/value를 byte []로 변환한다.
  2. RedisConnectionFactory에게 connection을 요청한다.
  3. LettuceConnection이 생성된다.
  4. Lettuce가 Redis Protocol을 만들어 TCP로 Redis 서버에 요청을 보낸다.

RedisTemplate은 추상화 계층이고 실제 통신은 Lettuce가 한다.


2. 왜 Lettuce를 사용하는가

과거에는 Java Redis Client 라이브러리  중 Jedis가 많이 사용되었다.

하지만 Jedis는 다음과 같은 문제점이 있었다.

  • Blocking 기반
  • Thread-safe 하지 않음
  • Thread마다 connection 필요
  • Connection Pool 필수

 

Connection 공유 가능 

Lettuce를 사용하는 주된 이유 중 하나는 바로 connection 공유 가능이다

Redis는 SingleThread다. 그래서 매 요청 Thread 마다  Connection을 요청하면 성능이 엄청 떨어진다 

그러나 Lettuce는 single connection 공유해 여러 thread가  사용 가능하게 한다 

 

 

async / reactive 지원

Lettuce는 async를 지원을 한다 

예를 들어 

Future<String> result = asyncCommands.get("key");

 

와 같이 요청 보내고 응답 기다리지 않는다.

 

 

pipeline / cluster / sentinel 지원

Redis 운영할 때

  • replication
  • failover
  • cluster

이거 다 Lettuce가 처리 가능하다 

 

참고) Sentinel 지원 

/**
 * Lettuce
 */
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
  RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
  .master("mymaster")
  .sentinel("127.0.0.1", 26379)
  .sentinel("127.0.0.1", 26380);
  return new LettuceConnectionFactory(sentinelConfig);
}

 


Lettuce Level에서 Master-Slave 구조 (참고)

특히 Lettuce Level에서 Master는 쓰기를 담당하고 Slave는 읽기를 담당하는 Master-Slave 구조도 설정할 수 있다 

 

@Configuration
class WriteToMasterReadFromReplicaConfiguration {

  @Bean
  public LettuceConnectionFactory redisConnectionFactory() {

    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
      .readFrom(REPLICA_PREFERRED)
      .build();

    RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);

    return new LettuceConnectionFactory(serverConfig, clientConfig);
  }
}

 

천천히 알아가 보자!

 

 

 

.readFrom(REPLICA_PREFERRED)

 

이 한 줄의 코드에서 REPLICA_PREFERRED는 옵션의 종류 중 하나다 

 

REPLICA_PREFERRED

  • 가능하면 Replica에서 읽음 Replica 죽으면 Master에서 읽음
  • 가장 많이 쓰는 옵션

MASTER

  • 무조건 Master에서 읽음
  • replication 의미 없음

REPLICA

  • Replica에서만 읽음
  • Replica 죽으면 에러

MASTER_PREFERRED

  • Master에서 읽고
  • 죽으면 Replica 읽음
  • 거의 안 씀 

 

newRedisStandaloneConfiguration("server",6379);

 

이건 Master Redis 주소와 포트를 의미한다 

Lettuce는 여기에 접속해 

  • replication topology 자동 파악
  • replica node 찾는다 

 


3. RedisConnection 추상화의 의미

이전 구조를 생각해 보면  Lettuce에서 바로 연결하지 않고 중간에 Redis Connection이라는 걸 거쳐서 가는 구조였다 

Application
  ↓ 
RedisTemplate 
  ↓ 
RedisConnection 
  ↓ 
Lettuce 
  ↓
Redis Server

Spring Data Redis는 Redis Client 구현체(Lettuce / Jedis)를 직접 사용하지 않도록 추상화 계층을 둔다.

 마치 Spring에서 Connection을 얻기 위해 DataSource라는 추상화 객체를 이용하는 것과 똑같다 

 

이것이 RedisConnection 인터페이스이다. Spring의 설계 철학은 항상 동일하다.

  • JPA → Hibernate 숨김
  • JdbcTemplate → JDBC 숨김
  • RestTemplate → HttpClient 숨김
  • RedisTemplate → Redis Client 숨김

4. RedisConnection과 RedisTemplate의 등장

그럼 Redis Connection을 써야 하는 건 알겠는데 실제로 이 추상화를 사용하면 너무 많은 불편함을 느낄 수 있다 

다음과 같이 코딩을 해야 하기 때문이다 

byte[]key="user".getBytes();
byte[]value="donguk".getBytes();

connection.set(key,value);

 

이렇게 코딩하면

  • String → byte 변환 직접 해야 함
  • JSON → byte 변환 직접 해야 함
  • connection 직접 관리해야 함

유지보수뿐만 아니라 코딩할 때 실수 할 수도 있고 , 너무 코드가 더럽다 

 

 

그래서 등장한 게 RedisTemplate이다 

RedisTemplate은 Redis와의 상호작용을 위한 고수준 추상화이다.

이걸 사용하면 단순히 아래 코드만 작성하면  된다 

redisTemplate.opsForValue().set("user","donguk");

왜 이게 가능할까?

 

RedisTemplate을 사용하면 내부에서 다음 기능을  자동으로 처리해 준다 

  • Serializer 처리
  • Connection 관리
  • Thread-safe 처리
  • Exception 변환
  • Reconnect 처리

반면 RedisConnection은 byte [] 기반 저수준 API이다. 

 


RedisTemplate 이 제공하는 기능

GeoOperations Redis의 지리공간 연산(예 GEOADD: GEORADIUS...)
HashOperations 레디스 해시 연산
HyperLogLogOperations PFADDRedis HyperLog는 다음 과 같은 작업을 기록합니다 PFCOUNT.
ListOperations Redis 목록 작업
SetOperations 레디스 집합 연산
ValueOperations Redis 문자열(또는 값) 연산
ZSetOperations Redis zset(또는 정렬된 세트) 연산
키 바인딩 작업  
BoundGeoOperations Redis 키 기반 지리 공간 연산
BoundHashOperations Redis 해시 키 바인딩 작업
BoundKeyOperations Redis 키 바인딩 작업
BoundListOperations Redis 목록 키 바인딩 작업
BoundSetOperations Redis 세트 키 바인딩 작업
BoundValueOperations Redis 문자열(또는 값) 키 바인딩 작업
BoundZSetOperations Redis zset(또는 정렬된 세트) 키 바인딩 작업

 


5. StringRedisTemplate이 따로 존재하는 이유

 

 RedisTemplate을 사용하면 다음과 같은 문제가 있다 

RedisTemplate은 기본 직렬화를 사용한다 

Java Serialization (byte 형태)

 

그러다 보니 Redis에 저장된 값을 CLI를 통해 확인해 보면 

\xac\xed\x00\x05sr...

 

이렇게 보인다 

그리고 우리가 Redis에 cache key , token , flag , count , id와 같이 대부분 String 기반 데이터를 저장한다.

 

 

이러한 이유 때문에 

Spring은 문자열 전용 템플릿을 제공한다.

그것이 StringRedisTemplate이고 이 템플릿은 내부적으로

StringRedisSerializer

를 사용한다.

 

해당 템플릿을 사용하면 

redisTemplate.opsForValue().set("user","donguk");
user → donguk

 

사람이 읽을 수 있게 저장되고 디버깅도 쉽고 다른 언어에서도 사용 가능하다 

 


내부 Class 구조

내부 Class 구조를 보면 그 차이를 더 잘 느낄 수 있다 

 

RedisTemplate

publicclassRedisTemplate<K,V>

 

StringRedisTemplate

publicclassStringRedisTemplate extends RedisTemplate<String,String>

 

 

즉 상속 + serializer 미리 세팅된 버전이다 


6. String Connection?

StringRedisTemplate에 관한 공식 문서 글을 읽다 보면 StringConnection 이란 말이 나온다.

 

공식문서에서 보면 다음과 같은 말이 있다 

StringRedisTemplate은 내부적으로

문자열 전용 connection인 StringRedisConnection을 사용한다

 

StringRedisConnection 이건 byte [] 안 쓰고 String 기반으로 Redis 명령 실행하는 인터페이스이다 

 

별건 아니고  기존에 RedisConnection 부분이 Byte 기반인데 이걸 String 기반으로 하고 싶어서 만든 것이다

 

따라서 구조를 보면 이렇게 된다 

App
 ↓
StringRedisTemplate
 ↓
StringRedisConnection
 ↓
LettuceConnection
 ↓
Lettuce
 ↓
Redis Server

 


그러면 Template 마다 Connection 각각 다른 Connection을 쓸까?

RedisTemplate 은 Redis Connetion을 쓰고 RedisStringTemplate 은 StringRedisConnection을 쓰고.. 하니까 

Template 마다 다른 Connetion을 쓰는 걸까?

 

결론부터 말하자면 

❌ 아니다

StringRedisTemplate이라고 해서 connection을 따로 쓰지 않는다.

 

전체구조를 다시 보면 

App
 ↓
StringRedisTemplate   ← 문자열 serializer만 다름
 ↓
RedisConnectionFactory   ← 동일 (공용)
 ↓
RedisConnection
 ↓
LettuceConnection
 ↓
:contentReference[oaicite:2]{index=2}
 ↓
Redis Server

 

Template은 여러 개여도 ConnectionFactory는 보통 하나다

 

Single Thread 인 Redis에서는 

connection 많이 만들면 오히려 성능 떨어짐 , TCP handshake 비용 있음 , context switch 증가와 같은 위험이 있다

그래서 Lettuce는 Connetion 공유 전략을 쓴다 

 

 

실제 Spring Boot 동작에서도 

Spring Boot Redis starter 쓰면 자동으로

@Bean
LettuceConnectionFactory

 

의 빈이 하나만 생성되고 

  • RedisTemplate
  • StringRedisTemplate
  • CacheManager

이 전부 이걸 같이 쓴다 

 

그래서 

@Bean
RedisTemplateredisTemplate()

@Bean
StringRedisTemplatestringRedisTemplate()

 

이걸 둘 다 만들어도 내부 connectionFactory는 동일하다 


7. Redis 저장 방식과 Redis Hash & HashMapper

단순히 JSON을 Redis에 그냥 저장할 수 있다

예를 들어 

Useruser=newUser("donguk",27);

 

이 객체를 RedisTemplate + JSON serializer 쓰면

 

Redis에는 JSON 구조 그대로 들어간다 

user:1 → {"name":"donguk","age":27}

 

 

매우 간단하고 , 구조 그대로 가져갈 수 있어서  많이 쓴다 

하지만 단점이 있다 

예를 들어 age만 바꾸고 싶으면?  전체 JSON 다시 써야 한다 

 


RedisHash

이러한 문제를 의식하고 Redis는 Hash라는 자료구조를 제공한다.

Key → Field → Value

user:1
   name → donguk
   age → 27

 

 

Spring Data Redis는 Spring 객체 ↔ Redis Hash 변환을 위해 HashMapper를 제공한다.

 

대표 구현체

  • ObjectHashMapper
  • Jackson2 HashMapper

ObjectHashMapper

  • Java 직렬화 기반
  • byte 중심
  • 사람이 읽기 어려움

 

Jackson2 HashMapper Flat Mapping

nested 객체를 필드 단위로 펼쳐 저장한다.

예를 들어 

 

객체 

class Person {
    String firstname;
    String lastname;
    Address address;
}

class Address {
    String city;
    String country;
}

 

가 있으면 Jackson2 HashMapper는 객체 → Redis Hash 필드들로 변환해 준다

이때 2가지 모드가 있다


Normal mapping (기본 모드)

JSON 문자열 하나로 넣는다

Key = user:1

firstname → Jon
lastname → Snow
address → {"city":"Castle Black","country":"The North"}

 

Flat mapping 모드

Key = user:1

firstname → Jon
lastname → Snow
address.city → Castle Black
address.country → The North

 

nested 객체 구조를 펼쳐서 field로 만든다

 

Flat mapping을 자주 사용하게 되는데

 

그 이유는  예를 들어 도시만 바꾸고 싶다면 

Normal mapping 은  

1. address JSON 읽음
2. 파싱
3. city 수정
4. 전체 JSON 다시 저장

과 같은 과정을 가져가지만 

 

Flat mapping 은

HSET user:1 address.city Seoul

 

이 한 줄로 끝난다 

결국 네트워크 적음, CPU 적음 , 코드 간단 , 성능 좋음

 


8. Redis 트랜잭션 동작

Redis 트랜잭션은

MULTI
명령 큐 저장
EXEC

구조이다.

 

Spring에서는 SessionCallback을 이용하여 동일 connection에서 트랜잭션을 수행한다.

또한 RedisTemplate은 기본적으로 Spring @Transactional에 참여하지 않는다.

setEnableTransactionSupport(true)

설정이 필요하다.


9. Spring Boot Auto Configuration

spring-boot-starter-data-redis 의존성을 추가하면 자동 구성된다.

자동 생성되는 Bean

  • LettuceConnectionFactory
  • RedisTemplate
  • StringRedisTemplate
  • RedisCacheManager (조건부)
  • ReactiveRedisTemplate (조건부)

반대로 자동 설정되지 않는 것

  • Serializer 커스터마이징
  • Transaction Support
  • ReadFrom Replica 설정
  • Sentinel / Cluster 상세 설정

결론

Spring Data Redis는

  • Redis Client 구현체를 추상화하고
  • RedisTemplate을 통해 고수준 API를 제공하며
  • Lettuce를 통해 실제 네트워크 통신을 수행한다.

Spring Boot는 Redis 연결 인프라를 자동 구성하지만
Serializer / Transaction / Topology 전략은 개발자가 직접 설정해야 한다.