RedisTemplate에서 Lettuce 네이티브 API로 전환하여 처리 속도 4배 향상
RedisTemplate 사용 시 각 명령어마다 새 TCP 커넥션이 생성되는 문제를 발견하고, Lettuce 네이티브 API로 전환하여 100만 건 처리 시간을 240초에서 60초로 줄인 경험을 공유합니다.
문제 발견
Gmarket 랭킹 시스템을 Spring Webflux + MongoDB 기반으로 재설계하는 과정에서, 100만 건의 랭킹 데이터를 Redis에 배치 업데이트하는 작업이 예상보다 훨씬 느리다는 것을 발견했습니다.
측정 결과:
- 100만 건 처리 시간: 240초 (4분)
- 예상 처리 시간: 약 60초 이내
원인 분석
RedisTemplate의 내부 동작 방식
Datadog으로 Redis 연결 수를 모니터링하던 중, 배치 작업 실행 중에 연결 수가 폭발적으로 증가하는 것을 발견했습니다.
원인은 RedisTemplate의 내부 구현에 있었습니다. 기본 설정의 RedisTemplate은 각 Redis 명령어 호출 시마다 새로운 TCP 연결을 수립하고 해제합니다.
// 이 코드가 100만 번 실행되면?
redisTemplate.opsForValue().set(key, value);
// → 매번 새로운 TCP 연결 수립 → 사용 → 해제
// → 엄청난 I/O 오버헤드!
100만 건에 대해 매번 TCP handshake가 발생하니 성능 저하가 클 수밖에 없었습니다.
해결: Lettuce 네이티브 API 사용
Spring Data Redis는 기본적으로 Lettuce 클라이언트를 사용합니다. RedisTemplate은 이 위에 추상화 레이어를 얹은 것인데, 이 추상화가 성능 문제를 야기했습니다.
Lettuce 네이티브 API를 직접 사용하면 커넥션 풀링과 **파이프라이닝(내부 커맨드 큐잉)**을 통해 I/O를 최적화할 수 있습니다.
구현
@Configuration
class RedisConfig {
@Bean
fun lettuceConnectionFactory(): LettuceConnectionFactory {
val config = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(30))
.build()
return LettuceConnectionFactory(redisStandaloneConfig, config)
}
}
@Component
class RankingRedisRepository(
private val connectionFactory: LettuceConnectionFactory
) {
fun batchSet(entries: Map<String, String>) {
// Lettuce 네이티브 StatefulRedisConnection 사용
val conn = connectionFactory.connection.nativeConnection as StatefulRedisConnection<String, String>
val commands = conn.async()
// 파이프라이닝으로 한번에 요청
commands.setAutoFlushCommands(false)
val futures = entries.map { (k, v) ->
commands.set(k, v)
}
commands.flushCommands()
LettuceFutures.awaitAll(30, TimeUnit.SECONDS, *futures.toTypedArray())
}
}
핵심: 파이프라이닝
Lettuce의 파이프라이닝은 여러 명령어를 한 번에 묶어서 서버에 전송하고 응답을 모아서 받습니다.
[RedisTemplate 방식]
Client → SET key1 → Redis
← OK
Client → SET key2 → Redis
← OK
Client → SET key3 → Redis
← OK
...
[Lettuce 파이프라이닝]
Client → SET key1, SET key2, SET key3, ... → Redis
← OK, OK, OK, ...
네트워크 왕복 횟수가 대폭 줄어들어 I/O 오버헤드가 크게 감소합니다.
결과
| 항목 | RedisTemplate | Lettuce 네이티브 |
|---|---|---|
| 100만 건 처리 시간 | 240초 | 60초 |
| 성능 개선 | - | 4배 향상 |
| TCP 연결 수 (피크) | 수천 개 | 소수 (풀링) |
정리
RedisTemplate은 편의성이 높지만, 대량 배치 처리 시 성능 이슈가 발생할 수 있습니다. 다음과 같은 경우에는 Lettuce 네이티브 API를 고려하세요:
- 수십만 건 이상의 대량 배치 처리
- 낮은 레이턴시가 요구되는 작업
- 커넥션 수를 명시적으로 관리해야 하는 경우
반면 단건 조회·수정이나 소량 데이터 처리에는 RedisTemplate의 편의성이 더 가치 있을 수 있습니다. 상황에 맞는 도구를 선택하는 것이 중요합니다.