최근 많은 시스템이 마이크로서비스 구조를 전환하면서, 서비스 간 통신 방식도 REST API가 아닌 gRPC 고려해야 하는 상황이 늘고 있습니다. 본 글에서는 두 기술의 개념과 쓰임새를 정리하고, Go 언어를 사용한 예제와 벤치마크 테스트를 통해 어떻게 다른지를 비교해보려 합니다.
사실 gRPC를 다 쓰길래 얼마나 좋으면 그렇게 쓰는지가 궁금했고
이미 형태가 정형화돼서 생산성이 좋은 REST API와의 차이점이 궁금했는데 이점이 글을 쓴 이유입니다.
RPC
RPC부터 짚고 넘어가죠
RPC, Remote procedure call : 원격 프로시저 호출로 원격 호출(remote invocation) 또는 원격 메소드 호출(remote method invocation)로도 불립니다.
분산 네트워크 환경에서 쉽게 프로그래밍을 하고자 해서 만들어졌고 아래 사항들 지켜주므로
여러 다른 컴퓨터에서 보다 쉽게 상호작용할 수 있게 도와줍니다.
- 클라이언트-서버 간의 커뮤니케이션에 필요한 상세정보는 최대한 줄일 수 있음
- 클라이언트는 일반 메소드를 호출하는 것처럼 원격지의 프로지서를 호출 가능
- 서버도 마찬가지로 일반 메소드를 다루는 것처럼 원격 메소드를 다룰 수 있음
아래 사이트에서 보다 자세한 구조를 알 수 있죠
https://www.geeksforgeeks.org/remote-procedure-call-rpc-in-operating-system/
Remote Procedure Call (RPC) in Operating System - GeeksforGeeks
Remote Procedure Call (RPC) enables programs on one computer to execute functions on another computer over a network as if they were local, simplifying the development of distributed applications while managing the complexities of network communication.
www.geeksforgeeks.org
아래의 이미지를 보시면 기존에 REST API와 달리 Clinet, Server의 구조가 동등하다는 걸 볼 수 있습니다.
생산자와 요청자만 있는 REST API와 달리 gRPC는 서로 동일한 선상에서 원격 호출이 가능하죠.

REST API, gRPC 개념 정리
사실 이 부분은 aws가 잘 정리하긴 했습니다.
https://aws.amazon.com/ko/compare/the-difference-between-grpc-and-rest/
gRPC와 REST 비교 - 애플리케이션 설계의 차이 - AWS
REST는 소프트웨어 구성 요소 간 데이터 교환을 위한 일련의 규칙을 정의하는 소프트웨어 아키텍처 접근 방식입니다. REST는 웹의 표준 통신 프로토콜인 HTTP를 기반으로 합니다. RESTful API는 생성,
aws.amazon.com
간단 요약하자면 아래와 같죠
개념 | 특징 | |
REST API | HTTP/1.x(혹은 HTTP/2)를 기반으로 요청과 응답을 주고받으며, 주로 JSON 형식의 데이터를 사용 | 텍스트 기반의 JSON 포맷으로 가독성이 좋고, 구현 및 디버깅이 간편하지만, 직렬화/역직렬화 오버헤드가 있음 단방향인 요청-응답 구조 |
gRPC | HTTP/2 기반의 이진 프로토콜로, Protocol Buffers를 사용하여 데이터를 직렬화 | 빠른 직렬화/역직렬화와 낮은 네트워크 오버헤드 스트리밍과 멀티플렉싱 지원 인터페이스가 명세(proto 파일)를 통해 엄격하게 정의됨 주로 내부 서비스 간 통신이나 대용량 데이터 전송에 유리 |
사실 둘 다 http2 프로토콜을 지원해서 빠르지만 gRPC 쪽은 구글에서 만든 Protocol Buffers 덕분에
이진 데이터 직렬화 방식을 사용해서 데이터 전송 시 같은 크기의 데이터를 상대적으로 크고 빠르게 처리할 수 있습니다.
이 때문에 gRPC가 복잡한 시스템이 성능이 중요한 곳에서 더 유리하죠.
하지만 REST API 구성이 간단하고 생산성이 좋으니 적재적소에 사용하면 됩니다.
REST API
- 웹 애플리케이션, 모바일 백엔드, 공개 API 등 외부에 노출되는 서비스에 적합
- HTTP/1.x 기반으로 이미 널리 사용되고 있어 학습 비용이 낮음
gRPC
- 내부 시스템, 마이크로서비스 간 통신, 실시간 스트리밍 및 대용량 데이터 전송에 적합
- 높은 성능과 엄격한 인터페이스 정의가 필요한 경우 선택
REST API vs gRPC 비교
공통으로 사용할 데이터 형식입니다, 사실 이 부분은 아무 데이터나 되기에 크게 중요하지 않습니다
type User struct {
LastName string `json:"last_name"`
FirstName string `json:"first_name"`
Phone string `json:"phone"`
Email string `json:"email"`
Gender string `json:"gender"`
BirthDate string `json:"birth_date"`
Username string `json:"username"`
}
아래처럼 REST API 벤치마크 코드를 만들었고
main.go
package main
import (
"encoding/json"
_struct "grpc_vs_rest/struct"
"log"
"net/http"
)
func registerHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var user _struct.User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 실제 회원가입 로직 (예: DB 저장)
log.Printf("REST - Received user: %+v", user)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
}
func main() {
http.HandleFunc("/register", registerHandler)
log.Println("REST API server is running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
test/rest_benchmark_test.go
package test
import (
"bytes"
"encoding/json"
_struct "grpc_vs_rest/struct"
"net/http"
"testing"
)
func BenchmarkRestRegisterParallel(b *testing.B) {
user := _struct.User{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
data, _ := json.Marshal(user)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := http.Post("http://localhost:8080/register", "application/json", bytes.NewBuffer(data))
if err != nil {
b.Error(err)
continue
}
resp.Body.Close()
}
})
}
gRPC는 아래처럼 예제를 만들었습니다.
proto/user.proto
syntax = "proto3";
package user;
option go_package = "/proto/user;user";
service UserService{
// Unary RPC 통해 구현 : RPC 구조 중 가장 간단한 서비스 형태
rpc RegisterUser(UserRequest) returns (UserResponse){}
}
// protocol buffer 에서 필드를 식별하기 위해 숫자 태그 사용
// 이를 통해 데이터 인코딩, 호환성 유지, 효율성 측면을 챙김
message UserRequest {
string last_name = 1;
string first_name = 2;
string phone = 3;
string email = 4;
string gender = 5;
string birth_date = 6;
string username = 7;
}
message UserResponse {
string status = 1;
}
server.go
package main
import (
"context"
"google.golang.org/grpc"
pb "grpc_vs_rest/proto/user"
"log"
"net"
)
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) RegisterUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
log.Printf("gRPC - Received user: %+v", req)
// 실제 회원가입 로직 (예: DB 저장)
return &pb.UserResponse{Status: "success"}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
log.Println("gRPC server is running on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
test/grpc_benchmark_test.go
package test
import (
"context"
"testing"
"time"
"google.golang.org/grpc"
pb "grpc_vs_rest/proto/user"
)
func BenchmarkGrpcRegisterParallel(b *testing.B) {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
b.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
req := &pb.UserRequest{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
_, err := client.RegisterUser(ctx, req)
if err != nil {
b.Error(err)
}
cancel()
}
})
}
이제 이들을 실행시키고 테스트를 돌리면 다음과 같은 결과가 나오죠
go run .\main.go
go run .\server.go
※ 참고로 제 pc 상황은 아래와 같습니다
goos: windows
goarch: amd64
pkg: grpc_vs_rest/test
cpu: AMD Ryzen 7 5800X 8-Core Processor
우선 rest api를 다음 명령어로 벤치마크 돌리면
go test -bench=BenchmarkRestRegisterParallel -benchmem -benchtime=30s ./
rest_benchmark_test.go:27: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
...
FAIL
exit status 1
FAIL grpc_vs_rest/test 450.789s
FAIL
에러가 뜨면서 요청 상당 부분이 막히고 시간도 초과하였습니다.
이유는 로컬 서버에서 클라이언트의 요청을 받을 때 매번 새로운 연결을 만들고 이에 대한 임시 포트를 할당하는데
이 과정에 포트 충돌로 생기는 문제입니다.
임시 포트(동적 포트)는 다음처럼 확인이 가능한대, 기본으로는 49152~65535으로 설정되어 있습니다.

gRPC는 잘되지만 REST API는 이미 여기서부터 동적 포트 부족으로 비교가 되는 게 신기하여 가져왔습니다.
추가로 해당 문제는 로드밸런싱이나 도커로 해결이 가능합니다.
그러면 gRPC는 어떠냐
go test -bench=BenchmarkGrpcRegisterParallel -benchmem -benchtime=30s ./
BenchmarkGrpcRegisterParallel-16 766513 46522 ns/op 6496 B/op 112 allocs/op
PASS
ok grpc_vs_rest/test 36.173s
아주 잘 돌아가는 걸 볼 수 있죠.
이점만 봐도 안전성은 gRPC가 높다고 봐도 될 것 같습니다.
참고로 벤치마크 코드를 분석하면
- BenchmarkGrpcRegisterParallel-16 : 벤치마크 테스트 함수 이름과 실행된 고루틴의 수 GOMAXPROCS 값에 설정된 SetParallelism를 곱합 ( 4 x 4 = 16)
- 766513 : 벤치마크 함수 실행 횟수
- 46522 ns/op : 연산당 소요 시간으로, 한 번의 요청당 평균 46522 나노초가 걸렸음을 보여줌
- 112 allocs/op : 연산 1회당 메모리 할당된 수가 112개
- 36.173s : 그리고 총 36초 걸렸네요
좀 더 비교해 보기 위해 동시성을 직접 기입해서 해봅시다
그러면 조건은 아래와 같아지게 되죠.
- 연결 재사용(Keep-Alive) 설정
REST 벤치마크에서 기본 http.Post는 매번 새 연결을 생성하므로, 연결 풀을 지원하는 커스텀 http.Client를 사용해서 해결 가능합니다, 이렇게 하면 GRPC와 유사하게 지속 연결을 재사용하게 되죠 - 동일한 동시성(parallelism) 설정
벤치마크 함수 내에서 b.SetParallelism(n) 호출을 통해 동시에 실행되는 고루틴 수를 REST API와 동일하게 설정하면, 동일한 병렬 수준으로 부하를 걸 수 있습니다.
그리고 추가적인 비교를 위해 rest api는 Fail이 안 나오게 해 봅시다
grpc_benchmark_test.go는 아래처럼 한 줄로 병렬성 크기를 지정해 줍시다
...
func BenchmarkGrpcRegisterParallel(b *testing.B) {
// 코드 추가 동일한 병렬성 설정 (예: 4)
b.SetParallelism(4)
// 코드 추가 끝
...
}
rest_benchmark_test.go는 아래처럼 수정해 줍니다.
package test
import (
"bytes"
"encoding/json"
"net/http"
"testing"
"time"
_struct "grpc_vs_rest/struct"
)
func BenchmarkRestRegisterParallel(b *testing.B) {
// 커스텀 HTTP 클라이언트 생성 (연결 재사용 지원)
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
}
// GRPC와 동일한 병렬성 수준 설정 (예: 4)
b.SetParallelism(4)
user := _struct.User{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
data, _ := json.Marshal(user)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := client.Post("http://localhost:8080/register", "application/json", bytes.NewBuffer(data))
if err != nil {
b.Error(err)
continue // 에러 발생 시 건너뜁니다.
}
resp.Body.Close()
}
})
}
이러고 grpc 먼저 실행하면, 아까와 크게 다르지 않은 걸 볼 수 있죠
PS C:\Users\seung\code\go\learn-go\grpc_vs_rest\test> go test -bench=BenchmarkGrpcRegisterParallel -benchmem -benchtime=30s ./
goos: windows
goarch: amd64
pkg: grpc_vs_rest/test
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkGrpcRegisterParallel-16 828602 43619 ns/op 6493 B/op 111 allocs/op
PASS
ok grpc_vs_rest/test 36.619s
Parallelism을 4로 설정했지만 제 경우는 이전과 다른 게 없네요 ㅎㅎ
rest의 결과는 아래와 같죠
PS C:\Users\seung\code\go\learn-go\grpc_vs_rest\test> go test -bench=BenchmarkRestRegisterParallel -benchmem -benchtime=30s ./
goos: windows
goarch: amd64
pkg: grpc_vs_rest/test
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkRestRegisterParallel-16 530880 1044133 ns/op 13993 B/op 124 allocs/op
--- BENCH: BenchmarkRestRegisterParallel-16
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
... [output truncated]
PASS
ok grpc_vs_rest/test 555.044s
굉장히.. rest api가 느린 점을 볼 수 있습니다.
표로 정리하면 아래와 같습니다, 이렇게 결과가 나오면 대용량에서는 gRPC는 선택이 아닌 필수네요
gRPC | REST API | |
함수 실행 횟수 | 828602 | 530880 |
한 연산당 소요 시간 | 43619 ns/op | 1044133 ns/op |
연산당 할당된 바이트 수 | 6493 B/op | 13993 B/op |
연산당 메모리 할당 횟수 | 111 allocs/op | 124 allocs/op |
총 소요 시간 | 36.619s | 555.044s |
로컬에서 Error: Only one usage of each socket address (protocol/network address/port) is normally permitted 에러를 뜨지 않게 하려면 소켓별의 기본 대기 시간과 포트수의 맞춰서 테스트를 진행해야 합니다.

위 사진을 보면 tcp 동적 포트의 수는 16384개여서 처음 테스트 시 10,000건을 부담 없이 해결한 걸 볼 수 있습니다.
하지만 바로 아래에 같은 테스트를 돌릴 경우 아직 종료되지 않은 소켓 연결이 남아있어서 에러 로그가 남은 걸 볼 수 있죠.
즉 Error: Only one usage of each socket address (protocol/network address/port) is normally permitted 에러를 없애려면 동적 포트 수와 적절한 소켓 연결 시간을 설정해야 합니다.
여담으로 병렬 테스트 시 gRPC는 잘 처리한다고 하지만 REST API는 병령 테스트시 해당 문제를 일으킬 가능성이 높다고 합니다.
이점도 참고하면 좋겠네요
HTTP2
gRPC가 빠르고 분산 시스템에서 적합한 이유는 protocol buffers를 통한 데이터 직렬화와 http2라고 알고 있습니다.
그러면 여태까지의 예시처럼 단순 데이터 전송일 경우 http2로 바꾸면 어느 정도의 속도 향상이 생기고 gRPC랑 어떻게 다른 지도 궁금해졌습니다.
HTTP2는 HTTP1과 달리
- 하나의 TCP 연결에서 다수의 요청과 응답을 처리 가능
- 데이터가 이진 프레임으로 전송되어 파싱 효율 상승
- 해더 정보 압축해서, 네트워크 대역폭 절약
- 서버에서 클라이언트의 요청없이도 리소스를 전송할 수 있음
아래 패키지를 통해 http2 서버를 열었습니다.
https://pkg.go.dev/golang.org/x/net/http2/h2c
h2c package - golang.org/x/net/http2/h2c - Go Packages
Discover Packages golang.org/x/net http2 h2c Version: v0.37.0 Opens a new window with list of versions in this module. Published: Mar 5, 2025 License: BSD-3-Clause Opens a new window with license information. Imports: 14 Opens a new window with list of imp
pkg.go.dev
h2c를 통해 일단 TLS를 사용하지 않았기에 데이터를 암호화도 되지 않은 상태로 연결이 된 케이스입니다.
즉 암호화 작업이 빠졌으므로 기본적으로 TLS를 하는 gRPC보다 좀 더 속도의 우위를 점할 확률이 높은 케이스죠
※ TLS(Transport Layer Security) : 웹 사이트를 로드하는 웹 브라우저와 같이 웹 애플리케이션과 서버 간의 커뮤니케이션을 암호화하는 걸로, HTTPS는 HTTP 프로토콜 상위에서 TLS 암호화를 구현한 것이다.
코드는 다음과 같습니다.
h2main.go
package main
import (
"encoding/json"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
_struct "grpc_vs_rest/struct"
"log"
"net/http"
)
func h2RegisterHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var user _struct.User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Printf("REST - Received user: %+v", user)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
}
func main() {
h2cHandler := h2c.NewHandler(http.HandlerFunc(h2RegisterHandler), &http2.Server{})
server := &http.Server{
Addr: ":8080",
Handler: h2cHandler,
}
log.Println("HTTP/2 (h2c) server is running on :8080")
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Server failed: %v", err)
}
}
test/h2rest_benchmark_test.go
// 이 코드는 golang.org/x/net/http2/h2c 공식 문서를 참고하여 작성한 예시입니다.
package test
import (
"bytes"
"crypto/tls"
"encoding/json"
"net"
"net/http"
"testing"
"time"
"golang.org/x/net/http2"
_struct "grpc_vs_rest/struct"
)
func BenchmarkRestRegisterParallelHTTP2(b *testing.B) {
// 동시에 실행되는 고루틴 수 제한 (예: 4)
b.SetParallelism(4)
// HTTP/2(h2c) 전용 클라이언트 설정: TLS 없이 HTTP/2 연결 생성
client := &http.Client{
Transport: &http2.Transport{
AllowHTTP: true, // TLS 없이 HTTP/2 사용 허용
// DialTLS를 재정의하여 일반 TCP 연결을 반환함으로써 TLS 없이 연결 생성
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
Timeout: 10 * time.Second,
}
user := _struct.User{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
data, err := json.Marshal(user)
if err != nil {
b.Fatalf("failed to marshal user: %v", err)
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := client.Post("http://localhost:8080/register", "application/json", bytes.NewBuffer(data))
if err != nil {
b.Logf("HTTP POST error: %v", err)
continue
}
resp.Body.Close()
}
})
}
아래 http2의 벤치마크 결과물

기존 http1의 벤치마크 결과물 (pc가 바뀌어서 다시 실행)

그리고 rRPC

테이블로 정리하면 아래와 같죠
http1 | http2(TLS x) | gRPC | |
함수 실행 횟수 | 144621 | 820033 | 950257 |
한 연산당 소요 시간 | 2032664ns | 43574ns | 38345ns |
연산당 할당된 바이트 수 | 13807B | 4754B | 6492B |
연산당 메모리 할당 횟수 | 122 | 51 | 111 |
총 소요 시간 | 296.555s | 36.254s | 36.893s |
gRPC가 TLS가 빠진 http2보다 살짝 더 오래 걸렸지만, 함수 실행 횟수와 소요시간을 보면 유의미하게 gRPC가 빠른 걸 확인할 수 있죠, TLS가 없어도 이 정도의 차이가 난다는 게 신기하네요
여기서 더 나아가면 REST API에서 대중적으로 쓰이는 JSON 데이터 대신 Protocol Buffers를 적용해볼 수도 있겠네요
다양한 포멧이 있지만 multipart/form-data, xml, json, html, yaml, plain text 등등
아무래도 바이너리로 직렬화해서 적용한 Protocol Buffers를 이기긴 어렵죠 (거기다가 구글에서 만든건대)
추가로 테스트 해봐야할 부분
- http2 에 tls 추가 되었을 때의 테스팅
- 매우 큰 데이터를 보낼 때 json과 grpc의 이진 데이터 속도 차이
'Go Lang > Study' 카테고리의 다른 글
[GoLang] gRPC를 통한 동영상 스트리밍 및 모니터링 (0) | 2025.04.04 |
---|---|
[GoLang] gRPC 통신 패턴 (0) | 2025.03.21 |
[GoLang] 시간 초과 로직 Context, 채널과 비교 (0) | 2025.02.25 |
[GoLang] net/http에서 http 요청을 동시적으로 처리하는 이유 (0) | 2025.02.17 |
[GoLang] GC 튜닝 Profiling (0) | 2025.02.05 |
최근 많은 시스템이 마이크로서비스 구조를 전환하면서, 서비스 간 통신 방식도 REST API가 아닌 gRPC 고려해야 하는 상황이 늘고 있습니다. 본 글에서는 두 기술의 개념과 쓰임새를 정리하고, Go 언어를 사용한 예제와 벤치마크 테스트를 통해 어떻게 다른지를 비교해보려 합니다.
사실 gRPC를 다 쓰길래 얼마나 좋으면 그렇게 쓰는지가 궁금했고
이미 형태가 정형화돼서 생산성이 좋은 REST API와의 차이점이 궁금했는데 이점이 글을 쓴 이유입니다.
RPC
RPC부터 짚고 넘어가죠
RPC, Remote procedure call : 원격 프로시저 호출로 원격 호출(remote invocation) 또는 원격 메소드 호출(remote method invocation)로도 불립니다.
분산 네트워크 환경에서 쉽게 프로그래밍을 하고자 해서 만들어졌고 아래 사항들 지켜주므로
여러 다른 컴퓨터에서 보다 쉽게 상호작용할 수 있게 도와줍니다.
- 클라이언트-서버 간의 커뮤니케이션에 필요한 상세정보는 최대한 줄일 수 있음
- 클라이언트는 일반 메소드를 호출하는 것처럼 원격지의 프로지서를 호출 가능
- 서버도 마찬가지로 일반 메소드를 다루는 것처럼 원격 메소드를 다룰 수 있음
아래 사이트에서 보다 자세한 구조를 알 수 있죠
https://www.geeksforgeeks.org/remote-procedure-call-rpc-in-operating-system/
Remote Procedure Call (RPC) in Operating System - GeeksforGeeks
Remote Procedure Call (RPC) enables programs on one computer to execute functions on another computer over a network as if they were local, simplifying the development of distributed applications while managing the complexities of network communication.
www.geeksforgeeks.org
아래의 이미지를 보시면 기존에 REST API와 달리 Clinet, Server의 구조가 동등하다는 걸 볼 수 있습니다.
생산자와 요청자만 있는 REST API와 달리 gRPC는 서로 동일한 선상에서 원격 호출이 가능하죠.

REST API, gRPC 개념 정리
사실 이 부분은 aws가 잘 정리하긴 했습니다.
https://aws.amazon.com/ko/compare/the-difference-between-grpc-and-rest/
gRPC와 REST 비교 - 애플리케이션 설계의 차이 - AWS
REST는 소프트웨어 구성 요소 간 데이터 교환을 위한 일련의 규칙을 정의하는 소프트웨어 아키텍처 접근 방식입니다. REST는 웹의 표준 통신 프로토콜인 HTTP를 기반으로 합니다. RESTful API는 생성,
aws.amazon.com
간단 요약하자면 아래와 같죠
개념 | 특징 | |
REST API | HTTP/1.x(혹은 HTTP/2)를 기반으로 요청과 응답을 주고받으며, 주로 JSON 형식의 데이터를 사용 | 텍스트 기반의 JSON 포맷으로 가독성이 좋고, 구현 및 디버깅이 간편하지만, 직렬화/역직렬화 오버헤드가 있음 단방향인 요청-응답 구조 |
gRPC | HTTP/2 기반의 이진 프로토콜로, Protocol Buffers를 사용하여 데이터를 직렬화 | 빠른 직렬화/역직렬화와 낮은 네트워크 오버헤드 스트리밍과 멀티플렉싱 지원 인터페이스가 명세(proto 파일)를 통해 엄격하게 정의됨 주로 내부 서비스 간 통신이나 대용량 데이터 전송에 유리 |
사실 둘 다 http2 프로토콜을 지원해서 빠르지만 gRPC 쪽은 구글에서 만든 Protocol Buffers 덕분에
이진 데이터 직렬화 방식을 사용해서 데이터 전송 시 같은 크기의 데이터를 상대적으로 크고 빠르게 처리할 수 있습니다.
이 때문에 gRPC가 복잡한 시스템이 성능이 중요한 곳에서 더 유리하죠.
하지만 REST API 구성이 간단하고 생산성이 좋으니 적재적소에 사용하면 됩니다.
REST API
- 웹 애플리케이션, 모바일 백엔드, 공개 API 등 외부에 노출되는 서비스에 적합
- HTTP/1.x 기반으로 이미 널리 사용되고 있어 학습 비용이 낮음
gRPC
- 내부 시스템, 마이크로서비스 간 통신, 실시간 스트리밍 및 대용량 데이터 전송에 적합
- 높은 성능과 엄격한 인터페이스 정의가 필요한 경우 선택
REST API vs gRPC 비교
공통으로 사용할 데이터 형식입니다, 사실 이 부분은 아무 데이터나 되기에 크게 중요하지 않습니다
type User struct {
LastName string `json:"last_name"`
FirstName string `json:"first_name"`
Phone string `json:"phone"`
Email string `json:"email"`
Gender string `json:"gender"`
BirthDate string `json:"birth_date"`
Username string `json:"username"`
}
아래처럼 REST API 벤치마크 코드를 만들었고
main.go
package main
import (
"encoding/json"
_struct "grpc_vs_rest/struct"
"log"
"net/http"
)
func registerHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var user _struct.User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 실제 회원가입 로직 (예: DB 저장)
log.Printf("REST - Received user: %+v", user)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
}
func main() {
http.HandleFunc("/register", registerHandler)
log.Println("REST API server is running on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
test/rest_benchmark_test.go
package test
import (
"bytes"
"encoding/json"
_struct "grpc_vs_rest/struct"
"net/http"
"testing"
)
func BenchmarkRestRegisterParallel(b *testing.B) {
user := _struct.User{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
data, _ := json.Marshal(user)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := http.Post("http://localhost:8080/register", "application/json", bytes.NewBuffer(data))
if err != nil {
b.Error(err)
continue
}
resp.Body.Close()
}
})
}
gRPC는 아래처럼 예제를 만들었습니다.
proto/user.proto
syntax = "proto3";
package user;
option go_package = "/proto/user;user";
service UserService{
// Unary RPC 통해 구현 : RPC 구조 중 가장 간단한 서비스 형태
rpc RegisterUser(UserRequest) returns (UserResponse){}
}
// protocol buffer 에서 필드를 식별하기 위해 숫자 태그 사용
// 이를 통해 데이터 인코딩, 호환성 유지, 효율성 측면을 챙김
message UserRequest {
string last_name = 1;
string first_name = 2;
string phone = 3;
string email = 4;
string gender = 5;
string birth_date = 6;
string username = 7;
}
message UserResponse {
string status = 1;
}
server.go
package main
import (
"context"
"google.golang.org/grpc"
pb "grpc_vs_rest/proto/user"
"log"
"net"
)
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) RegisterUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
log.Printf("gRPC - Received user: %+v", req)
// 실제 회원가입 로직 (예: DB 저장)
return &pb.UserResponse{Status: "success"}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
log.Println("gRPC server is running on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
test/grpc_benchmark_test.go
package test
import (
"context"
"testing"
"time"
"google.golang.org/grpc"
pb "grpc_vs_rest/proto/user"
)
func BenchmarkGrpcRegisterParallel(b *testing.B) {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
b.Fatalf("failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
req := &pb.UserRequest{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
_, err := client.RegisterUser(ctx, req)
if err != nil {
b.Error(err)
}
cancel()
}
})
}
이제 이들을 실행시키고 테스트를 돌리면 다음과 같은 결과가 나오죠
go run .\main.go
go run .\server.go
※ 참고로 제 pc 상황은 아래와 같습니다
goos: windows
goarch: amd64
pkg: grpc_vs_rest/test
cpu: AMD Ryzen 7 5800X 8-Core Processor
우선 rest api를 다음 명령어로 벤치마크 돌리면
go test -bench=BenchmarkRestRegisterParallel -benchmem -benchtime=30s ./
rest_benchmark_test.go:27: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
...
FAIL
exit status 1
FAIL grpc_vs_rest/test 450.789s
FAIL
에러가 뜨면서 요청 상당 부분이 막히고 시간도 초과하였습니다.
이유는 로컬 서버에서 클라이언트의 요청을 받을 때 매번 새로운 연결을 만들고 이에 대한 임시 포트를 할당하는데
이 과정에 포트 충돌로 생기는 문제입니다.
임시 포트(동적 포트)는 다음처럼 확인이 가능한대, 기본으로는 49152~65535으로 설정되어 있습니다.

gRPC는 잘되지만 REST API는 이미 여기서부터 동적 포트 부족으로 비교가 되는 게 신기하여 가져왔습니다.
추가로 해당 문제는 로드밸런싱이나 도커로 해결이 가능합니다.
그러면 gRPC는 어떠냐
go test -bench=BenchmarkGrpcRegisterParallel -benchmem -benchtime=30s ./
BenchmarkGrpcRegisterParallel-16 766513 46522 ns/op 6496 B/op 112 allocs/op
PASS
ok grpc_vs_rest/test 36.173s
아주 잘 돌아가는 걸 볼 수 있죠.
이점만 봐도 안전성은 gRPC가 높다고 봐도 될 것 같습니다.
참고로 벤치마크 코드를 분석하면
- BenchmarkGrpcRegisterParallel-16 : 벤치마크 테스트 함수 이름과 실행된 고루틴의 수 GOMAXPROCS 값에 설정된 SetParallelism를 곱합 ( 4 x 4 = 16)
- 766513 : 벤치마크 함수 실행 횟수
- 46522 ns/op : 연산당 소요 시간으로, 한 번의 요청당 평균 46522 나노초가 걸렸음을 보여줌
- 112 allocs/op : 연산 1회당 메모리 할당된 수가 112개
- 36.173s : 그리고 총 36초 걸렸네요
좀 더 비교해 보기 위해 동시성을 직접 기입해서 해봅시다
그러면 조건은 아래와 같아지게 되죠.
- 연결 재사용(Keep-Alive) 설정
REST 벤치마크에서 기본 http.Post는 매번 새 연결을 생성하므로, 연결 풀을 지원하는 커스텀 http.Client를 사용해서 해결 가능합니다, 이렇게 하면 GRPC와 유사하게 지속 연결을 재사용하게 되죠 - 동일한 동시성(parallelism) 설정
벤치마크 함수 내에서 b.SetParallelism(n) 호출을 통해 동시에 실행되는 고루틴 수를 REST API와 동일하게 설정하면, 동일한 병렬 수준으로 부하를 걸 수 있습니다.
그리고 추가적인 비교를 위해 rest api는 Fail이 안 나오게 해 봅시다
grpc_benchmark_test.go는 아래처럼 한 줄로 병렬성 크기를 지정해 줍시다
...
func BenchmarkGrpcRegisterParallel(b *testing.B) {
// 코드 추가 동일한 병렬성 설정 (예: 4)
b.SetParallelism(4)
// 코드 추가 끝
...
}
rest_benchmark_test.go는 아래처럼 수정해 줍니다.
package test
import (
"bytes"
"encoding/json"
"net/http"
"testing"
"time"
_struct "grpc_vs_rest/struct"
)
func BenchmarkRestRegisterParallel(b *testing.B) {
// 커스텀 HTTP 클라이언트 생성 (연결 재사용 지원)
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
}
// GRPC와 동일한 병렬성 수준 설정 (예: 4)
b.SetParallelism(4)
user := _struct.User{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
data, _ := json.Marshal(user)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := client.Post("http://localhost:8080/register", "application/json", bytes.NewBuffer(data))
if err != nil {
b.Error(err)
continue // 에러 발생 시 건너뜁니다.
}
resp.Body.Close()
}
})
}
이러고 grpc 먼저 실행하면, 아까와 크게 다르지 않은 걸 볼 수 있죠
PS C:\Users\seung\code\go\learn-go\grpc_vs_rest\test> go test -bench=BenchmarkGrpcRegisterParallel -benchmem -benchtime=30s ./
goos: windows
goarch: amd64
pkg: grpc_vs_rest/test
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkGrpcRegisterParallel-16 828602 43619 ns/op 6493 B/op 111 allocs/op
PASS
ok grpc_vs_rest/test 36.619s
Parallelism을 4로 설정했지만 제 경우는 이전과 다른 게 없네요 ㅎㅎ
rest의 결과는 아래와 같죠
PS C:\Users\seung\code\go\learn-go\grpc_vs_rest\test> go test -bench=BenchmarkRestRegisterParallel -benchmem -benchtime=30s ./
goos: windows
goarch: amd64
pkg: grpc_vs_rest/test
cpu: AMD Ryzen 7 5800X 8-Core Processor
BenchmarkRestRegisterParallel-16 530880 1044133 ns/op 13993 B/op 124 allocs/op
--- BENCH: BenchmarkRestRegisterParallel-16
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
rest_benchmark_test.go:42: REST POST error: Post "http://localhost:8080/register": dial tcp [::1]:8080: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
... [output truncated]
PASS
ok grpc_vs_rest/test 555.044s
굉장히.. rest api가 느린 점을 볼 수 있습니다.
표로 정리하면 아래와 같습니다, 이렇게 결과가 나오면 대용량에서는 gRPC는 선택이 아닌 필수네요
gRPC | REST API | |
함수 실행 횟수 | 828602 | 530880 |
한 연산당 소요 시간 | 43619 ns/op | 1044133 ns/op |
연산당 할당된 바이트 수 | 6493 B/op | 13993 B/op |
연산당 메모리 할당 횟수 | 111 allocs/op | 124 allocs/op |
총 소요 시간 | 36.619s | 555.044s |
로컬에서 Error: Only one usage of each socket address (protocol/network address/port) is normally permitted 에러를 뜨지 않게 하려면 소켓별의 기본 대기 시간과 포트수의 맞춰서 테스트를 진행해야 합니다.

위 사진을 보면 tcp 동적 포트의 수는 16384개여서 처음 테스트 시 10,000건을 부담 없이 해결한 걸 볼 수 있습니다.
하지만 바로 아래에 같은 테스트를 돌릴 경우 아직 종료되지 않은 소켓 연결이 남아있어서 에러 로그가 남은 걸 볼 수 있죠.
즉 Error: Only one usage of each socket address (protocol/network address/port) is normally permitted 에러를 없애려면 동적 포트 수와 적절한 소켓 연결 시간을 설정해야 합니다.
여담으로 병렬 테스트 시 gRPC는 잘 처리한다고 하지만 REST API는 병령 테스트시 해당 문제를 일으킬 가능성이 높다고 합니다.
이점도 참고하면 좋겠네요
HTTP2
gRPC가 빠르고 분산 시스템에서 적합한 이유는 protocol buffers를 통한 데이터 직렬화와 http2라고 알고 있습니다.
그러면 여태까지의 예시처럼 단순 데이터 전송일 경우 http2로 바꾸면 어느 정도의 속도 향상이 생기고 gRPC랑 어떻게 다른 지도 궁금해졌습니다.
HTTP2는 HTTP1과 달리
- 하나의 TCP 연결에서 다수의 요청과 응답을 처리 가능
- 데이터가 이진 프레임으로 전송되어 파싱 효율 상승
- 해더 정보 압축해서, 네트워크 대역폭 절약
- 서버에서 클라이언트의 요청없이도 리소스를 전송할 수 있음
아래 패키지를 통해 http2 서버를 열었습니다.
https://pkg.go.dev/golang.org/x/net/http2/h2c
h2c package - golang.org/x/net/http2/h2c - Go Packages
Discover Packages golang.org/x/net http2 h2c Version: v0.37.0 Opens a new window with list of versions in this module. Published: Mar 5, 2025 License: BSD-3-Clause Opens a new window with license information. Imports: 14 Opens a new window with list of imp
pkg.go.dev
h2c를 통해 일단 TLS를 사용하지 않았기에 데이터를 암호화도 되지 않은 상태로 연결이 된 케이스입니다.
즉 암호화 작업이 빠졌으므로 기본적으로 TLS를 하는 gRPC보다 좀 더 속도의 우위를 점할 확률이 높은 케이스죠
※ TLS(Transport Layer Security) : 웹 사이트를 로드하는 웹 브라우저와 같이 웹 애플리케이션과 서버 간의 커뮤니케이션을 암호화하는 걸로, HTTPS는 HTTP 프로토콜 상위에서 TLS 암호화를 구현한 것이다.
코드는 다음과 같습니다.
h2main.go
package main
import (
"encoding/json"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
_struct "grpc_vs_rest/struct"
"log"
"net/http"
)
func h2RegisterHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var user _struct.User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Printf("REST - Received user: %+v", user)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
}
func main() {
h2cHandler := h2c.NewHandler(http.HandlerFunc(h2RegisterHandler), &http2.Server{})
server := &http.Server{
Addr: ":8080",
Handler: h2cHandler,
}
log.Println("HTTP/2 (h2c) server is running on :8080")
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Server failed: %v", err)
}
}
test/h2rest_benchmark_test.go
// 이 코드는 golang.org/x/net/http2/h2c 공식 문서를 참고하여 작성한 예시입니다.
package test
import (
"bytes"
"crypto/tls"
"encoding/json"
"net"
"net/http"
"testing"
"time"
"golang.org/x/net/http2"
_struct "grpc_vs_rest/struct"
)
func BenchmarkRestRegisterParallelHTTP2(b *testing.B) {
// 동시에 실행되는 고루틴 수 제한 (예: 4)
b.SetParallelism(4)
// HTTP/2(h2c) 전용 클라이언트 설정: TLS 없이 HTTP/2 연결 생성
client := &http.Client{
Transport: &http2.Transport{
AllowHTTP: true, // TLS 없이 HTTP/2 사용 허용
// DialTLS를 재정의하여 일반 TCP 연결을 반환함으로써 TLS 없이 연결 생성
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
Timeout: 10 * time.Second,
}
user := _struct.User{
LastName: "Kim",
FirstName: "Minji",
Phone: "010-1234-5678",
Email: "minji@example.com",
Gender: "F",
BirthDate: "1990-01-01",
Username: "minji90",
}
data, err := json.Marshal(user)
if err != nil {
b.Fatalf("failed to marshal user: %v", err)
}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
resp, err := client.Post("http://localhost:8080/register", "application/json", bytes.NewBuffer(data))
if err != nil {
b.Logf("HTTP POST error: %v", err)
continue
}
resp.Body.Close()
}
})
}
아래 http2의 벤치마크 결과물

기존 http1의 벤치마크 결과물 (pc가 바뀌어서 다시 실행)

그리고 rRPC

테이블로 정리하면 아래와 같죠
http1 | http2(TLS x) | gRPC | |
함수 실행 횟수 | 144621 | 820033 | 950257 |
한 연산당 소요 시간 | 2032664ns | 43574ns | 38345ns |
연산당 할당된 바이트 수 | 13807B | 4754B | 6492B |
연산당 메모리 할당 횟수 | 122 | 51 | 111 |
총 소요 시간 | 296.555s | 36.254s | 36.893s |
gRPC가 TLS가 빠진 http2보다 살짝 더 오래 걸렸지만, 함수 실행 횟수와 소요시간을 보면 유의미하게 gRPC가 빠른 걸 확인할 수 있죠, TLS가 없어도 이 정도의 차이가 난다는 게 신기하네요
여기서 더 나아가면 REST API에서 대중적으로 쓰이는 JSON 데이터 대신 Protocol Buffers를 적용해볼 수도 있겠네요
다양한 포멧이 있지만 multipart/form-data, xml, json, html, yaml, plain text 등등
아무래도 바이너리로 직렬화해서 적용한 Protocol Buffers를 이기긴 어렵죠 (거기다가 구글에서 만든건대)
추가로 테스트 해봐야할 부분
- http2 에 tls 추가 되었을 때의 테스팅
- 매우 큰 데이터를 보낼 때 json과 grpc의 이진 데이터 속도 차이
'Go Lang > Study' 카테고리의 다른 글
[GoLang] gRPC를 통한 동영상 스트리밍 및 모니터링 (0) | 2025.04.04 |
---|---|
[GoLang] gRPC 통신 패턴 (0) | 2025.03.21 |
[GoLang] 시간 초과 로직 Context, 채널과 비교 (0) | 2025.02.25 |
[GoLang] net/http에서 http 요청을 동시적으로 처리하는 이유 (0) | 2025.02.17 |
[GoLang] GC 튜닝 Profiling (0) | 2025.02.05 |