From a9909f2f770ea8da22f91060adb71772d6ed87ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EB=AF=B8?= Date: Fri, 24 Jul 2026 20:02:49 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20Redis=20=EC=84=A4=EC=A0=95=EC=97=90?= =?UTF-8?q?=20=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8/TLS/=ED=83=80=EC=9E=84?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=A7=80=EC=9B=90=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(=EA=B4=80=EB=A6=AC=ED=98=95=20Redis=20=EB=8C=80=EC=9D=91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존엔 로컬 Redis(비밀번호 없음, 평문 연결)만 전제로 했는데, Upstash 같은 관리형 Redis는 비밀번호 인증 + TLS가 필수라 연결 자체가 불가능했음. - spring.data.redis.password/ssl.enabled 프로퍼티로 조건부 활성화 (미설정 시 기존 로컬 docker-compose Redis와 100% 하위 호환) - Lettuce 연결/커맨드 타임아웃을 30초로 상향 (Render Free 티어는 0.1 CPU라 TLS 핸드셰이크 자체가 느려서, 기본 타임아웃 안에 핸드셰이크가 못 끝나고 "Connection closed prematurely"로 실패하는 문제 확인) --- .../server/config/redis/RedisConfig.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/runnect/server/config/redis/RedisConfig.java b/src/main/java/org/runnect/server/config/redis/RedisConfig.java index 7cd9b7c..7f516d3 100644 --- a/src/main/java/org/runnect/server/config/redis/RedisConfig.java +++ b/src/main/java/org/runnect/server/config/redis/RedisConfig.java @@ -1,14 +1,20 @@ package org.runnect.server.config.redis; +import io.lettuce.core.ClientOptions; +import io.lettuce.core.SocketOptions; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.EnableCaching; 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.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.RedisTemplate; +import java.time.Duration; + @EnableCaching @Configuration public class RedisConfig { @@ -19,9 +25,34 @@ public class RedisConfig { @Value("${spring.data.redis.port}") private int port; + @Value("${spring.data.redis.password:}") + private String password; + + @Value("${spring.data.redis.ssl.enabled:false}") + private boolean sslEnabled; + @Bean public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(host, port); + RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration(host, port); + if (!password.isBlank()) { + standaloneConfig.setPassword(password); + } + + ClientOptions clientOptions = ClientOptions.builder() + .socketOptions(SocketOptions.builder() + .connectTimeout(Duration.ofSeconds(30)) + .build()) + .build(); + + LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigBuilder = + LettuceClientConfiguration.builder() + .clientOptions(clientOptions) + .commandTimeout(Duration.ofSeconds(30)); + if (sslEnabled) { + clientConfigBuilder.useSsl(); + } + + return new LettuceConnectionFactory(standaloneConfig, clientConfigBuilder.build()); } @Bean From fcaf4cc8f12fea23de955c2d994c4967d0c41f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=82=98=EB=AF=B8?= Date: Fri, 24 Jul 2026 20:12:24 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20Lettuce=20SSL=20=ED=95=B8=EB=93=9C?= =?UTF-8?q?=EC=85=B0=EC=9D=B4=ED=81=AC=20=ED=83=80=EC=9E=84=EC=95=84?= =?UTF-8?q?=EC=9B=83(SslOptions)=20=EB=B3=84=EB=8F=84=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SocketOptions.connectTimeout은 TCP 연결 단계만 커버하고, TLS 핸드셰이크는 별도의 SslOptions.handshakeTimeout이 관리함. SSL 활성화 시에만 handshakeTimeout(30s)을 추가로 설정. --- .../runnect/server/config/redis/RedisConfig.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/runnect/server/config/redis/RedisConfig.java b/src/main/java/org/runnect/server/config/redis/RedisConfig.java index 7f516d3..a5bf359 100644 --- a/src/main/java/org/runnect/server/config/redis/RedisConfig.java +++ b/src/main/java/org/runnect/server/config/redis/RedisConfig.java @@ -2,6 +2,7 @@ import io.lettuce.core.ClientOptions; import io.lettuce.core.SocketOptions; +import io.lettuce.core.SslOptions; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -38,15 +39,19 @@ public RedisConnectionFactory redisConnectionFactory() { standaloneConfig.setPassword(password); } - ClientOptions clientOptions = ClientOptions.builder() + ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder() .socketOptions(SocketOptions.builder() .connectTimeout(Duration.ofSeconds(30)) - .build()) - .build(); + .build()); + if (sslEnabled) { + clientOptionsBuilder.sslOptions(SslOptions.builder() + .handshakeTimeout(Duration.ofSeconds(30)) + .build()); + } LettuceClientConfiguration.LettuceClientConfigurationBuilder clientConfigBuilder = LettuceClientConfiguration.builder() - .clientOptions(clientOptions) + .clientOptions(clientOptionsBuilder.build()) .commandTimeout(Duration.ofSeconds(30)); if (sslEnabled) { clientConfigBuilder.useSsl();