gRPC와 HTTP
gRPC는 어떻게 생겨나게 되었나
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의 주요 특징
- HTTP/2 기반
- HTTP 1.1보다 더 효율적인 네트워크 전송 가능
- 멀티플렉싱 지원(여러 요청을 하나의 연결에서 동시에 처리)
- 헤더 압축으로 네트워크 트래픽 감소
- Protocol Buffers(proto) 사용
- JSON보다 더 빠르고 크기가 작은 바이너리 직렬화 방식
- 자동 코드 생성 지원 → 개발 생산성 향상
- 다양한 프로그래밍 언어 지원 (Python, Go, Java, C++, etc.)
- 스트리밍 지원
- 클라이언트 → 서버 (Client Streaming)
- 서버 → 클라이언트 (Server Streaming)
- 양방향 스트리밍 (Bidirectional Streaming)
- 마이크로서비스 친화적
- 경량, 고성능, 다중 언어 지원
- 서비스 간 통신 비용 절감
- 인증/보안 기능(TLS 지원)
즉, gRPC는 REST API의 단점을 극복하고, 마이크로서비스 시대에 적합한 고성능 RPC 솔루션으로 등장한 것이다.
gRPC vs HTTP(REST) 비교 및 Python 예제 코드
gRPC와 HTTP(REST)의 차이점을 비교하고, 각각의 예제 코드를 Python으로 작성해보겠다.
gRPC vs HTTP(REST) 비교
항목 | gRPC | HTTP(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.py
및hello_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)