リアルタイム通信 — WebSocket・gRPC ストリーミング・WebRTC
gRPC の期限・キャンセル・フロー制御・keepalive
한국어 원문으로 표시합니다.
한 줄 요약
기한은 사슬을 따라 줄어들며 넘겨야 하고, 취소는 직접 전해야 하며, 흐름 제어는 우회하지 말아야 하고, keepalive·재시도·종료는 양쪽의 규칙이 맞아야 합니다.
왜 이게 필요했나
실시간 서비스의 호출은 사슬입니다. 브라우저 → 게이트웨이 → 음성 인식 → 언어 모델 → 음성 합성처럼 이어지고, 사슬의 어느 한 곳이 규칙을 끊으면 증상은 엉뚱한 곳에서 나타납니다. 앞에서 사용자가 포기한 요청을 뒤에서 끝까지 처리하고, 느린 소비자 하나 때문에 생산자 메모리가 차고, 조용한 스트림이 중간 장비에 잘리고, 재시도가 결과를 두 번 만들고, 배포할 때마다 스트림이 끊깁니다.
어떻게 동작하나
기한 전파. 앞 서비스가 0.6초 기한으로 받은 호출을 뒤로 넘길 때, 뒤에는 남은 시간만 줘야 합니다. Go 와 Java 는 들어온 문맥을 그대로 넘기면 기한이 따라가지만, 파이썬에서는 context.time_remaining() 을 뒤 호출의 timeout 으로 직접 넘겨야 합니다. 기한이 없는 호출에서 이 값은 아주 큰 수가 되므로 그대로 넘기지 말고 기한 없이 부릅니다.
취소 전파. 취소 안내 가 설명하듯 클라이언트가 호출을 취소하면 서버 쪽 문맥이 비활성이 됩니다. 그런데 그 서버가 뒤 서비스를 막혀 기다리는 중이라면 뒤 호출은 취소를 모릅니다. 파이썬에서는 뒤 호출을 future 로 걸고 context.add_callback 으로 자기 호출이 끝날 때 그 future 를 취소하게 해야 사슬이 함께 멈춥니다.
흐름 제어. 흐름 제어 안내 에 따르면 gRPC 는 HTTP/2 흐름 제어로 받는 쪽이 감당할 만큼만 보내게 합니다. 파이썬 서버의 스트리밍 제너레이터는 창이 허락할 때만 다음 값을 꺼내므로, 소비자가 멈추면 생산도 멈춥니다. 생산 스레드를 따로 두고 제한 없는 큐에 미리 채우면 이 장치를 우회해 소비자 몫이 서버 메모리에 쌓입니다. 실습에서는 64KiB 메시지 3,000개를 요청하고 하나만 읽은 뒤 멈췄을 때 이 차이가 수 MB 와 수백 MB 로 갈립니다.
keepalive. keepalive 안내 의 클라이언트 설정은 ping 간격(grpc.keepalive_time_ms), 답을 기다릴 시간(grpc.keepalive_timeout_ms), 호출이 없을 때도 ping 할지(grpc.keepalive_permit_without_calls) 입니다. 서버는 너무 잦은 ping 을 거절할 권리가 있습니다. 서버가 허용하는 최소 간격(grpc.http2.min_recv_ping_interval_without_data_ms)보다 자주 ping 하면 GOAWAY 에 ENHANCE_YOUR_CALM 오류 코드와 too_many_pings 를 담아 연결을 끊습니다. 그래서 간격은 중간 장비의 유휴 한도보다 짧고 서버가 허용하는 간격보다 길어야 합니다. 둘은 서로 다른 팀이 정하므로 반드시 맞춰 봐야 합니다.
재시도. retry 설계 문서(gRFC A6) 의 재시도는 서비스 설정(service config)의 retryPolicy 로 선언합니다 — 최대 시도 수, 첫 대기와 최대 대기, 배수, 재시도할 상태 코드 목록. 중요한 제약은 확정(commit) 입니다. 서버의 응답 머리나 첫 메시지를 받으면 호출이 확정되고, 그 뒤의 실패는 같은 상태 코드라도 재시도하지 않습니다. 스트림을 애플리케이션에서 처음부터 다시 부르면 이미 받은 메시지를 두 번 받습니다. INVALID_ARGUMENT 처럼 다시 해도 같은 결과가 나올 오류는 목록에 넣지 않습니다.
우아한 종료. 쿠버네티스는 파드를 내릴 때 SIGTERM 을 보내고 terminationGracePeriodSeconds(기본 30초) 뒤에 SIGKILL 을 보냅니다. 처리기가 없으면 파이썬 프로세스는 SIGTERM 에 곧바로 죽고, 진행 중이던 스트림은 모두 UNAVAILABLE 로 끊깁니다. server.stop(grace) 는 새 호출을 거절하고 진행 중인 호출에 유예를 준 뒤 끝냅니다.
현장에서 만나는 모습
gRPC 연결은 오래 살기 때문에 L4 로드밸런서 뒤에서는 연결이 처음 붙은 서버에 계속 머뭅니다. 서버를 늘려도 새 서버에는 부하가 가지 않습니다. 서버가 연결에 최대 수명(grpc.max_connection_age_ms)을 두어 주기적으로 GOAWAY 를 보내면 클라이언트가 다시 붙으며 부하가 퍼집니다. 이것도 keepalive 와 같은 종류의 "연결 수명 규칙" 입니다.
다음 실습에서 할 것
기한과 취소를 뒤 서비스로 넘기는 중계 서버, 흐름 제어를 지키는 스트리밍 서버, keepalive 와 재시도 정책을 선언한 채널, SIGTERM 에 우아하게 내려가는 서버를 만듭니다. 판정은 기준 서버가 받은 기한·호출 수·마친 단계 수, 그리고 여러분 서버의 메모리로 합니다.