Redis 클라이언트 핸들링
Redis 클라이언트 핸들링 (Client Handling)
Redis 서버가 클라이언트 연결을 어떻게 관리하는지 — 연결, 타임아웃, 버퍼 같은 네트워크 계층의 이야기를 다룹니다.
이 문서는 Redis가 네트워크 계층에서 클라이언트를 어떻게 처리하는지 설명합니다. 연결, 타임아웃, 버퍼 같은 주제가 여기에 포함돼요. 이 문서의 내용은 Redis 2.6 이상 버전에만 적용됩니다.
클라이언트 연결 수락 (Accepting Client Connections)
Redis는 설정된 TCP 포트와 (활성화한 경우) 유닉스 소켓에서 클라이언트 연결을 수락합니다. 새 클라이언트 연결이 수락되면 다음과 같은 동작이 수행됩니다.
- 클라이언트 소켓을 논블로킹(non-blocking) 상태로 둡니다. Redis는 멀티플렉싱과 논블로킹 I/O를 사용하거든요.
TCP_NODELAY옵션을 설정해 연결에 지연이 없도록 보장합니다.- 소켓에 읽을 새 데이터가 생기면 Redis가 클라이언트 쿼리를 모을 수 있도록 readable 파일 이벤트를 생성합니다.
클라이언트 초기화가 끝나면 Redis는 동시 클라이언트 수 한도(maxclients 구성 지시어로 설정, 다음 절 참고)에 도달했는지 확인합니다. 최대 클라이언트 수에 도달해 새 연결을 수락할 수 없으면, Redis는 이 상태를 알리는 오류를 클라이언트에 보내려 시도하고 즉시 연결을 닫습니다. 새 소켓의 출력 버퍼는 보통 오류를 담을 만큼 충분히 크므로, Redis가 즉시 연결을 닫아도 커널이 오류 전송을 처리해 오류 메시지가 클라이언트에 도달합니다.
클라이언트 요청이 처리되는 순서는?
처리 순서는 클라이언트 소켓 파일 디스크립터 번호와 커널이 이벤트를 보고하는 순서의 조합으로 정해지므로, 미지정(unspecified)으로 간주해야 합니다.
다만 Redis는 클라이언트를 서빙할 때 두 가지 일을 합니다.
- 클라이언트 소켓에서 읽을 새 데이터가 있을 때마다 단 한 번의
read()시스템 콜만 수행합니다. 이렇게 하면 여러 클라이언트가 연결되어 있고 몇몇이 높은 속도로 쿼리를 보내도 다른 클라이언트가 불이익을 받아 지연 문제를 겪지 않습니다. - 하지만 클라이언트에서 새 데이터를 읽으면, 현재 버퍼에 담긴 모든 쿼리는 순차적으로 처리됩니다. 이는 지역성을 높이고, 처리할 클라이언트가 있는지 두 번 순회할 필요가 없게 합니다.
최대 동시 연결 클라이언트 수 (Maximum Concurrent Connected Clients)
Redis 2.4에서는 동시 처리 가능한 최대 클라이언트 수에 하드코딩된 한도가 있었습니다.
Redis 2.6부터는 이 한도를 redis.conf의 maxclients 지시어로 구성할 수 있습니다. 기본값은 10,000개 클라이언트입니다.
Redis는 커널에 물어볼 수 있는 최대 파일 디스크립터 수(soft limit을 확인)를 확인합니다. 이 한도가 처리하려는 최대 클라이언트 수 + 32(Redis가 내부용으로 예약하는 파일 디스크립터 수)보다 작으면, 최대 클라이언트 수는 현재 OS 한도에서 정말로 처리할 수 있는 클라이언트 수에 맞춰 갱신됩니다.
maxclients를 Redis가 지원할 수 있는 것보다 큰 값으로 설정하면 시작 시 다음과 같은 메시지가 로그됩니다.
$ ./redis-server --maxclients 100000
[41422] 23 Jan 11:28:33.179 # Unable to set the max number of files limit to 100032 (Invalid argument), setting the max clients configuration to 10112.
특정 수의 클라이언트를 처리하도록 Redis를 구성할 때는, 프로세스당 최대 파일 디스크립터 수에 대한 운영체제 한도도 그에 맞게 설정하는 것이 좋습니다.
Linux에서는 이러한 한도를 현재 세션과 시스템 전체 설정으로 모두 정할 수 있습니다.
ulimit -Sn 100000 # This will only work if hard limit is big enough.sysctl -w fs.file-max=100000
출력 버퍼 한도 (Output Buffer Limits)
명령 하나가 클라이언트로 전송해야 할 많은 데이터를 만들 수 있으므로, Redis는 모든 클라이언트에 대해 가변 길이 출력 버퍼를 처리해야 합니다.
하지만 클라이언트가 Redis가 기존 출력을 보내는 속도보다 빠르게 더 많은 출력을 만드는 명령을 보낼 수 있습니다. 특히 Pub/Sub 클라이언트가 새 메시지를 충분히 빨리 처리하지 못하는 경우에 그렇습니다.
이 두 상황 모두 클라이언트 출력 버퍼가 계속 커지며 메모리를 점점 더 소모하게 만듭니다. 그래서 기본적으로 Redis는 클라이언트 유형별로 출력 버퍼 크기에 한도를 설정합니다. 한도에 도달하면 클라이언트 연결이 끊기고 그 이벤트가 Redis 로그 파일에 기록됩니다.
Redis가 사용하는 한도는 두 종류입니다.
- 하드 한도(hard limit): 고정된 한도로, 도달하면 Redis가 가능한 한 빨리 클라이언트 연결을 닫습니다.
- 소프트 한도(soft limit): 시간에 의존하는 한도입니다. 예를 들어 10초 동안 32MB 소프트 한도는, 클라이언트 출력 버퍼가 32MB보다 크면서 10초간 지속되면 연결이 닫히는 식입니다.
클라이언트 유형마다 기본 한도가 다릅니다.
- 일반 클라이언트: 기본 한도 0, 즉 제한 없음. 대부분의 일반 클라이언트는 단일 명령을 보내고 완전히 읽힌 응답을 기다렸다가 다음 명령을 보내는 블로킹 구현을 사용하므로, 일반 클라이언트의 연결을 닫는 것은 항상 바람직하지 않기 때문입니다.
- Pub/Sub 클라이언트: 기본 하드 한도 32MB, 소프트 한도 8MB/60초.
- 복제본(Replica): 기본 하드 한도 256MB, 소프트 한도 64MB/60초.
런타임에는 CONFIG SET 명령으로, 영구적으로는 Redis 구성 파일 redis.conf에서 한도를 바꿀 수 있습니다. 한도 설정 방법은 Redis 배포판의 예제 redis.conf를 참고하세요.
쿼리 버퍼 하드 한도 (Query Buffer Hard Limit)
모든 클라이언트는 쿼리 버퍼 한도에도 적용받습니다. 이는 구성 불가능한 하드 한도로, 클라이언트 쿼리 버퍼(클라이언트의 명령을 축적하는 버퍼)가 1GB에 도달하면 연결을 닫습니다. 사실상 클라이언트나 서버 소프트웨어 버그로 인한 서버 크래시를 피하기 위한 극단적인 한도에 불과합니다.
클라이언트 퇴출 (Client Eviction)
Redis는 매우 많은 수의 클라이언트 연결을 처리하도록 설계되었습니다. 클라이언트 연결은 메모리를 소모하는 경향이 있고, 수가 많으면 전체 메모리 소모가 극도로 높아져 데이터 퇴출이나 OOM(메모리 부족) 오류로 이어질 수 있습니다. 이런 경우는 출력 버퍼 한도로 어느 정도 완화할 수 있지만, Redis는 모든 클라이언트 연결이 쓰는 전체 메모리를 제한하는 더 견고한 구성을 제공합니다.
이 메커니즘을 클라이언트 퇴출(client eviction) 이라고 하며, 본질적으로 모든 클라이언트의 전체 메모리 사용량이 임계값을 넘으면 클라이언트를 연결 해제하는 안전 장치입니다. 이 메커니즘은 먼저 메모리를 가장 많이 쓰는 클라이언트부터 연결을 끊으려 시도하며, maxmemory-clients 임계값 아래로 돌아가기 위해 필요한 최소한의 클라이언트 수만 끊습니다.
maxmemory-clients는 Redis에 연결된 모든 클라이언트의 최대 전체 메모리 사용량을 정의합니다. 집계에는 클라이언트 연결이 사용하는 모든 메모리 — 쿼리 버퍼, 출력 버퍼, 기타 중간 버퍼 — 가 포함됩니다.
복제본과 마스터 연결은 클라이언트 퇴출 메커니즘의 영향을 받지 않으므로 절대 퇴출되지 않습니다.
maxmemory-clients는 구성 파일(redis.conf)에서 영구적으로 설정하거나 CONFIG SET 명령으로 설정할 수 있습니다. 이 값은 0(제한 없음), 바이트 단위 크기(mb/gb 접미사 가능), 또는 % 접미사를 써서 maxmemory의 백분율이 될 수 있습니다(예: 10% = maxmemory 구성의 10%).
기본 설정은 0, 즉 클라이언트 퇴출이 기본적으로 꺼져 있습니다. 하지만 대규모 프로덕션 배포에서는 0이 아닌 maxmemory-clients 값을 구성하는 것을 강력히 권장합니다. 예를 들어 5%가 좋은 시작점이 될 수 있습니다.
특정 클라이언트 연결을 클라이언트 퇴출 메커니즘에서 제외하도록 표시할 수 있습니다. 제어 경로 연결에 유용합니다. 예를 들어 INFO 명령으로 서버를 모니터링하고 문제 시 알림을 보내는 애플리케이션이 있다면, 이 연결이 퇴출되지 않도록 하고 싶을 것입니다. 다음과 같이 (해당 클라이언트의 연결에서) 할 수 있습니다.
그리고 다음과 같이 되돌릴 수 있습니다.
자세한 내용과 예시는 기본 redis.conf 파일의 maxmemory-clients 섹션을 참고하세요.
클라이언트 퇴출은 Redis 7.0부터 사용할 수 있습니다.
클라이언트 타임아웃 (Client Timeouts)
기본적으로 최신 Redis 버전은 클라이언트와의 연결을 닫지 않습니다. 자세한 내용은 redis.conf의 timeout 구성을 참고하세요.
CLIENT 명령 (The CLIENT Command)
예를 들어 CLIENT LIST 명령을 사용해 서버에 연결된 모든 클라이언트를 확인할 수 있습니다.
$ redis-cli CLIENT LIST
addr=127.0.0.1:52555 fd=5 name= age=855 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=client
addr=127.0.0.1:52787 fd=6 name= age=6 idle=5 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping
위 예시에서 두 클라이언트가 Redis 서버에 연결되어 있습니다. 반환된 데이터 중 일부가 의미하는 바를 살펴봅시다.
- addr: 클라이언트 주소, 즉 Redis에 연결할 때 사용한 클라이언트 IP와 원격 포트 번호.
- fd: 클라이언트 소켓 파일 디스크립터 번호.
- name:
CLIENT SETNAME으로 설정한 클라이언트 이름. - age: 연결이 유지된 시간(초).
- idle: 연결이 유휴 상태인 시간(초).
- flags: 클라이언트 유형(N은 일반 클라이언트, 전체 플래그 목록 참고).
- omem: 클라이언트가 출력 버퍼에 사용하는 메모리 크기.
- cmd: 마지막으로 실행된 명령.
전체 필드 목록과 목적은 CLIENT LIST 문서를 참고하세요.
클라이언트 목록을 얻으면 CLIENT KILL 명령으로 클라이언트 주소를 인자로 지정해 연결을 닫을 수 있습니다.
CLIENT SETNAME과 CLIENT GETNAME 명령은 연결 이름을 설정·조회하는 데 사용합니다. Redis 4.0부터 클라이언트 이름은 SLOWLOG 출력에도 표시되어, 지연 문제를 일으키는 클라이언트를 식별하는 데 도움이 됩니다.
TCP keepalive
버전 3.2부터 Redis는 기본적으로 TCP keepalive(SO_KEEPALIVE 소켓 옵션)를 활성화하며, 약 300초로 설정됩니다. 이 옵션은 연결되어 있는 것처럼 보여도 도달할 수 없는 죽은 피어(클라이언트)를 감지하는 데 유용합니다. 또한 클라이언트와 서버 사이에 연결을 열어두기 위해 트래픽을 요구하는 네트워크 장비가 있다면, 이 옵션이 예상치 못한 연결 닫힘 이벤트를 방지해 줍니다.
더 알아보기 (Learn more)
CLIENT LIST— 연결된 전체 클라이언트 목록과 각 필드의 의미CLIENT KILL— 특정 클라이언트 연결 닫기CLIENT SETNAME/CLIENT GETNAME— 연결 이름 설정·조회CLIENT NO-EVICT— 특정 연결을 클라이언트 퇴출에서 제외CONFIG SET— 런타임에maxclients·maxmemory-clients등 구성 변경