ETC

gRPC에 대해

partner_jun 2023. 12. 4. 22:44

최근 여기저기에서 gRPC에 대한 이야기가 들린다. 회사에서 gRPC를 쓰겠다는 말이 있기도 했지만 이런 통신 계열에 재미있는 것이 자주 나와 관심을 가져봤다. 책을 읽고 이런저런 테스트를 해보니 당장 쓰진 않을 것 같지만, 관련 시스템을 한번쯤 만질 일은 있을 것 같다는 생각이 들어 이 글을 남겨둔다. 최소한 순식간에 없어질 정도는 아닐 것 같다는 뜻이다.
 
 

gRPC

gRPC는 구글에서 만든 원격 프로시저 호출(RPC) 시스템이다. CGI부터 이어져 온, 외부 시스템에 값을 전달해주기 위해 사용하는 연결 방식이다. 더 줄이자면 "RESTful API를 대체할 수 있는 무엇이다" 정도로 말할 수 있겠다. 물론 응답을 주고 받을 프로젝트가 웹 프로젝트로 한정되지 않았다는 점 때문에 약간 다르지만, 결국 서버를 띄워 유지해야 하기 때문에 웹 프레임워크 위에 얹는 것이나 다를 바가 없다. gRPC 자체의 데이터 전송 방식이나 데드라인(취소/서킷브레이킹), 로그 등은 실제 구현보다는 유지 보수 기능이라 우선순위를 낮게 가져가도 될 것이다.
 

 
 
먼저 gRPC는 어떤 장단점이 있는지 파악해봤다.

  • vs RESTful
    • protobuf를 이용한 메시지 응답 형태 단일화
      • 단일화라고는 하지만 기본 형태로 작성한 후, 다시 언어별로 컴파일 할 수 있는 개념
    • 스트림이 정규 스펙으로 포함되어 있음
      • 이 부분을 눈여겨볼만 한데, 언어나 플랫폼별로 응답을 처리하는 방식이 달라 연결이 골치아팠던 경험이 많다. 
  • 장점
    • HTTP 헤더/바디 같은 자잘한 것들에 신경 쓸 필요가 없음
    • protobuf를 이용해 메시지-인터페이스를 단일화하여 사용할 수 있음
    • 언어/플랫폼에 자유롭게 스트림 구현이 가능
  • 단점
    • protobuf로 만든 "파일"을 이용하므로 모노레포 혹은 sparse checkout 관리가 요구됨
    • 상황에 따라 protobuf 플러그인이나 옵션 플래그 설정이 필요함 (이것 또한 관리 포인트)
    • HTTP2 이상 환경에서만 지원되므로 인프라 세팅이 필요함
    • server to client (grpc-web)가 현실적으로 불가능 (아래에서 설명)

 

protobuf

protobuf는 gRPC 통신을 위한 일종의 규격서이다. 아주 간단한 형식이라 한번 보면 사용할 수 있고, gRPC와 별개의 프로젝트이니만큼 다양한 용도로 사용이 가능해 보인다. 특히 다양한 언어로 변환이(공식 설명은 컴파일이지만 트랜스파일링과 유사하다) 가능한 점도 장점이다. 하지만 2023년 현재 protobuf는 Typescript를 공식적으로 지원하지 않는다(Node-js로만 지원). 그나마 다행인 것은 플러그인 기능이 있어 TypeScript로의 컴파일이 가능하긴 하다는 것이다.
 
먼저 컴파일된 protobuf는 크게 두 부분으로 나누어서 볼 수 있다.

  • Request / Response 메시지(인터페이스) 선언부
    • 네임스페이스 혹은 인터페이스로 노출되어 타입 참조 용도로 활용이 가능하다.
    • 플러그인/언어에 따라 단순 선언이 아닌 구현이 포함되기도 한다.
  • 통신을 위한 Service/Client 선언부
    • gRPC에서 데이터 송수신에 사용되는 mock 객체 생성에 사용되는 부분이다.
      • 여기서 mock 객체는 테스트를 위해 흔히 사용하는 mock이 아니라, gRPC에서 실제로 요청-응답을 처리하는 부분이다.
      • 사용자는 호출만 하게 되니(구현체는 대상 서버에 있으니) 테스트용으로 사용되는 mock 개념과 비슷하다고 판단하여 이름붙인 것으로 보인다.
    • 언어에 따라 다르지만, 추상 클래스 혹은 인터페이스로 구현되어 있어 이를 상속받아 구현하는 방식이다.

위 설명만으로 충분히 이해할 수 있겠지만, protobuf를 이용한 gRPC 구현 방식은 아래 순서를 따르게 된다.
 

1. protobuf 파일 작성

작성한 Test.proto 파일

 
먼저 proto 확장자를 가지는 protobuf 파일을 작성한다. protobuf 파일에는 message, rpc 등 송수신할 메시지 형태나 호출할 서비스명, 그리고 분류될 패키지 명 등을 작성한다. protobuf의 버전명도 명시하도록 되어 있어 버전업에도 대응이 가능해 보인다.
 

(선택) protobuf 파일 컴파일

node 환경 기준으로 protobuf-loader를 통해 트랜스파일링하지 않고 protobuf 파일 자체를 사용할 수 있다. 하지만 타입 추론이 정상적으로 작동하지 않아 컴파일 하는 것이 좋아 보인다.
 

2. gRPC server 코드 작성

위에서 생성한 파일들을 기반으로 gRPC 서버 코드를 작성하고 기동한다. 위에서 설명했듯, protobuf 파일을 컴파일하면 상속받아 사용할 수 있는 추상 클래스나 인터페이스가 생성된다. 


 

3. gRPC client 코드 작성

 
이제 열려있는 gRPC 서버에 gRPC 클라이언트를 통해 요청, 응답을 받아낼 수 있게 된다. 여기서 눈여겨볼만 한것은 서비스를 열기 위해 주소와 포트를 입력하고, 그 다음 파라미터로 'createInsecure' 메소드를 호출했다는 것이다. 이는 gRPC가 http2를 기반으로 하기 때문인데, http2는 "https" 환경에서만 사용이 가능하니 자연스럽게 인증서 관련 옵션이 필수가 되는 것이다.
 
 
이렇게 정말 작은 코드만으로 외부 시스템에서의 요청에 응답하는 서버가 구성된다. 이 방식은 비교적 최근에 만들어진 웹 프레임워크나 소켓 통신 프레임워크와 아주 유사하다. 단지 이 코드가 웹이 아닌, gRPC 클라이언트로만 호출이 가능하다는 점이 큰 차이다.
 
 

gRPC Typescript 지원 플러그인

위에서 말했듯, gRPC는 공식적으로 Typescript를 지원하지 않는다. 따라서 TypeScript로 사용하려면 아래 플러그인 중 하나를 선택해야 한다. npm에서 검색해보면 더 다양하게 나오기는 하지만, 유명한 것 두개만 뽑아 보았다.
 

아래에서 설명할 코드도 모두 이 Test.proto를 기반으로 한다

 
 
1. ts-protoc-gen
마지막 업데이트가 무려 3년 전임에도 주간 다운로드가 10만이 넘는 플러그인이다.

export namespace TestRequest {
  export type AsObject = {
    id: number,
    value: number,
  }
}

 

객체지향에서 볼법한 set/get 패턴이다

 
위 코드에서 볼 수 있듯, JS 진영의 코드라고 보기에는 어려움이 있다. TestRequest 타입이 네임 스페이스로 선언되어 있을 뿐더러, Request가 클래스로 구성되어 있어 new 키워드로 객체를 만들어 낸 후, 각각의 필드를 set 메소드로 업데이트 해야 한다.
 
 
2. ts-proto
주간 다운로드 20만이 넘는 메이저한 플러그인이다.

export interface TestRequest {
  id: number;
  value: number;
}

 

_m0 등 보기엔 엉망이다

 
이 플러그인은 더 깔끔한 인터페이스로 생성된다. 또, TestRequest가 객체로 생성되어 fromJSON 메소드를 이용해 객체를 바로 Request로 변형할 수 있다.
 
이 두개 외에도 내가 설치해본 플러그인이 몇가지 더 있다. 하지만 대부분이 ts-protoc-gen과 유사하기도 하고, 유의미한 특징이 있는 정도는 아니었다.
 
 

gRPC-Web

이 글을 쓰게 된 가장 큰 이유이다. 난 위 기능들이 궁금하기도 했지만, gRPC가 브라우저에서의 요청도 대체할 수 있으리라 기대했다. 바로 이 grpc-web이라는 프로젝트 때문이다. 이름이나 설명 자체가 브라우저에서 gRPC 서버를 호출한다고 설명되어 있지만 두번째 줄이 더 중요하다는 것을 간과했다.

"via a special proxy"

 
이 프로젝트는 "일반적인" 환경이 아니라, "특별한 프록시" 환경에서만 사용이 가능했던 것이다. 결국 브라우저에서 서버로의 "일반적인" 요청을 대체할 수는 없다는 뜻이다. 왜 그런가 하여 열심히 살펴보니...
 

 
결국 브라우저에 종속적인, 환경을 제어할 수 없는 FE에서 직접적인 사용이 불가능한 것이다. 재미있는 것은 네이티브, 그러니까 직접 컨트롤이 가능한 AndroidIOS에서는 사용이 가능하다는 것이다. 물론 Next.js같이 이미 서버가 준비된 환경이라면 gRPC API와 백엔드를 gRPC-js로 연결하고, FE와는 기존 방식으로 연결하는 것이 가능하다.
다만 그렇게까지 gRPC 백엔드를 사용해야 할지가 의문이다. 네이티브 앱이나 웹 양쪽 모두 REST를 위한 생태계가 구축된 상태이니, BFF 서버를 두고 BFF가 직접 gRPC와 통신하는게 더 효율적이지 않을까 싶다.
 
 

마치며

gRPC는 모노레포 혹은 sparse checkout을 위시로 하는 프로젝트에서 장점을 발휘할 수 있다. 하지만 무리해서 바꿀 정도로 장점이 있다고 하기는 어려워 보인다. 위 기능 외에도 서킷 브레이킹, 로드 밸런싱 등을 큰 장점이라고 자랑하고 있지만, 이는 REST 기반에서도 충분히 가능하다. 기존 REST 기반 환경에서 사용하던 postman과 같은 툴들을 사용할 수 없다는 것도 단점이다. 그리고 확실하진 않은 내용이지만 payload가 큰 경우 열등한 성능을 보인다는 글을 레딧에서 보았다.
하지만 위에서도 언급했듯 지속적인 응답, 그러니까 스트림을 사용해야 하는 상황은 REST로 해결이 쉽지 않다. 구현이 가능하기는 하지만 이를 위해서 준비해야 하는 것도 많고, 플랫폼별 차이를 극복하는 것도 그리 쉽지만은 않다. 특히 대규모 시스템일수록 발생하기 쉬운 인터페이스 불일치는 protobuf로 해결할 수 있다.
gRPC도 세월에 잊혀지는 기술이 될지, 모두가 사용하는 대세가 될지는 모르겠다. 하지만 확실한 것은 gRPC는 특정 상황에 사용할 수 있는 카드가 되리라는 것이다.