IDL (Interface Description Language) + JSON vs Protobuf 성능 체크

fearisthemindkiller·2024년 11월 12일

grpc

목록 보기
1/1

grpc를 보기 전에 IDL이 뭔지 확인해본다 ...

👽 IDL이란?

  1. 인터페이스 정의 언어(Interface Description Language, IDL) 는 특정 프로그래밍 언어와 별도로 지정된 객체의 인터페이스에 사용되는 일반 언어입니다.
    ( https://developer.mozilla.org/ko/docs/Glossary/IDL)
    _
  2. 소프트웨어 컴포넌트의 인터페이스를 묘사하기 위한 명세 언어이다.
    (https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4_%EC%A0%95%EC%9D%98_%EC%96%B8%EC%96%B4)

IDL은 어느 한 언어에 국한되지 않은 언어중립적인 방법으로 인터페이스를 표현함으로써, 같은 언어를 사용하지 않는 소프트웨어 컴포넌트 사이의 통신을 가능하게 한다

그니까... 한 쪽에서는 golang을 쓰고, 다른 쪽에서는 javascript를 써도 서로 언어와 상관없이 서로 인터페이스를 가능하게 한다.
인터페이스를 하기 위한 정의 언어이기 때문에 서로 통신하는 정보를 어떻게 저장할 지에 대한 규칙도 정할 수 있다.

구조화된 데이터를 IDL로 직렬화해서 보내고, 통신 환경에 이종간 플랫폼 환경에서 원활한 통신이 잘 되도록 한다.

IDL 중 Protocol Buffers는 보통 *RPC를 이용한 소프트웨어에서 사용한다.
이 때, RPC 연경의 양 쪽에 있는 컴퓨터는 다른 운영 체제와 프로그래밍 언어를 사용할 수 있다.
IDL은 다른 두 개의 시스템을 연결하는 다리 역할을 한다.

( RPC는 다음 게시물로 정리 예정. )



🥷 IDL 종류 및 특징 ...

xml, json 그리고 Protocol Buffers 등이 있다.
대표적으로 이 3개를 비교해본다.

XMLJSONProtocol Buffers (=proto)
사람이 읽을 수 있는가?OOX
브라우저에서 사용 가능?OOX
javaScript 지원OOX
스키마 여부옵션 (DTD,XSD으로 스키마 지원함)옵션필수
데이터 보안도청 및 디코딩 가능도청 및 디코딩 가능스키마 없이 해석 어려움
처리 속도느림보통빠름
데이터 크기중간작음
비용대규모 데이터에서는 비용이 많이 듦대규모 데이터에서는 비용이 많이 듦더 저렴함
RPC 인터페이스 지원아니오 (SOAP)아니오 (restful)
유효성 검사수동 파싱 및 유효성 검사 필요수동 파싱 및 유효성 검사 필요키워드로 유효성 검사 가능 (스키마 기반)


🌟 XML(eXtensible Markup Language)

eXtensible Markup Language의 약어. W3C에서 여러 특수 목적의 마크업 언어를 만드는 용도에서 권장되는 다목적 마크업 언어이다. 마크업 언어는 태그 등을 이용하여 데이터의 구조를 기술하는 언어의 한 가지이다. 가장 친숙하고 흔하게 접할 수 있는 마크업 언어로 HTML이 있다.

주로 데이터 표현 및 전송을 위한 마크업 언어다.

  • 마크업 언어 : 태그를 사용해서 문서나 데이터의 구조를 표현하는 언어

1. 사람이 읽을 수 있는가 ? - O

  • 복잡한 데이터 구조 표현 가능
  • 트리 구조로 복잡한 계층 구조에 사용하기 좋음
  • HTML, XSLT, SVG 등에서 사용함.

2. 장점 - 문서 중심의 데이터, 확장성 좋음

  • 문서 속성, 메타데이터, 주석 등을 표현할 수 있어서 문서 중심의 데이터 처리에 좋음.
  • 태그, 속성을 사용해서 데이터를 명확하게 정의할 수 있음
  • 호환할 수 있는 형식을 정의함으로써 데이터 공유 및 전송이 단순해진다. 유연하게 확장도 가능함.

3. 스키마 여부 - 옵션이지만, 스키마를 사용해서 유효성 검사 가능함

  • 옵션 사항이나 스키마 정의 (DTD, XSD)로 데이터 유효성 검사를 할 수 있다.

4. 데이터 크기 - 가장 큼...

  • 텍스트 기반이기 때문에 데이터 크기가 가장 크다.

5.어디서 보통 사용하는지?

  • SOAP API, configuration 파일, 문서 마크업, SVG


🤔 JSON (JavaScript Object Notation)

JavaScript Object Notation (JSON) 은 데이터 교환 형식의 일종입니다. 엄격한 부분 집합은 아니지만, JSON은 JavaScript 구문과 매우 유사합니다. 많은 프로그래밍 언어가 JSON을 지원하지만, 웹 사이트와 브라우저 확장기능을 포함한 JavaScript 기반 앱에 특히 유용합니다.

1. 사람이 읽을 수 있는가 ? - O

  • 사람이 읽을 수 있으며, 단순하고 직관적이이여서 가독성이 제일 좋다.
  • 키-값 쌍 형태로 되어있다.

2. 데이터 크기 및 효율성 - XML보다 좋다..

  • 간결한 문법으로 XML보다 데이터 크기가 작다.
  • 직렬화 / 역직렬화를 빠르게 처리할 수 있어서 API 통신할 때 좋다.
  • 파싱을 빠르게 할 수 있다. ( * 파싱 : 다른 형식으로 저장된 데이터를 원하는 형식의 데이터로 변환하는 것 )

3. 스키마 여부 - 옵션 => 유연하게 데이터 표현이 가능함.

  • 문자열, 숫자, 객체, 배열 등 다양한 데이터 타입을 사용해서 데이터 표현하기 좋다.

  • 스키마 지원을 하지 않기 떄문에 유효성 검사를 XML처럼 엄격하게 할 수는 없다... ( JSON Schema가 있긴 함)

  • 날짜, 바이너리 등 데이터 타입을 지원하지 않는 것이 있다.

4. 데이터 보안

  • 사실 JSON도 텍스트이기 때문에 데이터 보안을 위해서는 암호화가 필요하다.
  • JSON 인젝션 공격이 있을 수 있음.

5. RESTful API 시 사용

  • RESTful API 의 기본 데이터 포맷으로 사용하고 있어서 RESTful 통신할 때 좋음...
  • 그래서 웹 애플리케이션이나 모바일 앱에서 많이 사용 됨.


🤔 Protocol Buffers (=proto)

나는 RPC에 대해 공부할 예정이기 때문에 RPC에서 사용하는 protocol buffer에 대해서 더 자세히 알려보려고 한다.

Protocol Buffers는 Google에서 개발한 Protocol Buffers는 데이터를 직렬화(Serialization)하는 방법으로, RPC 및 네트워크 통신에서 사용된다.

1. 사람이 읽을 수 있는가? - X

  • proto는 바이너리 형식으로 데이터를 직렬화 하여, JSON이나 XML보다 더 작고, 빠르게 전송이 가능하다.
  • 사람이 읽을 수 있는 포맷이 아니므로, 사람이 이해하기 어렵다.

2. 데이터 보안 -스키마 기반이다

  • .proto 파일로 생성해서 사용한다.
  • 엄격한 타입 검사와 유효성 검사를 지원하므로 스키마 정의가 명확하다.
  • 그래서 스키마 없이는 데이터를 해석하기 어려워서 보안성이 상대적으로 높은 편이다.

3. 처리 속도 및 비용

  • 직렬화, 역직렬화가 빨라서 JSON, XML 보다 성능이 좋다.
  • 네트워크 대역폭을 절약할 수 있어서 대용량 데이터를 전송할 때 적합하다.

4. 언어, 플랫폼으로부터 독립적

  • 언어와 플랫폼에 종속받지 않는다.
  • JSON, XML에서 지원하지 않은 RPC 시스템에서 데이터를 주고받을 수 있다.

5. RPC 지원

  • gRPC와 통합해서 고성능의 원격 프로시저 호출 구현이 가능하다.
  • MSA 간의 통신을 효율적으로 처리할 수 있다.

🥸 정리 ..

따라서,

IDL은 인터페이스 정의 언어...로
정말 쉽게 말하자면 프로그램들이 다른 언어로 개발됐어도 서로 대화(인터페이스)할 수 있게 도와주는 공통으로 사용하는 언어라고 보면 됨...

이 때 종류는 대표적으로 3가지가 있는데 XML, JSON, Protocol Buffers가 있음..

사람이 알아볼 수 있어야하는데 복잡한 데이터 구조일 때.. 문서 중심 데이터..로 표현하고 싶다면 XML을 사용하고...
사람이 알아볼 수 있어야 하는데 RESTful 통신해야 한다면 JSON..
RPC 통신이나 데이터 전송할 때 효율성이 중요하다면 proto...

사용하면 된다...

https://developer.mozilla.org/ko/docs/Glossary/JSON
https://hungryjayy.github.io/posts/IDL/
https://velog.io/@gth1123/IDL-XML-JSON-gRPC
https://developer.mozilla.org/ko/docs/Web/XML

(+)

JSON vs Protobufs 성능 확인

그럼 진짜 얼마나 차이나는지 궁금해서 golang server로 api 서버를 띄우고, locust로 체크해보았다.

구조는 다음과 같다.

performance_idl
- proto
	|- message.proto	: proto 파일
    |- message.pd.go	: proto를 go에서 쓸 수 있도록 변환
    |- message_pd2.py	: proto를 py에서 쓸 수 있도록 변환
- go.mod
- locustfile.py			: locust 성능 테스트 파일
- server.go				: go server

Go 언어에서 Protocol Buffers (프로토콜 버퍼)**를 사용하기 위한 공식 라이브러리

  1. 사전설치 (go, python 설치되어있다는 가정하)
  • brew install protobuf
  • pip install locust
  • go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

message.proto

syntax = "proto3";

package myapp;

option go_package = ".;proto";
option optimize_for = SPEED;

message TestMessage {
  int32 id = 1;
  string name = 2;
  string message = 3;
  map<string, string> data = 4;
}
  • .proto 파일 => .go 파일
    protoc --go_out=. --go_opt=paths=source_relative proto/message.proto

  • .proto 파일 => .py 파일 (locust 에서 사용 예정)
    protoc --python_out=. proto/message.proto

server.go

package main

import (
	"encoding/json"
	"fmt"
	"google.golang.org/protobuf/proto"
	"io"
	"log"
	"net/http"
	pd "performance-idl/proto"
)

func handleJSON(w http.ResponseWriter, r *http.Request) {
	var data map[string]interface{}

	if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func handleProtobuf(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var message pd.TestMessage
	if err := proto.Unmarshal(body, &message); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	res := &pd.TestMessage{
		Id:      message.Id,
		Message: message.Message,
		Name:    message.Name,
		Data:    message.Data,
	}

	data, err := proto.Marshal(res)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/x-protobuf")
	w.Write(data)
}

func main() {
	http.HandleFunc("/api/json", handleJSON)
	http.HandleFunc("/api/protobuf", handleProtobuf)

	fmt.Println("server on.. 🥷")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

locustfile.py

from locust import HttpUser, task, between, constant
import json
from proto import message_pb2


class LoadTestUserProtobuf(HttpUser):
    wait_time = between(1, 3)

    @task(1)
    def protobuf_test(self):
        message = message_pb2.TestMessage(id=1, name="fear", message="fear is the mind killer",
                                          data={"key1": "value1", "key2": "value2", "key3": "value4"})
        self.client.post("/api/protobuf", data=message.SerializeToString(),
                         headers={"Content-Type": "application/x-protobuf"})
    @task(1)
    def json_test(self):
        payload = {"id": 1, "name": "fear", "message": "fear is the mind killer",
                   "data": {"key1": "value1", "key2": "value2", "key3": "value4"}}
        self.client.post("/api/json", data=json.dumps(payload), headers={"Content-Type": "application/json"})

상세 내용은 아래에서 확인해보면 된다.
https://github.com/fritmk/idl-performance

https://medium.com/@akresling/go-benchmark-json-v-protobuf-4ec3c62ec8d4

locust 지표 설명은 여서 확인해보면 된다.
https://velog.io/@tallguy188/%EC%84%B1%EB%8A%A5%EC%B5%9C%EC%A0%81%ED%99%94-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%B0%8F-%EB%A1%9C%EB%93%9C%ED%85%8C%EC%8A%A4%ED%8A%B8Locust

0개의 댓글