ㅌ
포스트

[Redis] Lettuce TCP 연결 유실

[Redis] Lettuce TCP 연결 유실

Redis와 Lettce 연결하는 클라이언트

사용자 클라이언트와 Redis는 Lettuce로 모든 스레드가 하나의 TCP 소켓으로 통신합니다.

이 하나의 TCP 소켓에서 발생 가능한 트러블 이슈 공유합니다.

Redis 연결 TCP 연결 실패

클라이언트는 Redis에게 데이터를 보내고 이에 대한 응답(ACK, RST)을 받습니다. 하지만 어떤 이슈(노드 작업)로 연결이 죽은 연결이 된다면 새로운 연결을 수립해야합니다.

하지만 Linux 환경에서는 tcp_retries2 = 15 재시도 15회 정책으로 OS가 죽은 연결을 약 15분 정도 붙잡고 있을 수 있습니다.

그 기간동안 Redis를 사용하는 모든 업무는 타임아웃이 발생하며 이는 서비스의 치명적인 문제를 야기할 수 있습니다.

조치법

Linux OS 자체에 타임아웃을 수정하는 방법도 가능하지만 일반 VM의 경우OS 전체에 적용됩니다.

다만 Kubernetes 환경에서도 Pod 단위로 지정이 되어 정확한 적용 범위 파악이 힘듭니다.

서비스 클라이언트에서 적용하는게 가장 깔끔한데 JDK 자체적으로는 지원하지 않고, Lettuce 내부적으로 epoll를 통해 지원하는 기능이 있으니 이걸 사용하면 됩니다.

TCP_TIMEOUT 지정

1
2
3
4
5
SocketOptions.builder()
        .tcpUserTimeout(SocketOptions.TcpUserTimeoutOptions.builder()
                .enable()
                .tcpUserTimeout(Duration.ofSeconds(15))
                .build())

→ ACK를 처음 못 받은 시점부터 15초 지나면 새로운 TCP 연결 수립

적용 후 테스트

적용하였으면, TCP 연결 유실 환경을 재현하여 테스트 해봅니다.

netshoot 이미지를 Pod에 디버그로 붙입니다

1
2
3
4
5
# 디버그 컨테이너 접속
kubectl debug -it <pod> --image=<netshoot 이미지> --profile=netadmin

# Redis 연결 상태 확인
ss -tnio '( dport = :6380 )'

Redis와 현재 연결된 Port를 확인합니다. 단 Redis 포트가 6380이 아니라면 위 값을 수정합니다

연결된 port를 확인하고 Redis에서 들어오는 트래픽을 iptables내에서 버립니다

1
iptables -A INPUT -p tcp --sport 6380 --dport <포트> -j DROP

결과적으로 클라이언트는 어떤 응답도 받지 못하고 위의 TCP 연결 장애와 비슷한 환경에서 적극적으로 TCP 연결이 재수립되는지 확인합니다.

Linux가 왜 이런 설정을?

기본적으로 연결을 기다릴것인가, 아니면 기다리지 않을 것인가의 차이로 볼 수 있습니다.

TCP의 RFC 1122 권고는 최소 100초 이상으로 약간의 연결 끊김에서 모든 TCP 연결이 재수립되는게 더 큰 문제라고 판단하였습니다. (세션, 장시간 DB 트랜잭션)

다만 실제 대부분의 서비스는 즉시 응답이 필요하기에 이 시간을 다 기다리기에는 무리가 있다고 판단되기에 본인의 서비스의 중요성에 따라 적절하게 이 값을 튜닝하는건 필수적으로 보입니다.

참조한 문서

auzre-cache-best-practices-connection

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.