개발

[개발] gRPC 기초 - 개념 이해와 Python 서버-클라이언트 실습

weweGH 2026. 5. 18. 09:27
반응형

gRPC 기초
gRPC 기초


[개발] gRPC 기초 - 개념 이해와 Python 서버-클라이언트 실습


들어가며


소프트웨어 서비스는 대부분 여러 개의 서버가 서로 통신하며 동작합니다. 예를 들어 하나의 앱 안에도 사용자 인증을 담당하는 서버, 데이터를 저장하는 서버, 알림을 보내는 서버가 각각 존재할 수 있습니다. 이처럼 서버가 많아질수록 서버 간 통신을 어떤 방식으로 설계하느냐가 중요한 문제입니다.

현재 가장 널리 사용되는 방식은 REST API와 JSON입니다. 직관적이고 사용하기 쉬워 많은 서비스에서 채택하고 있지만, 대규모 시스템에서는 성능이나 유지보수 측면에서 한계가 드러나기도 합니다. 이러한 한계를 보완하기 위해 등장한 방식 중 하나가 gRPC입니다.

이 글에서는 gRPC가 무엇인지, 기존 REST + JSON 방식과 어떤 차이가 있는지, 그리고 내부적으로 어떤 흐름으로 동작하는지를 순서대로 소개합니다.


  • gRPC란?
  • JSON vs gRPC
  • gRPC 통신 흐름
  • 파이썬을 활용한 gRPC 통신 실습

gRPC란?


gRPC는 Google이 만든 오픈소스 RPC(Remote Procedure Call) 프레임워크입니다. 2016년 정식 출시 이후 현재까지 활발히 발전하고 있으며, Netflix, Apple, Microsoft 등 대형 서비스에서 내부 통신 방식으로 채택하고 있습니다.

먼저, RPC란 Remote Procedure Call의 줄임말로, 네트워크로 연결된 다른 컴퓨터의 함수를 호출하는 방식을 의미합니다. 호출하는 쪽에서는 해당 함수가 원격에 있다는 사실을 크게 의식하지 않아도 되며, 로컬 함수를 호출하는 것과 유사한 방식으로 사용할 수 있습니다.

REST API는 URL을 호출하고 JSON을 주고받는 방식이지만, gRPC는 "다른 서버의 함수를, 내 코드에서 직접 호출하듯이 사용할 수 있게 해주는 통신 기술"입니다. gRPC는 내부적으로 두 가지 기술을 조합하여 동작합니다.

  • HTTP/2: 데이터를 빠르게 주고받기 위한 전송 프로토콜
  • Protocol Buffers(Protobuf): 데이터를 작고 빠르게 압축하는 직렬화 포맷

위 조합을 통해 일반적인 REST + JSON 방식 대비 빠른 속도와 작은 데이터 용량을 실현하며, 다양한 프로그래밍 언어 간 통신에도 유연하게 대응할 수 있습니다.


JSON vs gRPC


REST + JSON과 gRPC + Protobuf의 주요 차이점을 확인해보겠습니다. 두 방식의 가장 핵심적인 차이는 데이터 용량과 전송 속도입니다. REST + JSON은 데이터 용량이 크고, 전송 속도가 느려 대규모 트래픽 환경에서는 한계가 있지만, gRPC + Protobuf는 바이너리 기반으로 데이터를 압축하여 더 작은 용량과 빠른 속도를 제공합니다.

REST + JSON gRPC + Protobuf
텍스트 기반, 사람이 읽기 쉬움 바이너리 기반, 3~5배 가볍고 빠름
브라우저에서 바로 사용 가능 브라우저 직접 호출 어려움
별도 설정 없이 바로 시작 .proto 파일로 계약 먼저 정의
상대적으로 느리고, 용량이 큼 스트리밍을 기본으로 지원
프론트 백엔드 통신에 적합 백엔드 서비스 간 통신에 적합

 


gRPC 통신 흐름


gRPC로 두 서버가 통신하려면 사전 준비가 필요합니다. 일반적인 REST API는 URL과 JSON 형식만 맞추면 바로 통신할 수 있지만, gRPC는 통신하기 전에 서버와 클라이언트가 미리 "어떤 데이터를 어떤 형식으로 주고받을지" 약속이 필요합니다. 이 약속을 담은 파일이 바로 .proto 파일입니다.

** 현재 챕터에서는 통신 흐름의 큰 그림만 파악하고, 각 단계의 구현 내용은 다음 챕터에서 Python 코드와 함께 살펴보겠습니다.

gRPC 통신 흐름
gRPC 통신 흐름

1. '.proto' 파일 정의

.proto 파일에는 두 가지를 선언합니다.

  • 첫째, 주고받을 데이터의 구조
    예시: "요청에는 이름(name)이 포함되고, 응답에는 메시지(message)가 포함된다"
  • 둘째, 제공할 함수(서비스)의 목록
    예시: "SayHello라는 함수를 제공하며, 이 함수는 HelloRequest를 받아 HelloReply를 반환한다"
// 예시

syntax = "proto3";

// 제공할 서비스(함수) 목록
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// 요청 데이터 구조
message HelloRequest {
  string name = 1;
}

// 응답 데이터 구조
message HelloReply {
  string message = 1;
}

2. 코드 자동 생성

.proto 파일을 작성하면 protoc라는 컴파일러를 사용해 코드를 자동으로 생성합니다. 이 과정에서 서버와 클라이언트 양쪽에서 사용할 코드가 만들어집니다. 직접 작성할 필요 없이, 명령어 한 줄로 생성되기 때문에 실수가 줄고 양쪽 코드가 항상 동일한 구조를 유지합니다.

# 다음 명령어로 py코드를 자동 생성
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. hello.proto

3. 서버 구현

자동 생성된 코드를 바탕으로 서버의 실제 로직을 작성합니다. .proto에서 선언한 함수(SayHello)가 어떻게 동작할지를 여기서 구현합니다. 이미 틀이 갖춰져 있기 때문에 핵심 로직에만 집중할 수 있습니다.

def SayHello(self, request, context):
    # request.name 으로 요청 데이터 접근
    return hello_pb2.HelloReply(
        message=f"안녕하세요, {request.name}님!"
    )

4. 클라이언트 호출

클라이언트는 stub이라는 객체를 통해 서버 함수를 호출합니다. stub은 자동 생성된 코드에 포함되어 있으며, 클라이언트 입장에서는 로컬 함수를 호출하는 것과 동일한 방식으로 사용할 수 있습니다. 실제 네트워크 통신, 데이터 직렬화/역직렬화는 내부에서 자동으로 처리됩니다.

# 서버 함수 호출 (마치 로컬 함수처럼!)
response = stub.SayHello(
    hello_pb2.HelloRequest(name="GH")
)
반응형

파이썬을 활용한 gRPC 통신 실습


파이썬으로 "안녕하세요" 한 마디를 주고받는 가장 간단한 gRPC 서버와 클라이언트를 만들어보겠습니다.

gRPC 통신 시퀀스 다이어그램
gRPC 통신 시퀀스 다이어그램


실습 step 1. 패키지 설치

pip install grpcio grpcio-tools

실습 step 2. 프로젝트 구조

프로젝트 구조레포지토리 구조는 다음과 같습니다. grpc-practice 폴더에 hello.proto, server.py, client.py를 생성할 예정입니다.

grpc-practice/
├── hello.proto        # 데이터 & 서비스 정의
├── server.py          # 서버 코드
└── client.py          # 클라이언트 코드

실습 step 3. hello.proto 작성

서버와 클라이언트가 공유할 "약속"입니다. 어떤 요청을 보내고, 어떤 응답을 받을지 정의합니다. 다음 코드를 hello.proto로 저장합니다.

syntax = "proto3";

// 제공할 서비스(함수) 목록
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

// 요청 데이터 구조
message HelloRequest {
  string name = 1;
}

// 응답 데이터 구조
message HelloReply {
  string message = 1;
}

실습 step 4. Python 자동 생성 코드 실행

다음 명령어를 터미널 창에서 실행합니다. 명령어를 실행하면, hello_pb2.py와 hello_pb2_grpc.py 두 파일이 자동으로 생성됩니다. 

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

실습 step 5. 서버 작성 (server.py)

자동 생성된 GreeterServicer를 상속받아 SayHello 함수의 실제 로직을 구현합니다. 이후 서버 인스턴스를 생성하고 50051번 포트에서 클라이언트의 요청을 수신 대기합니다.

import grpc
from concurrent import futures
import hello_pb2
import hello_pb2_grpc

class GreeterServicer(hello_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        # request.name 으로 요청 데이터 접근
        return hello_pb2.HelloReply(
            message=f"안녕하세요, {request.name}님!"
        )

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    hello_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
    server.add_insecure_port("[::]:50051")
    server.start()
    print("✅ 서버 시작! 포트 50051에서 대기 중")
    server.wait_for_termination()

if __name__ == "__main__":
    serve()

실습 step 6. 클라이언트 작성 (client.py)

GreeterStub을 통해 서버에 연결하고, stub.SayHello()를 호출하여 서버로 요청을 전송합니다. 응답은 response.message로 접근할 수 있으며, 로컬 함수를 호출하는 것과 동일한 방식으로 사용합니다.

import grpc
import hello_pb2
import hello_pb2_grpc

def run():
    # 서버에 연결
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = hello_pb2_grpc.GreeterStub(channel)

        # 서버 함수 호출 (마치 로컬 함수처럼!)
        response = stub.SayHello(
            hello_pb2.HelloRequest(name="GH")
        )
        print(f"서버 응답: {response.message}")

if __name__ == "__main__":
    run()

실습 step 7. 실행

서버를 먼저 실행한 후 클라이언트를 실행해야 합니다. 클라이언트는 서버가 실행 중인 상태에서만 정상적으로 연결되므로, 반드시 아래 순서를 따릅니다. 다음과 같은 결과가 출력된다면, gRPC 서버와 클라이언트 간의 통신 성공입니다!

- 1번 터미널: 서버 실행
python server.py

1번 터미널 - 서버 실행
1번 터미널 - 서버 실행

 

- 2번 터미널: 클라이언트 실행
python client.py

2번 터미널 - 클라이언트 실행
2번 터미널 - 클라이언트 실행


반응형