Post

gRPC와 HTTP

gRPC는 어떻게 생겨나게 되었나

gRPC와 HTTP

gRPC 등장 배경

1. 기존 HTTP/REST의 한계

과거 대부분의 웹 서비스는 HTTP 1.1을 기반으로 JSON을 주고받는 REST API 방식으로 통신했다. REST는 사용하기 쉽고 범용적으로 활용되었지만, 다음과 같은 한계가 존재했다.

텍스트 기반의 JSON 직렬화 문제

  • REST는 주로 JSON을 데이터 포맷으로 사용한다.
  • JSON은 사람이 읽기 쉬운 장점이 있지만, 바이너리 포맷이 아니기 때문에 크기가 크고 직렬화/역직렬화 속도가 느리다.
  • 특히, 대량의 데이터를 처리하는 경우 네트워크 대역폭을 많이 차지하고 성능이 저하된다.

HTTP 1.1의 단점

  • HTTP 1.1은 기본적으로 요청-응답(Request-Response) 방식이다.
  • 다수의 요청을 보낼 경우, 새로운 TCP 연결을 생성해야 해서 지연(latency)이 증가한다.
  • Keep-Alive를 사용하여 연결을 유지할 수 있지만, 근본적인 해결책이 아니다.
  • 다중 요청을 병렬로 처리하는 멀티플렉싱(multiplexing) 기능이 없어 네트워크 활용이 비효율적이다.

실시간 데이터 스트리밍 부족

  • REST는 기본적으로 단방향 요청-응답 방식이므로, 서버가 클라이언트에게 데이터를 푸시(push) 하는 기능이 부족하다.
  • 실시간 데이터가 필요한 애플리케이션(예: 채팅, IoT, 주식 거래)에서는 WebSocket이나 Long Polling과 같은 추가적인 기술이 필요했다.

2. 마이크로서비스 아키텍처의 등장

과거에는 모놀리식 아키텍처(Monolithic Architecture) 가 주류였다. 즉, 하나의 거대한 애플리케이션이 모든 기능을 담당하는 구조였다.

그러나, 대규모 서비스(Google, Netflix, Amazon 등)에서는 모놀리식 아키텍처의 한계를 느끼고 마이크로서비스 아키텍처(Microservices Architecture) 를 도입했다.

마이크로서비스에서의 문제점

  • 마이크로서비스는 여러 개의 독립적인 서비스로 구성되며, 이들 간의 통신이 필수적이다.
  • 기존 REST API를 사용하면 서비스 간 오버헤드가 증가하고, 대량의 네트워크 요청을 처리하기 어렵다.
  • 언어가 다른 서비스들(Python, Go, Java 등) 간의 통신을 효율적으로 지원할 방법이 필요했다.

3. Google의 내부 RPC 시스템

Google은 오래전부터 RPC(Remote Procedure Call, 원격 프로시저 호출) 를 사용하여 내부 서비스 간 통신을 처리했다.
특히, Google의 내부 RPC 프레임워크인 Stubby는 고성능, 낮은 지연, 안정성을 갖춘 시스템이었다.

그러나 Stubby는 Google 내부 전용 프레임워크였기 때문에 외부에서 사용할 수 없었다.
따라서, Google은 Stubby를 기반으로 오픈소스화한 gRPC를 개발하여 공개했다.


4. gRPC의 등장

2015년, Google은 gRPC(gRPC Remote Procedure Call)를 발표했다.
gRPC는 Stubby의 핵심 기능을 유지하면서, 외부 개발자도 사용할 수 있도록 만든 오픈소스 RPC 프레임워크이다.

gRPC의 주요 특징

  1. HTTP/2 기반
    • HTTP 1.1보다 더 효율적인 네트워크 전송 가능
    • 멀티플렉싱 지원(여러 요청을 하나의 연결에서 동시에 처리)
    • 헤더 압축으로 네트워크 트래픽 감소
  2. Protocol Buffers(proto) 사용
    • JSON보다 더 빠르고 크기가 작은 바이너리 직렬화 방식
    • 자동 코드 생성 지원 → 개발 생산성 향상
    • 다양한 프로그래밍 언어 지원 (Python, Go, Java, C++, etc.)
  3. 스트리밍 지원
    • 클라이언트 → 서버 (Client Streaming)
    • 서버 → 클라이언트 (Server Streaming)
    • 양방향 스트리밍 (Bidirectional Streaming)
  4. 마이크로서비스 친화적
    • 경량, 고성능, 다중 언어 지원
    • 서비스 간 통신 비용 절감
    • 인증/보안 기능(TLS 지원)

즉, gRPC는 REST API의 단점을 극복하고, 마이크로서비스 시대에 적합한 고성능 RPC 솔루션으로 등장한 것이다.

gRPC vs HTTP(REST) 비교 및 Python 예제 코드

gRPC와 HTTP(REST)의 차이점을 비교하고, 각각의 예제 코드를 Python으로 작성해보겠다.


gRPC vs HTTP(REST) 비교

항목gRPCHTTP(REST)
프로토콜HTTP/2 기반HTTP 1.1 기반
데이터 직렬화Protocol Buffers(바이너리)JSON(텍스트)
성능빠름(바이너리 데이터, 압축, 멀티플렉싱)상대적으로 느림(텍스트 기반, 큰 데이터)
스트리밍 지원양방향 스트리밍 가능일반적으로 요청-응답 방식
언어 지원다양한 언어 자동 코드 생성 지원언어에 따라 직접 구현 필요
주요 사용 사례마이크로서비스, 고성능 API, 실시간 데이터일반적인 웹 API, 브라우저 기반 서비스

Python 예제 코드: gRPC vs HTTP(REST)

gRPC 예제 코드

gRPC를 사용하여 간단한 “Hello World” 서비스를 만든다.

gRPC 서버 코드 (grpc_server.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import grpc
from concurrent import futures
import time

# gRPC 관련 프로토콜 버퍼 생성 코드
import hello_pb2
import hello_pb2_grpc

# gRPC 서비스 정의
class HelloService(hello_pb2_grpc.HelloServiceServicer):
    def SayHello(self, request, context):
        return hello_pb2.HelloReply(message=f"Hello, {request.name}!")

# 서버 실행
def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    hello_pb2_grpc.add_HelloServiceServicer_to_server(HelloService(), server)
    server.add_insecure_port('[::]:50051')
    server.start()
    print("gRPC Server started on port 50051...")
    try:
        while True:
            time.sleep(86400)  # 1일 동안 유지
    except KeyboardInterrupt:
        server.stop(0)

if __name__ == '__main__':
    serve()

2️⃣ gRPC 클라이언트 코드 (grpc_client.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import grpc
import hello_pb2
import hello_pb2_grpc

def run():
    # gRPC 서버에 연결
    channel = grpc.insecure_channel('localhost:50051')
    stub = hello_pb2_grpc.HelloServiceStub(channel)

    # 요청 전송
    response = stub.SayHello(hello_pb2.HelloRequest(name="Alice"))
    print("gRPC Response:", response.message)

if __name__ == '__main__':
    run()

Protocol Buffers 파일 (hello.proto)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
syntax = "proto3";

package hello;

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

위 파일을 컴파일하여 hello_pb2.pyhello_pb2_grpc.py를 생성해야 한다.
실행 방법:

1
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. hello.proto

HTTP(REST) 예제 코드

Flask를 이용하여 동일한 “Hello World” 기능을 제공하는 HTTP(REST) API를 만든다.

HTTP 서버 코드 (rest_server.py)

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/hello', methods=['GET'])
def say_hello():
    name = request.args.get('name', 'World')
    return jsonify({"message": f"Hello, {name}!"})

if __name__ == '__main__':
    app.run(port=5000)

2️⃣ HTTP 클라이언트 코드 (rest_client.py)

1
2
3
4
5
6
7
8
import requests

url = "http://localhost:5000/hello"
params = {"name": "Alice"}

response = requests.get(url, params=params)

print("HTTP Response:", response.json()["message"])

비교 분석 (gRPC vs HTTP)

성능 비교

  • gRPC는 Protocol Buffers를 사용하여 바이너리 직렬화를 수행하므로, 데이터 크기가 작고 전송 속도가 빠르다.
  • HTTP(REST)는 JSON을 사용하여 직렬화를 수행하므로, 데이터 크기가 크고 처리 속도가 상대적으로 느리다.

코드 자동 생성

  • gRPC는 .proto 파일을 기반으로 자동으로 서버/클라이언트 코드를 생성할 수 있어 유지보수성이 높다.
  • HTTP(REST)는 개발자가 직접 API 요청과 응답을 처리해야 한다.

스트리밍 지원

  • gRPC는 클라이언트-서버 간 양방향 스트리밍을 지원하여 실시간 데이터 처리가 가능하다.
  • HTTP(REST)는 기본적으로 요청-응답 방식이며, 실시간 처리가 어려운 구조이다.

그래서 결론

선택 기준추천 기술
고성능, 저지연 API가 필요할 때✅ gRPC
실시간 스트리밍 데이터가 필요할 때✅ gRPC
기존 웹 서비스와의 호환성이 중요할 때✅ HTTP(REST)
브라우저 및 일반 클라이언트 지원이 필요할 때✅ HTTP(REST)
  • gRPC는 성능과 효율성이 중요한 시스템에 적합하다. (예: 마이크로서비스, 대규모 데이터 처리)
  • HTTP(REST)는 범용적이고, 브라우저에서 사용하기 쉬운 장점이 있다. (예: 웹 애플리케이션 API)
This post is licensed under CC BY 4.0 by the author.