문제상황
서비스 운영 중, 앱에서 실시간 데이터가 갱신이 되어야 하는 상황임에도 불구하고 실시간 정보가 제대로 갱신되지 않는 것 같다는 요구사항이 들어왔다.
해결과정
현재 우리는
Android 에서 gRPC Java 를 통해 server 와 통신 중에 있으며, StreamObserver 로 응답 값을 받아오고 있다.
당시 문제가 되는 rpc 는 Server Stream 이었다.
원인 분석
Q) 서버에서는 응답 값을 정상적으로 전달하였는가?
⇒ YES.
server 측 로그 분석 결과, client 로 정상적으로 응답 값을 전달을 시도한 것으로 확인했다. (하지만 client 에서 메시지를 받지 못하였음)
============근데 전송 당시에 실제 서버에 남은 로그가 존재했던가? 아니면 아무런 이상이 없었던가?
Q) 네트워크 불안정으로 인한 stream 연결이 단절된 것인가?
⇒ 네트워크 문제는 아닌 것 같음
그렇게 생각한 이유
- ConnectivityManager 를 통해 네트워크 상태를 바꿔가며 체크했을 때 특이사항을 발견할 수 없었다.
- wifi, LTE, 등 모두 확인
- 만약, 네트워크 이상으로 인해 stream 연결이 단절되었다면, StreamObserver 의 onError() 가 트리거 되었어야 한다.
Q) 클라이언트에서 channel 생성 후 stream 을 연결한 시점으로부터 일정시간 아무런 message 를 송수신하지 않으면 channel 이 유휴상태로 들어간다?
postman 으로 했을 땐 10분 지나도 유휴상태로 안들어갔었음
proxy 또는 로드 밸런서에 의해 유후상태로 간주되었나? ⇒ 사실 잘 모르겠음..
증상 재현
우선, 증상 재현에 들어갔다.
실시간 데이터가 갱신되어야 하는 상황을 만들어서 데이터가 갱신이 되는지 확인했다.
하지만, 내 로컬 환경에서는 데이터 갱신이 정상적으로 잘 되었다. (재현 실패)
인입된 쪽에게 다시 물어보니, 데이터가 갱신되지 않는 현상이 아주 간헐적으로 발현된다고 전달 받았다.
그러던 중, server stream 을 열어 둔 채로 회의를 갔다 와서 다시 확인해보니 운영팀에서 말했던 현상과 동일한 현상이 재현되었었다. 즉, (1) 네트워크 연결이 끊겨있지 않고, (2) gRPC onError 가 발생하지 않았음에도 불구하고 server 로부터의 응답을 받을 수 없는 상황인 것이다.
최초에 재현한 조건과 현재 조건의 차이점에는 딱 한 가지가 존재했다.
stream 을 열어 둔 채 일정 시간이 지난 상태인 것이다.
본격적으로 증상 재현을 하기 위해 이것 저것 테스트를 진행했다.
클라이언트(Android) 에서 stream 을 열어둔 채 일정 시간(약 10~30분) 방치(여기서 방치라는 것은 stream 을 열어둔 채로 어떠한 메시지도 주고 받지 않은 상태를 말함) 한 후, 메시지를 전달했다.
그런데 동일 현상이 발생하지 않았다..?
이상하다 싶어서 다시 시도 했는데 위에서 말했던 동일한 현상(서버로부터 응답을 받을 수 없는 상황)이 발생했다.
요구사항이 인입된 쪽의 말처럼 정말 간헐적으로 발생하는 것 같다.
(이거 확실하지 않음, 지워야할 듯)여러 번 시도한 끝에 약 10분 정도 stream 을 열어둔 채로 방치했을 경우, 해당 현상이 발생하는 것으로 추측했다.
(이거도 확실하지 않음) (여담) Postman 의 gRPC client 로 테스트했을 때도 동일하게 재현되었다.
현재까지 확인된 사항
client 에서..
- 네트워크는 정상
- stream observer 에서 오류가 발생하지 않았고 rpc 가 종료되지 않았음
- channel 이 닫히지 않았음
- 문제가 발생한 시점, 모든 rpc(server stream rpc 를 포함한 현재 사용 중인 다른 모든 rpc)도 동일하게 메시지를 송수신 할 수 없는 상태였음. (채널이 유휴상태로 바뀐 듯함)
// client 측 로그
channel.isShutdown: false,
channel.isTerminated: false,
Context.current().isCancelled: false,
Context.current().deadline null,
server 에서..
- client 로부터 cancel context 가 전달되지 않음
- deadline 이 만료되지 않음
- (이거 확실한가?) 해당 현상이 발생한 시점에, 클라이언트로 메시지를 보내니 client 와의 연결이 단절되었다는 로그를 확인
// server 측 로그
System.IO.IOException: The request stream was aborted.
---> Microsoft.AspNetCore.Connections.ConnectionAbortedException: The HTTP/2 connection faulted.
---> Microsoft.AspNetCore.Connections.ConnectionResetException: 현재 연결은 원격 호스트에 의해 강제로 끊겼습니다.
---> System.Net.Sockets.SocketException (10054): 현재 연결은 원격 호스트에 의해 강제로 끊겼습니다.
최종 조치
HTTP/2 연결에서 ping 기반의 keepAlive 를 구성하기로 했다. (https://grpc.io/docs/guides/keepalive/)
keepAlive 에 대한 잠깐 설명?
- TCP keepalive 와는 다르며, 이것을 설정헀을 경우 중복 패킷을 보낼 수도 있음
- server 에서는 keepalive 를 구성할 필요x
- client 는 server 와 keepalive 를 구성할 지 협의해야 함
- keepalive 는 주로 deadline 이 긴 rpc 에 대해 구성됨
- keepalive 를 1분 미만으로 구성하는 것은 권장하지 않음
- keepalive 서버 기본 값은 2시간
keepalive 가 유용한 상황
- proxy 또는 로드 밸런서에 의해 유휴 상태로 간주될 수 있는 장기 연결을 통해 데이터를 전송하는 경우
- 네트워크의 신뢰성이 떨어지는 경우(mobile application)
- 장기간 사용하지 않은 상태에서 연결을 사용할 때
문제를 해결하기 위해 client(Android) 에서 Channel 생성 시, 아래 3가지 옵션을 추가하여 채널을 빌드했다.
- keepAliveTime → 새로운 keepalive Ping을 보내기 전에 새 메시지를 기다리는 시간
- keepAliveTimeout → 이 시간 내에 keepalive 응답이 수신되지 않으면 연결을 종료
- keepAliveWithoutCalls → 아직 처리되지 않은 RPC가 없을 때 연결 유지를 수행할지 여부
결론
keepalive 를 설정하여 connection 을 유지시켰고, 실시간 데이터가 정상적으로 응답될 수 있도록 조치하였다.
더 이상 동일한 현상이 발생하지 않았다.
마무리
현재 시스템에서는 grpc stream 이 연결되면 client 측에서 의도적으로 rpc 를 cancel 하지 않는 이상, 계속해서 연결을 유지시키고 있다.
그 이유는, grpc server 에서는 redis 채널을 구독하고 있고, 구독 중인 채널의 상태가 변경되면 grpc server 에서 client 로 응답 메시지를 전달하는 형태인데, 그 상태가 언제 변경될 지 몰라서 계속 connection 을 유지시키고 있었다.
이에 따른 부하는 없는지, gRPC 기술이 우리 제품에 적합한 지, 제대로 검토를 해야 할 것 같다.
느낀점
로그를 남기는 중요성과 모니터링의 중요성에 대해 알게 되었다.
로그를 체계적으로 남겼고 모니터링을 확실하게 할 수 있었다면 이번 상황이 발생하더라도 발빠르게 대처할 수 있었지 않았을까 라는 생각이 들었다.
같은 상황이 발생하는 것을 줄이기 위해 ‘로그를 언제 어떤 방식으로 남기는 것이 좋을 것인가’ 에 대해서 고민을 해야겠다는 생각이 들었다.
참고
- keepalive 관련 문서
- 성능 신경써서 gRPC 재설계 하라는 문서
- Client disconnect callback?
- gRPC client does not unblock when server becomes unreachable · Issue #13656 · grpc/grpc
- https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md
- gRPC Long-lived Streaming - Code The Cloud
- gRPC connection times out after 5 minutes · Issue #76 · onosproject/onos-gui
- How to disable Stream timeout · Issue #557 · grpc/grpc-web
- 비슷한 케이스인듯
- Exception when bi-directional stream get´s closed by client · Issue #1557 · grpc/grpc-dotnet
- Stream reader MoveNext() doesn't comply with gRPC spec, throws exception on underlying TCP/HTTP2 stream abort/close · Issue #1219 · grpc/grpc-dotnet
- .NET6 gRPC fails intermittently
- 이거는 아직 답변도 안달림..
- https://github.com/grpc/grpc-dotnet/issues/1557
- 비슷한 케이스
- https://www.ibm.com/docs/ko/was-liberty/nd?topic=configuration-grpcclient
'Mobile > Android' 카테고리의 다른 글
| [Android] 코드 최적화 proguard-android-optimize 관련 이슈 (0) | 2024.05.25 |
|---|---|
| [Jetpack] Compose 간단 개념 정리 (0) | 2024.05.25 |
| [Jetpack] Navigation 간단 개념 정리 (1) | 2024.05.25 |
| proto file 한글 주석이 깨지는 현상 (0) | 2023.05.19 |
| [해결][Android] Error: tools:replace specified at line:22 for attribute android:appComponentFactory (0) | 2022.11.22 |