공부하고 기록하는, 경제학과 출신 개발자의 노트

학습일지/네트워크

KubeCon 2018 - Using gRPC for long-lived and Streaming RPCs

inspirit941 2022. 9. 12. 11:40
반응형

Using gPRC for long-lived and Streaming RPCs

발표자: Eric Anderson - gRPC Java 팀 tech lead. Google에서 근무

발표자료: https://kccna18.sched.com/event/GrWo


발표영상: https://youtu.be/Naonb2XD_2Q

 

스크린샷 2022-09-08 오후 8 50 28

Long-Lived gRPC란?

  • RPCs that last minutes / hours/ days.
  • Long polling (Hanging Get). 예컨대 서버에서 특정 이벤트가 발생할 때까지 대기하는 로직.
    • 강연에서는 Notification / Watches를 예시로 들었음.
    • Hanging Get : 서버에서 요청에 응답할 데이터가 없는 경우, 해당 데이터를 사용할 수 있게 될 때까지 요청을 유지하는 것

 

스크린샷 2022-09-08 오후 8 53 17

Issues

  • Load Balancing
    • 기본적으로 LB는 개별 RPC 기준으로 동작함.
    • RPC를 호출하는 시점에서 load balancing이 동작하는데, 그 RPC가 Long-lived인 경우 - Load balancing Decision도 지속된다.
    • long-lived RPC가 누적될 경우, 서버에서 new RPC Connection을 더 이상 생성할 수 없어서 통신이 안 될 수 있음. (TCP Connection fails)
    • MAX_CONNECTION_AGE 설정으로 시간초과된 connection을 shut down할 수는 있으나, 이 설정 자체는 kill connection이 아니라 connection에 wait time limit을 설정하는 것.
  • Network Failure.
    • 네트워크 이슈가 생기면 Long-lived RPC 요청은 실패함. 서버에 문제가 있어도 통신 실패할 수 있음.
    • TCP Connection은 문제가 생기면 그냥 끊어짐. Migration 기능 같은 거 없음
    • Network 쪽 이슈는 감지될 때까지 시간이 필요함.
  • 네트워크 이슈로 latency가 길어질 수 있다 보니, Unary Deadline을 실제 기댓값보다 더 크게 잡아야 함.

스크린샷 2022-09-09 오후 12 05 49

Load balancing에서의 Improvement 방법

  1. server에서 RPC close call을 실행한다. new connection을 생성하면 load balancer가 다시 동작할 수 있음.
    1. MAX_CONNECTION_AGE_GRACE 옵션을 사용해서 how long you let this RPCs last를 설정할 수 있음.
    2. MAX_CONNECTION_AGE 값이 초과되면, MAX_CONNECTION_AGE_GRACE 에서 설정한 만큼의 시간이 추가로 부여된 후 connection이 종료됨. 예컨대 5 minutes로 설정하면, MAX_CONNECTION_AGE 시간이 지난 뒤 5분의 추가시간이 주어지는 것.
    3. 다른 RPC Call 종료 / 취소 방법이 있지만, 일종의 backup system으로 적용할 수 있음.
  2. Network Failure : client side의 keep-alive 옵션을 사용한다. RPC connection call 했을 때 응답이 없으면 자동으로 connection kill + notification을 제공함
  3. Wait-for-ready 옵션
    1. RPC 통신을 시도했을 때 no connectivity -> fail 발생할 경우, retrying the connect until the RPC connection re-established.

Streaming RPC

스크린샷 2022-09-09 오후 12 20 14

 

  • unary는 응답받는 메시지 개수가 1개이지만, Streaming은 0-to-Many Messages.
  • 응답으로 오는 메시지 순서는 정렬되어 있음. n번째 메시지를 받았다면, 직전의 메시지는 n-1번째 메시지.

 

스크린샷 2022-09-09 오후 12 23 17

 

유의점: streaming으로 메시지를 보낼 때 Metadata는 headers / trailers에만 존재한다. 중간에 들어오는 Message에서는 metadata를 확인할 수 없음.

 

스크린샷 2022-09-09 오후 12 37 42스크린샷 2022-09-09 오후 12 37 47

 

양쪽이 전부 Stream으로 데이터를 전송할 경우 (Bi-directional)

  • Half duplex: 한 번에 하나의 Client만 서버와 bi-directional streaming 통신이 가능함. client Send -> server Respond.
  • Full duplex
    • TCP와 개념적으로 유사하지만 bytes 대신 messages를 전달함. close 방식은 tcp와 다소 다르지만, 여기서는 다루지 않음
    • client send -> server respond 방식이 아니기 때문에, 'ack' 개념이 없음. 메시지를 수신했다 != 요청에 응답했다.

 

스크린샷 2022-09-09 오후 1 58 29

half-duplex

  • API latency / memory reduction. ex) STT
    • 문장 단위, 혹은 더 작은 chunk 단위의 message로 나눠서 streaming 방식으로 요청 / 응답
    • 서버 입장에서는 모든 문장을 input으로 받을 때까지 대기하는 게 아니므로 latency / memory 단축.
  • Response와 End of call의 구분 가능. unary의 경우 '요청 - 응답' 프로세스가 끝나면 더 이상 뭔가를 전달할 수 없지만, streaming은 계속 데이터를 전달할 수 있음.
  • Flow Control (push-back)
    • 'sender의 데이터 전송속도 > server의 데이터 처리속도' 인 경우, server가 처리할 수 있는 정도로 sender의 전송속도 조절이 가능함.
    • bulk upload download의 경우 유용함.
      • 기존의 chunk 단위로 데이터 전송 -> 업로드 완료 -> 다음 데이터 chunk 전송... 하는 방식에서는 Latency 발생 / bandwidth 낭비 가능성이 높음.
      • streaming 방식으로 message를 전달할 경우 server / network에서 처리 가능한 속도로 전달 가능 + chunk 단위를 세세하게 고려할 필요가 없어짐.

 

스크린샷 2022-09-09 오후 2 09 54

 

streaming으로 전달되는 데이터에서 '순서'에 의미가 있으므로, state 정보를 담은 데이터 전송이 가능함.

  • 한 번 streaming 방식으로 연결 -> 해당 데이터는 전부 동일한 backend Server로 전송.
  • call의 lifecycle이 길어짐 (Request -> response 단위보다 확장됨. 예컨대 transaction). 여러 query를 하나의 Streaming 단위로 보낼 경우 가능함. kill the call == kill the transaction.
  • 서버에서 계속 데이터를 전달해야 할 경우... setup cost 감소
  • state change를 stream message에 포함하는 것도 가능. (watches again...)
    • stateless 방식의 경우 '이전 상태가 무엇인지'를 recovery token에 포함하거나 하는 등 부가작업이 필요함
    • streaming으로 연결된 경우 message에 정보 포함하기만 하면 됨 -> client - server 간 sync 비용이 적게 든다.

 

Issues

스크린샷 2022-09-09 오후 2 18 02

 

단순히 gRPC에서 streaming 기능을 제공하고 있고, 그게 좋아보여서 사용할 거면 반대. 고려해야 할 Complexity는 증가한다. (unary의 경우는 괜찮음)

  • streaming에서 flow control에 사용하는 buffer의 크기는 64KB - 4MB. 작은 데이터를 전송할 거라면 buffer 크기 낭비
  • gRPC의 Flow control 단위는 Point to point. 만약 gRPC 통신 중간에 new proxy가 들어오거나 하면, proxy에서 필요로 하는 buffer가 또 추가될 수 있음.
    • buffer 크기만 MB 단위로 커질 수 있음. (물론 network 세팅과 상태에 따라 실제 buffer 크기는 다를 수 있으나, buffer 단위가 커질 가능성이 높다는 건 염두해야 함)
  • API Implementation에서의 Complexity 증가. (이건 구현에 사용한 언어에 따라 정도가 다를 수 있음)
  • streaming이 framework-Level에서 정의된 retry level로는 제대로 동작하지 않을 수 있음. application-level로 retry 설정을 해줘야 함
    • specialized logic이 추가로 필요할 수 있음.
  • Tracing / Stats 기능이 streaming을 완벽히 지원한다고 보장할 수 없음. muddled / missing 가능성 있음.

 

스크린샷 2022-09-09 오후 2 28 30

half-duplex에서 Flow Control 관련 이슈 발생 시

  • full duplex 사용 권장 + application-level flow control 추가해야 함

tracing / stats 이슈 발생: streaming으로 발생하는 메시지 전체를 하나의 unary처럼 간주해도 괜찮음

  • streaming으로 발생한 5 messages -> 전부 합쳐서 하나의 big unary message처럼 취급하는 것
  • watches처럼 streaming message 전체를 Unary 취급할 수 없는 경우도 있긴 한데.. 대부분의 경우 approximation으로는 나쁘지 않다고 함

full duplex

 

스크린샷 2022-09-09 오후 2 33 02

  • 본질적으로 TCP with Message... Custom protocol 생성 가능.
  • Application-level Flow Control 정의 가능
  • Transaction 처리에 용이함.
    • request로 Query를 계속 전달 -> response로 계속 응답. 이런 flow 전체가 하나의 stream에서 이루어지므로.
    • client가 죽거나 connection이 끊기거나 cancel요청할 경우 요청 전체를 취소할 수 있음 (rollback처럼)
  • Live Reconfiguration 가능. 사용자의 요청을 on-the-fly로 처리 가능 (서버에서 요청을 즉각적으로 반영해서 전달)
  • bulk upload에서 유용함.
    • large bulk upload의 경우 resumption이 필요함.
    • 업로드할 때, Resumption key를 클라이언트가 응답으로 받아서 다음 업로드에 사용해야 함
    • 이 키값을 가지고 있다면, upload RPC가 종료되거나 중단되었을 때 resumption Key를 가지고 new RPC connection을 생성하면 이어서 업로드 가능
    • 읿반적으로 resumption key는 constant한 값으로 쓰이는데, full duplex를 적용할 경우 constant하지 않은 key값을 사용할 수 있음.

 

Issues

스크린샷 2022-09-12 오전 10 07 24

  • half duplex와 마찬가지로 tracing / stats system이 완전하지 않음.
  • infinite data transfer가 가능한 두 개의 streaming -> API 복잡도 증가.
  • Application-Level Retry 로직이 반드시 있어야 함. (custom logic 대응을 위해서는 프레임워크에서 pre-defined된 걸 쓰기도 어려움. 직접 로직을 정의해야 함)
  • Flow Control 과정에서 Deadlock 발생할 수 있음.
  • Full duplex Streaming은 REST 철학에 마땅히 대응되는 게 없음.

 

스크린샷 2022-09-12 오전 10 13 45

자료화면에서 * 표시가 붙은 건 뾰족한 해결방법이 없다는 뜻임. Deal With it.

  • 그래도 Deadlock 이슈는 '반드시, server / client 최소 한 쪽은 receiving 상태여야 한다'는 원칙을 지키면 해결할 수 있다. 이 원칙만 확실하다면 Flow system이 동작하면서 종래에는 괜찮아진다.
  • deadlock이 발생하는 요인 중 하나는 '양쪽 다 데이터 sending만 하고, receiving을 어느 쪽도 하고 있지 않을 때' 이기 때문.

Long-lived Streaming Issues

 

스크린샷 2022-09-12 오전 10 17 45

  • transaction case, all messages are implying a lot of load. 이 경우 load balancing에 cpu / memory 리소스 사용량이 많으므로 조심해야 함
    • 단순히 long-lived RPC의 경우 load balancing에 리소스 사용량이 많진 않음

 

스크린샷 2022-09-12 오전 10 21 28

Long-lived RPC의 해결방안과 크게 다르진 않다고 함

  • RPC가 너무 길어지지 않도록 finish + clean, make another load balancing decision...

Q&A

Max Concurrenct streams 옵션이 구글 api에서는 기본 100으로 설정되어 있는 걸로 알고 있다. 꽤 작은 값인데, multiple stream with lots of clients / one server Application 에서 적정한 값은 얼마쯤이라 생각하는지 궁금하다.

  • max concurrent stream은 http/2 spec으로, 기본적으로는 infinite임. 하지만 proxy에서 보통 infinite보다는 작은 값으로 설정한다.
  • Long-lived concurrenct RPC 세팅할 때는 order of hundreds / thousands (대충 몇백 / 몇천 정도) 적용하면 대략 된다.

Flow Control의 back pressure handling이 자동인지 또는 관리해줘야 하는지

  • 언어마다 다름. API feature. 찾아보는 것을 권장

gRPC wire debugging tool로는 뭘 쓰는지? long-lived streaming의 complex case인 경우

  • wire 레벨에서 보는 거 같진 않고, events 단위로 보는 듯 - which messages are received. http2 레벨에서 해결되는 걸 디버깅하기 위해 http2 전문가가 되어야 할 필요까지는 없는 듯함
  • deadlock의 경우 window size 체크하고 비교하는 등의 작업이 필요할 수는 있어보인다.
  • http2 frames을 확인할 수 있는 debug flag이 있다. 지원하는 언어가 있고 아닌 언어가 있긴 한데... gRPC의 Logging이나 flag 추가해서 frame logging하는 정도. 필요하다면 앞서 언급한 Tracing / stats도 괜찮더라.
반응형