서비스 하나에 대해서 REST 서버와 gRPC 서버를 따로 운영하면서 재밌지만 다소 버겁다고 생각하고 있었습니다. 그러던중 GraphQL까지 추가되었지요. 내부 비지니스 로직은 통합했더라도 여러 프로토콜을 운영하는건 공부할 내용도 많고 어떤일이 발생할지 불명확하여 두려운 일입니다.
REST, gRPC, GraphQL 이 셋을 하나로 혹은 둘로만 줄일 수 있어도 참 좋겠다고 생각하고 찾아보던중 gRPC Gateway 라는 개념을 발견했고 이를 공부해보았습니다. 과연 현실적으로 개발 공수를 줄일 수 있을지, 그리고 운영하면서 추가적인 이슈를 만들어내지는 않을지 알아보았습니다.
gRPC gateway
개념
gRPC Gateway는 gRPC 서버를 HTTP/JSON API처럼 사용할 수 있게 중간에서 변환해주는 서버입니다.
클라이언트가 REST API처럼 HTTP 요청 (GET, POST 등)을 보내면 gRPC-Gateway가 이 요청을 받아서 내부적으로 gRPC 호출로 변환하고 최종적으로 gRPC 서버에 요청을 전달합니다.
즉, gRPC 서버를 REST API처럼 외부에 노출할 수 있게 해주는 '다리(리버스 프록시 서버)' 역할을 합니다.
구성
위의 개념에서도 이야기했지만 gRPC gateway는 리버스 프록시 서버를 만들어주는게 핵심입니다.
구현
준비사항
- 기본적인 gRPC 구성을 위한 지식이 필요합니다.
- gRPC gateway의 핵심인 reverse proxy는 go언어로만 구현이 됩니다. 따라서 해당 머신에 go를 설치해야합니다.
설치와 실행
기본적인 gRPC 구성에 대한 지식과 go 튜토리얼 정도를 맞췄다면 github readme 에 있는 설치 방법을 통해 gRPC gateway 구성을 위한 go 패키지를 설치하고, 사용 방법을 통해 직접 테스트해볼 수 있을 겁니다.
만약 이러한 작업을 직접하기 귀찮다면 이 모든것을 shell script 로 만들어둔 프로젝트를 이용할 수 있습니다. 간단하게 한번 해보려는거라면 개인적으로 shell script를 이용하시는걸 추천드립니다.
gRPC gateway에 대한 의문점
HTTP/2 에 따른 성능상 이점을 포기하는 걸까?
이전 글에서도 다뤘지만, gRPC는 HTTP/2에 기반하기 때문에 멀티플렉싱과 헤더압축를 활용할 수 있고, 일반적으로 HTTP/1.x 에 기반한 REST 보다 성능상 이득이 있다고 하였습니다. 물론 proto buffer를 사용함으로써 얻는 더 작은 Payload 사이즈에 따른 이득도 있지만 gRPC gateway를 이용하면 순수 gRPC를 썼을 때보다 성능상 불이익이 예상되는 것도 사실입니다.
JSON <-> Binary 변화에 따른 오버헤드가 존재하진 않을까?
gRPC Stub 과 gRPC Server 가 통신할때는 이진화된 데이터를 주고 받기 때문에 이진화된 데이터를 처리하는데 따른 오버헤드를 크게 걱정하지 않았습니다. 하지만 gRPC gateway를 적용하면 조금 달라집니다. 최초 JSON 요청이 넘어오고 그 이진화 한 다음, 다시 이진화된 데이터는 JSON 으로 바꿔야합니다.
도표화 하자면 아래와 같습니다:


이러한 과정에서 오버헤드가 존재한다면 역시 순수 gRPC 를 쓰는것보다도 성능상 이점을 찾기 어려울 것이며, 심지어 순수 REST 보다 성능이 떨어지는게 아닐까? 하는 생각이 듭니다. 이상의 두 의문점을 해소하기 위해 벤치마크 테스트를 시행했습니다.
gRPC 러닝커브 * google.api 러닝커브를 줄일만한 방법이 있을까?
성능과는 다른 관점에서의 의문이 들기도 했습니다. SpringBoot, Django, FastAPI 등 Web Server Framework 들은 웹 표준에 맞춰서 서버를 개발할 수 있는 많은 편의 기능을 제공해주고 있습니다. 덕분에 RESTful API 등을 개발하는 백엔드 개발자 입장에서는 생산성이 많이 올라갔지요, 하지만 지금의 protoc를 통해 코드들이 이러한 편의 기능을 잘 제공해주는지는 의문입니다.
AIP 스펙문서를 보면 gRPC를 RESTful하게 제공하기 위해 proto 파일을 작성하는 순간부터 지켜야할 표준들이 엄청 많아지는것이 보입니다. gRPC 자체도 러닝커브가 존재하는데 googe.api.* 의 proto 를 어떻게 사용해야하는지를 익히는 것도 개발자 입장에서는 부담으로 작용합니다.
아직은 Web Server Framework 수준으로 편하게 개발할 수 있는 환경이 갖춰지지 않은 것으로 보입니다.
벤치마크
그렇다면 얼마나 성능을 희생하는건지 벤치마크 테스트를 통해 확인할 수 있을 것입니다. 순수 gRPC에 비해서 gRPC gateway는 성능을 얼마나 희생하는 걸까요? 기존에 벤치마크를 진행했던 히스토리가 있기 때문에 동일한 조건을 유지하기 위해 gRPC server 자체는 Python으로 구성하고, go를 이용해 gRPC gateway를 만들어서 진행했습니다. (github 에서 코드 확인 가능)
평소에 사용하는 Macbook Air 환경에서 실험하기 때문에 완벽히 통제된 결과를 얻기는 힘듭니다. 따라서 이전의 테스트 결과를 그대로 사용하지 않았고 벤치마크 테스트 스크립트는 다시 시행해서 새로운 결과를 얻어냈습니다. 이러한 이유로 이전 히스토리와는 세부적인 숫자가 다를 수 있습니다.
벤치마크 결과를 그래프로 정리하겠습니다.
| 평균 지연시간(ms) | Requests/sec | 가장느린 응답 | 가장 빠른 응답 | 지연시간 중앙값 | 99% 지연시간 | |
| gRPC aio | 13.02 | 7600.1 | 21.09 | 0.32 | 12.8 | 19.5 |
| gRPC | 15.16 | 6510.8 | 42.38 | 0.75 | 14.6 | 23.9 |
| REST | 20.8 | 4241.8 | 567.3 | 0.5 | 20.4 | 39.8 |
| gRPC gateway | 24.5 | 3991.1 | 113 | 0.7 | 23.5 | 42.2 |
| gRPC gateway aio | 29.3 | 3378.8 | 77.4 | 1.4 | 28.3 | 49.2 |
예상한 대로, gRPC Gateway를 이용한 HTTP 요청은 worker 2개를 사용한 Starlette 기반 REST 서버에 비해 성능 면에서 다소 부족한 모습을 보였습니다.
특히 이번 테스트는 localhost 환경에서 진행되었기 때문에, HTTP/1.1과 HTTP/2 프로토콜 차이보다는, 이진 데이터(Protobuf)를 JSON으로 변환하는 과정에서 발생하는 오버헤드가 추가 지연시간의 주요 원인으로 작용했을 것으로 추정됩니다.
예상에 부합하는 결과가 나왔다는 것과는 별개로 2가지 흥미로운 점이 있습니다.
첫번째는 REST 와 gRPC gateway의 Tail Case를 비교하면 REST 가 가장 안좋은 모습을 보였으며, 여기서도 gRPC aio 가 가장 안정적인 결과를 보여였다는 것입니다.
또 하나는 순수 gRPC 서버에서는 grpc.aio를 이용해 asyncio 기반 비동기 처리를 적극 적용할 경우 가장 뛰어난 성능을 보였지만, gRPC Gateway 환경에서는 오히려 grpc.aio를 사용한 서버가 성능 저하를 일으킨다는 사실입니다.
Tail Case 관리 측면에서 Python의 asyncio 를 사용하는 것이 유리한 이유와 gRPC Gateway에서 grpc.aio 가 더 느린 이유를 알아보는 것도 흥미로운 주제가 될 것 같습니다.
gRPC gateway 활용 사례
etcd
유명한 분산 Key-Value 저장소중 하나인 etcd는 공식문서에서 gRPC gateway를 사용중임을 밝히고 있습니다. 특히 etcd 는 K8s환경에서 클러스터 상태 저장 (Pod, Service, ConfigMap, Secret 등)에서 활용중이기 때문에 백엔드 개발자라면 생각지도 못한 순간에 gRPC gateway를 간접적으로 이용중이었던 셈입니다.
동일한 문서에서 etcd는 gRPC gateway를 도입한 이유에 대해서 밝히고 있습니다.
etcd v3는 클러스터 내부 통신을 gRPC 프로토콜로 처리합니다. Go 언어용 gRPC 클라이언트와 etcdctl CLI를 기본 제공하며,
gRPC를 직접 사용할 수 없는 환경을 위해 HTTP/JSON 요청을 gRPC 메시지로 변환하는 RESTful 프록시(gRPC Gateway) 도 함께 제공합니다.
즉, 더 많은 클라이언트가 이용할 수 있는 범용성 있는 솔루션을 만들기 위해 gRPC gateway를 사용할 수 있는 셈입니다. 이런 경우 gRPC gateway로 인한 성능 저하는 일부 클라이언트들에게 나타나는 문제이지, 정상적으로 etcdctl 을 이용하는 클라이언트들은 별도 성능 저하를 경험하지 않을 것입니다.
gRPC Gateway 활용에 대한 중간 결론
MSA 환경에서 특정 서비스가 RESTful API와 gRPC 를 모두 제공하고 있기 때문에 이를 통합하기 위한 방법으로 gRPC gateway에 대해서 알아봤습니다. 그리고 지금까지 알아본 결론은 etcd 처럼 특정 사용자의 행위를 정확히 규정하는 솔루션의 경우 서비스 제공 범위를 넓히기 위해 gRPC Gateway는 유용하게 사용될 수 있겠지만 일반 유저가 직접 사용하는 프로덕트 서버의 경우 섣불리 gRPC gateway를 도입하기는 어렵다는 것입니다.
아직까지는 일반 유적 직접 사용하는 프로덕트는 HTTP/1.1 웹 기반인 경우가 많으며 이런 경우에는 gRPC Gateway도입이 오히려 성능 저하를 불러일으킬 것이며 또한 대고객 서비스의 업데이트가 잦다고 할때 아직까지는 gRPC gateway를 구성하는 도구가 제공하는 생산성이 Web Server Framework 가 제공하는 생산성보다 떨어지기 때문입니다.
참고
- gRPC gateway: https://grpc-ecosystem.github.io/grpc-gateway/
- HTTP and gRPC Transcoding Spec: https://google.aip.dev/127
- etch gRPC gateway: https://etcd.io/docs/v3.5/dev-guide/api_grpc_gateway/
'탐구 생활 > gRPC&Python' 카테고리의 다른 글
| Python gRPC (3) - gRPC가 빠른 이유와 벤치마크 (1) | 2025.04.13 |
|---|---|
| Python gRPC (2) - 기본 구조와 python 구현 (0) | 2025.03.08 |
| Python gRPC (1) - 왜 gRPC 를 선택했나 (0) | 2025.03.08 |