VPN을 통한 Redis가 느립니다: 요청당 왕복 줄이기
VPN은 원격 Redis 호출을 로컬로 만들지 않습니다. 요청당 왕복을 예산에 넣고, 연결을 재사용하며, 파이프라인을 트랜잭션과 멀리 두세요.
로컬 Redis에 대해 200 ms로 응답하던 페이지가 클라이언트가 VPN 뒤에 앉는 순간 네 초가 걸리는 반면 서버 카운터는 놀고 있습니다. 슬로우 로그에는 밀리초를 넘는 항목이 없고, CPU 그래프는 평평하며, 인스턴스는 다른 클라이언트를 문제없이 처리합니다. 달라진 것은 거리이고, 거리를 배가하는 것은 코드가 멈춰서 기다리는 횟수입니다. 클라이언트에서 바깥으로 요청 경로를 따라가 보면 해결책은 거의 항상 더 빠른 서버가 아니라 직렬 대기의 감소입니다.
서버 지표가 아니라 클라이언트에서 시작하세요
첫 명령과 마지막 명령 사이의 클라이언트 프로세스를 관찰하세요. 추적이 응답, 일시 정지, 그다음 명령을 보여 준다면 명령마다 왕복을 전부 지불하는 것이고 서버 튜닝으로는 바뀌지 않습니다. 여기서 슬로우 로그는 잘못된 도구인데, 실행 시간을 기록하고 통신 I/O를 의도적으로 제외하기 때문입니다. 느린 엔드포인트 옆의 깨끗한 슬로우 로그는 왕복 문제의 서명입니다.
코드를 만지기 전에 경로 자체를 측정하세요. redis-cli -h redis.example.internal --latency 는 PING을 반복해서 보내고 평균 요청 및 응답 시간을 출력하며, 이것이 교환 한 번의 비용입니다. 한 사용자 동작의 직렬 명령 수를 곱하면 엔드포인트의 하한이 나옵니다.
한 사용자 동작이 실제로 필요한 왕복을 세세요
엔드포인트 하나를 골라 ORM, 세션 미들웨어, 기능 플래그 조회 안에 숨은 것까지 포함해 모든 Redis 호출을 순서대로 적으세요. 대부분의 느린 엔드포인트는 세 번이면 될 곳에서 스무 번이나 서른 번 호출하며, 숨은 호출이 보통 다수를 차지합니다.
의존성 사슬은 접을 수 없어서, 두 번째 명령이 첫 번째가 반환한 키를 필요로 한다면 대기는 실제이며 선택지는 다른 데이터 모델이나 전체 단계를 한 번에 표현하는 서버 측 작업뿐입니다. 독립적인 읽기는 다른 경우인데, 두 번째를 보내기 전에 첫 응답을 기다리도록 강제하는 것은 없습니다. 무엇이든 편집하기 전에 각 호출을 의존 또는 독립으로 표시하면 수정의 형태가 분명해집니다.
중요한 숫자는 직렬 대기이지 전체 명령이 아닙니다. 한 배치에 서른 개의 명령을 넣어도 대기는 한 번입니다.
서로 의존하지 않는 명령만 배치하세요
파이프라인은 독립 명령을 큐에 넣고 응답을 순서대로 모아 N번의 대기를 한 번으로 바꿉니다. 대부분의 클라이언트는 이를 별도의 연결 모드가 아니라 블록, 콜백, 컨텍스트 관리자로 제공하므로 설치된 버전이 제공하는 API를 확인하세요. 이득은 왕복 시간에 비례해 커지며, 바로 그래서 VPN에서 중요하고 localhost에서는 거의 느껴지지 않습니다.
import redis
r = redis.Redis(host="redis.example.internal", port=6379, socket_timeout=2)
pipe = r.pipeline(transaction=False)
for key in ("user:1001", "user:1002", "user:1003"):
pipe.get(key)
print(pipe.execute())
배치만 원한다면 transaction=False 를 전달하세요. 기본값에서는 클라이언트가 배치를 MULTI와 EXEC로 감싸므로 원자적이 되고 다른 클라이언트가 중간에 관찰할 수 있는 내용이 달라집니다. 원자성은 성능 손잡이가 아니라 정확성 결정이며, 둘 다 왕복을 줄인다고 해서 파이프라인이 트랜잭션이 되는 것은 아닙니다.
배치 제출만이 아니라 모든 응답을 읽으세요. 큐에 넣은 명령은 배치 전체가 완료되는 동안 오류를 반환할 수 있고, 응답 목록을 버리는 클라이언트는 실패한 쓰기를 조용한 쓰기로 바꿉니다. 응답 목록의 길이를 큐에 넣은 명령 수와 비교하세요.
동작 사이에 연결을 살려 두세요
새 연결마다 이름 확인, TCP 핸드셰이크, TLS 핸드셰이크, AUTH 비용을 지불하며 VPN에서는 그중 어느 것도 배치로 없앨 수 없는 왕복입니다. 프로세스마다 클라이언트를 한 번 만들어 재사용하거나, 라이브러리가 제공하는 상한이 있는 풀을 사용하세요. 요청마다 다시 연결하는 것은 명령당 대기 다음으로 이 증상의 두 번째 흔한 원인입니다.
구성에는 리터럴 주소가 아니라 사설 호스트 이름을 두세요. 인증서 검증과 서비스 신원은 사설 네트워크에서도 적용되며, IP를 고정하면 공급자가 서브넷 번호를 다시 매기는 순간 깨집니다. 이름 확인이 느린 부분이라면 연결 문자열이 아니라 리졸버 경로를 고치세요.
명시적 소켓 타임아웃을 설정해 멈춘 터널이 워커를 매다는 대신 빨리 실패하게 하세요. 그런 다음 클라이언트에서 연결 생성을 기록해 풀이 정말 재사용되는지 확인하세요.
메모리 문제가 되기 전에 배치 크기를 정하세요
Redis는 배치가 진행되는 동안 큐에 넣은 응답을 보관하고, 클라이언트는 그 뒤에 파싱된 결과를 보관합니다. 큰 값 일만 개의 파이프라인은 네트워크 대기를 메모리 급증과 양쪽 끝의 긴 멈춤으로 바꿉니다.
배치 크기는 명령 수가 아니라 응답량을 기준으로 고르세요. 배치 하나에 작은 명령 수백 개는 보통 편안하지만, 수 킬로바이트 페이로드 백 개는 그렇지 않습니다. 바이트와 개수 모두로 나누고, 각 배치를 엔드포인트가 실제로 가진 지연 예산 안에 두세요.
변경 후 끝에서 끝까지 측정하면 redis-cli -h redis.example.internal --latency-history 는 경로 자체가 안정적인지, 직접 만든 요청 타이머는 엔드포인트가 개선되었는지 보여 줍니다. 지연 이력이 평평한데 엔드포인트가 여전히 느리다면 남은 비용은 서버 측 작업이나 직접 만든 직렬화이며, 다음 단계는 또 다른 배치 변경이 아니라 핸들러 프로파일입니다.