Database/Redis

[Spring Data Redis] 1. Redis

noahkim_ 2025. 5. 14. 21:49

1. RedisTemplate

  • Spring에서 Redis와 상호작용할 수 있게 해주는 중심 클래스.
항목 설명
동작 방식 자동으로 직렬화 및 연결 관리 처리 (고수준 API)
구성
RedisOperations 인터페이스 구현
스레드 안전성
설정 완료 후 스레드 안전함
뷰(Views)
opsForValue(), opsForList()
→ 특정 자료구조 전용 API처럼 사용 가능

 

예제) ListOperations

더보기
@Autowired
private ListOperations<String, String> listOps;

public void addLink(String userId, URL url) {
    listOps.leftPush(userId, url.toExternalForm());
}
  • 특정 자료형용 opsForXxx() 뷰를 DI로 주입 가능

vs StringRedisTemplate

항목 RedisTemplate StringRedisTemplate
직렬화 Java 직렬화 사용 (JdkSerializationRedisSerializer)
StringRedisSerializer 사용 (사람이 읽을 수 있는 문자열로 저장됨)
용도 객체 저장 시 유용 키-값 모두 문자열일 때 간편
명령 호출 template.opsForList().leftPush(...)
동일 (문자열 전용으로 최적화됨)

 

Serializer

항목 설명
기본 원리
객체 ↔ 바이트 간 자동 변환 (Redis에 저장되는 데이터는 모두 byte[])
종류
- JdkSerializationRedisSerializer: 기본값, Java native 방식 (보안 위험 있음)
- StringRedisSerializer: 문자열 기반
- Jackson2JsonRedisSerializer, GenericJackson2JsonRedisSerializer: JSON 기반
- OxmSerializer: XML용

 

설정

더보기

RedisTemplate.setKeySerializer(...), setValueSerializer(...)

 

RedisCallback 인터페이스

  • RedisConnection 수준의 낮은 레벨 명령어 실행 가능

 

예제

더보기
redisOperations.execute(new RedisCallback<Object>() {
  public Object doInRedis(RedisConnection connection) throws DataAccessException {
    Long size = connection.dbSize();
    ((StringRedisConnection)connection).set("key", "value");
    return null;
  }
});
  • StringRedisTemplate 사용 시 connection을 StringRedisConnection으로 캐스팅 가능

 

2. Redis Cache

RedisCacheManager

  • 전체 캐시 시스템을 관리하는 매니저 클래스
  • RedisCacheConfiguration를 사용하여 설정


설정) RedisCacheManager

더보기
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
                .transactionAware()
                .withInitialCacheConfigurations(Map.of("predefined",
                    RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()))
                .build();
}

기본 동작

  • TTL 미설정
  • null 값 캐시 금지
  • prefix 사용 (캐시이름::key)
  • key 직렬화: StringRedisSerializer
  • value 직렬화: JdkSerializationRedisSerializer

 

RedisCacheConfiguration
  • 개별 캐시의 동작 방식을 설정하는 구성 클래스
설정 설명
entryTtl(Duration)
TTL 설정 (고정 또는 동적으로 설정 가능)
prefixCacheNameWith("prefix") 고정 접두어 설정
computePrefixWith(cacheName -> "...") 동적 접두어 설정
disableCachingNullValues() null 값 캐시 방지
serializeKeysWith(...) 키 직렬화 방식 설정
serializeValuesWith(...) 값 직렬화 방식 설정

 

설정) RedisCacheConfiguration

더보기
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .disableCachingNullValues()
        .computePrefixWith(cacheName -> "")
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // TODO: Bean 등록 ?
        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer));

 

TTL vs TTI

구분 TTL (Time-To-Live) TTI (Time-To-Idle)
동작 조건 작성/수정 시 TTL 리셋 읽기/쓰기 모두 TTL 리셋
지원 방식 Redis 기본 지원
Redis 6.2+의 GETEX 명령어 기반
Spring 설정 entryTtl(Duration.ofMinutes(5)) .enableTimeToIdle() 추가
주의점 단순 GET 호출 시 TTL 리셋 안 됨
반드시 GETEX를 통해 접근해야 동작

 

예제) 동적 TTL 설정

더보기

TtlFunction 인터페이스

enum MyCustomTtlFunction implements TtlFunction {
    INSTANCE;
    public Duration getTimeToLive(Object key, @Nullable Object value) {
        // 키 또는 값에 따라 TTL 계산
    }
}
  • RedisCacheManager에서 entryTtl(MyCustomTtlFunction.INSTANCE)로 적용 가능

 

예제) TTI 설정

더보기
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {

    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(5))       // TTL 설정 (필수)
        .enableTimeToIdle();                   // TTI 활성화

    return RedisCacheManager.builder(connectionFactory)
        .cacheDefaults(config)
        .build();
}

 

Locking vs Non-locking

항목 설명
Non-locking (기본값) 빠름
putIfAbsent, clear에서 원자성 부족  
Locking 명시적 락 키를 사용
- 겹치는 명령어 방지
- 성능은 낮아질 수 있음

 

예제) Locking

더보기
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    return RedisCacheManager.builder(
            RedisCacheWriter.lockingRedisCacheWriter(connectionFactory)
    )
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    .build();
}

 

캐시 삭제 전략

  • 지울 키를 찾기 위해 사용하는 전략
전략 설명
KEYS (기본)
전체 키 조회.
- 대용량에서는 성능 이슈 발생
SCAN
배치 기반 순회 방식
- Lettuce에서 권장
- Jedis는 Standalone만 지원

 

예제) SCAN 설정

더보기
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    return RedisCacheManager.builder(
            RedisCacheWriter.nonLockingRedisCacheWriter(
                    connectionFactory,
                    BatchStrategies.scan(1000) // SCAN 기반, 한 번에 1000개씩 처리
            )
    )
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    .build();
}

 

Redis Cluster

Hash Mapping

Redis 해시 저장 방식

방식 설명
Jackson2JsonRedisSerializer 하나의 key에 전체 객체를 JSON 문자열로 저장 (SET 사용)
Redis Hash + HashMapper 객체의 속성을 field:value 형식으로 Hash 구조에 저장 (HSET 사용)

 

HashMapper

구현체 특징
ObjectHashMapper
단순 Java 직렬화 (바이트 기반)
BeanUtilsHashMapper
Java Bean → Map 변환 (평면 구조만 가능)
Jackson2HashMapper
JSON 기반, 중첩 객체까지 지원→ 평면(flat) 또는 중첩 저장 가능

 

설정) Jackson2HashMapper

더보기
@Bean("hashRedisTempate")
public RedisTemplate<String, Object> hashRedisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setKeySerializer(new StringRedisSerializer());
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setHashValueSerializer(RedisSerializer.byteArray()); // HashMapper가 직접 처리하기 위함

    return template;
}

@Bean
public Jackson2HashMapper jackson2HashMapper() {
    return new Jackson2HashMapper(false); // flat = false (중첩 JSON 저장)
}

 

예시) Jackson2HashMapper

더보기
@Component
public class PersonHashRepository {

    private final HashOperations<String, byte[], byte[]> hashOps;
    private final Jackson2HashMapper mapper;

    public PersonHashRepository(
            @Qualifier("hashRedisTemplate") RedisTemplate<String, Object> hashRedisTemplate,
            Jackson2HashMapper mapper) {
        this.hashOps = hashRedisTemplate.opsForHash();
        this.mapper = mapper;
    }

    public void save(String key, Person person) {
        Map<byte[], byte[]> mapped = mapper.toHash(person);
        hashOps.putAll(key, mapped);
    }

    public Person find(String key) {
        Map<byte[], byte[]> loaded = hashOps.entries(key);
        return (Person) mapper.fromHash(loaded);
    }
}

 

Pub/Sub Messaging

publishing

subscribing

 

 

Redis Streams

*Scripting

  • Lua Script를 실행하는 방법

 

실행 방식

RedisTemplate

ScriptExecutor

RedisScript<T>: 스크립트를 담는 객체

 

예제

lua script

더보기
local current = redis.call('GET', KEYS[1])
if current == ARGV[1] then
  redis.call('SET', KEYS[1], ARGV[2])
  return true
end

return false

 

spring data redis

더보기
@Bean
public RedisScript<Boolean> script() {
    ScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource("META-INF/scripts/checkandset.lua"));
    return RedisScript.of(scriptSource, Boolean.class);
}
@Autowired
RedisOperations<String, String> redisOperations;

@Autowired
RedisScript<Boolean> script;

public boolean checkAndSet(String expectedValue, String newValue) {
  return redisOperations.execute(script, List.of("key"), expectedValue, newValue);
}

 

성능

  • DefaultRedisScript: SHA1 재계산
  • evalsha 사용: 스크립트 SHA1 캐시를 자동 활용

 

*Redis Transactions

  • Redis는 트랜잭션 내의 명령들을 명령 큐(queue)에 저장했다가 EXEC 시점에 실행함

 

기본 명령어

명령어 설명
MULTI
트랜잭션 시작 (이후 명령들이 일시적으로 큐에 저장됨)
EXEC
큐에 저장된 명령들을 한 번에 실행
DISCARD
큐에 저장된 명령들을 모두 무시하고 취소

 

설정

  • 기본적으로 RedisTemplate은 스프링 트랜잭션과 연동되지 않음 (@Transactional)
  • 스프링 트랜잭션과 연동하기 위해 추가적인 설정이 필요함

 

설정) @Transactional 연동

더보기
@Configuration
@EnableTransactionManagement
public class RedisTxContextConfiguration {

    @Bean
    public StringRedisTemplate redisTemplate() {
        var template = new StringRedisTemplate(redisConnectionFactory());
        template.setEnableTransactionSupport(true); // 중요!!
    	return template;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource()); // JDBC 트랜잭션 매니저
    }
}
  • JDBC 트랜잭션과 함께 연동하고 싶을 때 쓰는 방식
  • setEnableTransactionSupport(): Redis connection이 현재 쓰레드에 바운드됨

 

사용

SessionCallback
  • Redis 명령을 같은 세션 안에서 고정되게 해주는 인터페이스
  • Redis 트랜잭션 명령을 하도록 도와줌
  • Redis의 MULTI~EXEC 사이의 명령은 같은 커넥션에서 실행되어야 함
더보기
List<Object> txResults = redisTemplate.execute(new SessionCallback<>() {
    public List<Object> execute(RedisOperations operations) {
        try {
            operations.multi();  // 트랜잭션 시작
            operations.opsForSet().add("key", "value1");
            return operations.exec();  // 실행
        } catch (RuntimeException e) {
            operations.discard(); // 꼭 discard로 정리
            throw e;
        }    
    }
});
  • 예외가 발생하면, 반드시 discard()로 정리하기 (exec()을 못 탐 → 다음 명령도 트랜잭션으로 인식됨)

 

트랜잭션 중 읽기 제한

  • 한 트랜잭션 안에서 쓰여진 데이터를 읽기 명령으로 읽을 수 없음
  • 읽기 명령은 새 커넥션으로 실행됨 (트랜잭션 큐를 못 봄)

 

Pipelining

*Support Classes

  • Java 표준 인터페이스를 Redis 위에서 구현한 클래스들을 제공
클래스 Redis 자료구조 설명
RedisList<T> List, Queue, Deque
FIFO/LIFO 동작 가능
BLPOP, BRPOP 같은 blocking 동작도 가능
RedisSet<T> Set
Redis의 SADD, SREM, SINTER 등 지원
RedisZSet<T> Sorted Set
ZADD, ZRANGE, ZINTER 등 지원
RedisAtomicLong String(Number)
INCR, DECR 등 Redis 기반 카운터

 

 

장점

항목 효과
저장소 추상화
Redis 대신 Java in-memory list로 대체 가능
테스트 용이성 모킹/대체 쉬움
유연한 구조
프로토타입 환경에서 in-memory, 운영에서는 Redis만 바꿔 끼우기

 

설정

더보기
@Bean
RedisList<String> myQueue(RedisTemplate<String, String> redisTemplate) {
    return new DefaultRedisList<>(redisTemplate, "queue-key");
}
  • "queue-key"라는 Redis 리스트 키로 연결되는 Java List가 생성됨

 

예제

더보기
public class AnotherExample {

    private Deque<String> queue;

    public void addTag(String tag) {
    	queue.push(tag); // 내부적으로 Redis LPUSH
    }

    public String popTag() {
    	return queue.pop(); // 내부적으로 Redis LPOP or RPOP
    }
}

 

 

출처