본문 바로가기

카테고리 없음

Protobuf로 JSON 성능 향상

프로토콜 버퍼 (Protobuf)는 Google이 여러 서비스간에 데이터를 직렬화하기 위해 만든 이진 형식입니다. Google은 이 프로토콜을 오픈 소스로 만들었으며 이제 JavaScript, Java, C #, Ruby 등과 같은 가장 일반적인 언어를 즉시 지원합니다. 테스트에서 이 프로토콜이 JSON보다 최대 6 배 더 빠른 것으로 나타났습니다.

 

Protobuf는 무엇입니까?

 

일반적으로 Protobuf라고하는 프로토콜 버퍼는 구조화 된 데이터의 직렬화 및 역 직렬화를 허용하기 위해 Google에서 개발 한 프로토콜입니다. Google은 XML과 비교하여 더 나은 시스템 커뮤니케이션 방법을 제공하기 위해 이를 개발했습니다. 그래서 그들은 XML보다 더 단순하고 작고 빠르며 유지 관리가 용이하도록 만드는 데 집중했습니다. 이 기사에서 볼 수 있듯이이 프로토콜은 성능, 유지 보수성 및 크기가 작기 때문에 JSON보다 뛰어났습니다.

 

JSON과 어떻게 다른가요?

 

JSON과 Protobuf 메시지는 서로 바꿔 사용할 수 있지만 이러한 기술은 서로 다른 목표로 설계되었습니다. JavaScript Object Notation의 약어 인 JSON은 JavaScript 프로그래밍 언어의 하위 집합에서 발생하는 메시지 형식 일뿐입니다. JSON 메시지는 텍스트 형식으로 교환되며 현재 거의 모든 프로그래밍 언어에서 완전히 독립적이며 지원됩니다.

 

반면에 Protobuf는 단순한 메시지 형식 일뿐 아니라 이러한 메시지를 정의하고 교환하는 규칙 및 도구 세트이기도합니다. 이 프로토콜을 만든 Google은 오픈 소스로 만들었으며 JavaScript, Java, PHP, C #, Ruby, Objective C, Python, C ++ 및 Go와 같이 가장 많이 사용되는 프로그래밍 언어에 대한 코드를 생성하는 도구를 제공합니다. 게다가 Protobuf는 열거 형 및 메서드와 같이 JSON보다 더 많은 데이터 형식을 가지며 RPC (원격 프로 시저 호출)에도 많이 사용됩니다.

 

Protobuf가 JSON보다 훨씬 빠릅니까?

 

온라인에서 Protobuf가 JSON, XML 등과 같은 것보다 성능이 우수하다는 것을 보여주는 몇 가지 리소스가 있지만, 이것이 자신의 필요와 사용 사례에 해당하는지 항상 확인하는 것이 중요합니다. 여기에서 Auth0에서 몇 가지 시나리오를 테스트하고 JSON 및 Protobuf의 수행 방식을 측정하는 간단한 Spring Boot 응용 프로그램을 개발했습니다. 주로 두 개의 Java 응용 프로그램이 통신하고 JavaScript 웹 응용 프로그램이이 백엔드와 통신하도록 두 프로토콜의 직렬화를 테스트했습니다.

 

이 두 시나리오 (Java to Java 및 JavaScript to Java)를 작성하는 주된 이유는 이 프로토콜이 Java와 같은 엔터프라이즈 환경과 JSON이 기본 메시지 형식 인 환경에서 작동하는 방식을 측정하는 것이 었습니다. 즉, 여기에 표시되는 것은 JSON이 내장되어 있고 매우 빠른 성능을 제공해야하는 환경 (JavaScript 엔진) 및 JSON이 일급 시민이 아닌 환경의 데이터입니다.

 

질문에 대한 짧은 대답은 그렇습니다 .Protobuf는 JSON보다 빠릅니다. 그러나이 답변은 실험에서 수집 한 데이터가 없으면 유용하거나 흥미롭지 않습니다. 이제 세부 사항을 살펴 보겠습니다.

 

테스트 샘플

 

측정을 지원하기 위해 3 개의 Protobuf 메시지를 작성했습니다. 거리와 숫자를 갖는 Address, 이름과 주모 모음, 핸드폰 번호, email 주모 소음을 갖는 Person,  Person의 메세지 모음을 갖는 People. 이 메시지는 4 개의 RESTful 엔드 포인트가있는 애플리케이션에서 함께 조립되었습니다.

 

1. 첫번째는 GET 요청을 수락하고 Protobuf 형식으로 55,000 명 목록을 반환.

2. 두번째는 GET 요청을 수락하고 50 만 명의 동일한 목록을 리턴했지만 JSON 형식으로 리턴했습니다.

3. 세 번째는 Protobuf 형식의 사람들과 함께 POST 요청을 수락 한 것입니다.

4. 네번째는 JSON 형식의 인원을 가진 POST 요청을 수락했습니다.

 

자바 스크립트에서 자바로의 커뮤니케이션

 

사용 가능한 JavaScript 엔진이 많으므로 가장 많이 사용되는 JavaScript 엔진이 이 데이터 세트에서 어떻게 작동하는지 확인하는 것이 좋습니다. 그래서 나는 다음 브라우저를 사용하기로 결정했습니다. Chrome은 가장 인기있는 브라우저이며 JavaScript 엔진은 Node.js에서도 사용됩니다. Firefox는 또 다른 매우 인기있는 브라우저입니다. Safari, MacBook 및 iPhone의 기본 브라우저입니다.

 

다음 차트는 50 개의 후속 GET 요청에서 이러한 브라우저의 평균 성능을 두 엔드 포인트 (Protobuf 및 JSON 엔드 포인트)에 표시합니다. 엔드 포인트 당 이러한 50 개의 요청이 두 번 발행되었습니다. 먼저 압축이 설정된 상태에서 Spring Boot 응용 프로그램을 실행할 때; 압축이 해제 된 상태에서 응용 프로그램을 실행할 때 결국, 각 브라우저는 이 5 만명 데이터의 200 배를 요청했습니다.

위의 차트에서 볼 수 있듯이 압축 환경의 결과는 Protobuf와 JSON 모두에서 매우 유사했습니다. 프로토 타입 메시지는 JSON 메시지보다 9 % 작았으며 JavaScript 코드를 사용할 수있는 시간이 4 % 단축되었습니다. 이것은 아무것도 아닌 것처럼 들릴 수 있지만 Protobuf가 바이너리에서 JSON으로 변환되어야한다는 것을 고려할 때 JavaScript 코드는 JSON을 객체 리터럴 형식으로 사용합니다 .Protobuf가 다른 것보다 빠르다는 것은 놀라운 일입니다.

 

이제 압축되지 않은 메시지를 처리해야 할 때 결과가 약간 변경됩니다. 아래 차트를 분석해 보겠습니다.

 

이러한 상황에서 JSON과 비교할 때 Protobuf의 성능이 훨씬 우수합니다. 이 형식의 메시지는 34 % 더 작았으며 JavaScript 코드에 사용 가능한 시간이 21 % 단축되었습니다.

 

POST 요청을 발행 할 때 일반적으로 이러한 종류의 요청은 무거운 메시지를 처리하지 않으므로 차이가 거의 인식되지 않습니다. 이 요청은 단순히 양식 또는 유사한 항목의 일부 필드 업데이트 만 처리합니다. 따라서 신뢰할 수있는 테스트를 위해 하나의 Person 메시지와 이메일 주소 및 모바일과 같은 몇 가지 속성으로 50 개의 요청을 발행했습니다. 결과는 아래에서 확인할 수 있습니다.

 

이 경우 메시지 크기는 그다지 크지 않았습니다. 주로 메시지 크기가 너무 작아서 메타 데이터가 데이터 자체보다 무거웠 기 때문입니다. 그리고 JSON 요청과 비교할 때 Protobuf 요청의 성능이 4 % 향상되어 요청을 발행하고 응답을받는 시간도 거의 동일했습니다.

 

다른 장점과 단점이 있습니까?

 

결정을 내릴 때마다 장단점이 있습니다. 그리고 하나의 메시지 형식이나 다른 프로토콜 형식을 선택할 때 이는 다르지 않습니다. 프로토콜 버퍼에는 아래 목록과 같이 몇 가지 문제가 있습니다.

  • 자원 부족. Protobuf 사용 및 개발에 대한 많은 자료 (매우 자세한 문서 나 블로그 게시물을 기대하지 않음)를 찾을 수 없습니다.
  • 소규모 커뮤니티. 아마도 첫 번째 단점의 ​​근본 원인 일 것입니다. 예를 들어, 스택 오버플로에서는 대략 1.500 개의 질문이 Protobuf 태그로 표시되어 있습니다. JSON은이 동일한 플랫폼에서 18 만 건 이상의 질문을합니다.
  • 지원 부족. Google은 Swift, R, Scala 등과 같은 다른 프로그래밍 언어를 지원하지 않습니다. 그러나 때때로 Apple에서 제공하는 Swift Protobuf와 같은 타사 라이브러리를 사용하여이 문제를 극복 할 수 있습니다.
  • 인간이 아닌 가독성. 텍스트 형식과 간단한 구조로 교환되는 JSON은 사람이 쉽게 읽고 분석 할 수 있습니다. 이진 형식의 경우에는 해당되지 않습니다.

Protobuf를 선택하면 이러한 단점이 발생하지만 위에서 설명한 것처럼 일부 상황에서는이 프로토콜이 훨씬 빠릅니다. 그 외에도 몇 가지 다른 장점이 있습니다.

 

  • 공식적인 형식. 형식은 자체 설명입니다.
  • RPC 지원. 서버 RPC 인터페이스는 프로토콜 파일의 일부로 선언 될 수 있습니다.
  • 구조 검증. JSON과 비교할 때 사전 정의되고 더 큰 데이터 유형 세트를 갖는 Protobuf에서 직렬화 된 메시지는이를 교환하는 코드에 의해 자동으로 유효성 검증 될 수 있습니다.

우리는 어떻게 Protobuf를 사용합니까?

 

Protobuf가 JSON보다 빠르다는 것을 이미 알고 있었고 장단점도 알고 있으므로이 기술을 사용하는 방법을 살펴 보겠습니다. Protobuf에는 다음 세 가지 주요 구성 요소가 있습니다.

 

1. 메시지 디스크립터 Protobuf를 사용할 때는 메시지 구조를 .proto 파일로 정의해야합니다.

2. 메시지 구현. 메시지 정의는 프로그래밍 언어로 데이터를 표현하고 교환하기에 충분하지 않습니다. 선택한 프로그래밍 언어로 데이터를 처리하기 위해 클래스 / 객체를 생성해야합니다. 다행히 Google은 가장 일반적인 프로그래밍 언어를위한 코드 생성기를 제공합니다.

3. 파싱 ​​및 직렬화. Protobuf 메시지를 정의하고 생성 한 후 이러한 메시지를 교환 할 수 있어야합니다. 지원되는 프로그래밍 언어 중 하나를 사용하는 한 Google에서 다시 도와 드리겠습니다.

 

각 구성 요소를 살짝 살펴 보겠습니다.

 

protobuf 메시지 정의

 

이미 언급했듯이 Protobuf의 메시지는 .proto 파일에 설명되어 있습니다. 아래에서 성능 테스트에 사용한 세 가지 메시지 설명 자의 예를 찾을 수 있습니다. 나는 people.proto라는 동일한 파일에 모든 것을 정의했습니다.

syntax = "proto3";

package demo;

option java_package = "com.auth0.protobuf";

message People {
    repeated Person person = 1;
}

message Person {
    string name = 1;
    repeated Address address = 2;
    repeated string mobile = 3;
    repeated string email = 4;
}

message Address {
    string street = 1;
    int32 number = 2;
}

 

위의 세 가지 메시지는 매우 간단하고 이해하기 쉽습니다. 첫 번째 메시지 인 People은 Person 메시지 모음 만 포함합니다. 두 번째 메시지 인 Person은 문자열 유형의 이름, 주소 메시지 모음, 문자열로 보유 된 휴대폰 번호 모음, 마지막으로 이메일 주소 모음도 문자열로 보유합니다. 세 번째 메시지 인 Address에는 두 가지 속성이 있습니다. 첫 번째 메시지는 string 유형의 거리입니다. 두 번째는 int32 유형의 수입니다.

 

이러한 정의 외에도 파일 맨 위에 코드 생성기를 돕는 세 줄이 있습니다.

 

1. 먼저 proto3 값으로 정의 된 syntax이 있습니다. 이것은 내가 사용하고있는 Protobuf 버전이며, 작성 당시 최신 버전입니다. 이전 버전의 Protobuf를 사용하면 개발자가 필요한 키워드를 사용하여 교환 한 메시지에 대해 개발자가 더 제한적으로 사용할 수있었습니다. 이것은 더 이상 사용되지 않으며 더 이상 사용할 수 없습니다.

2.  package demo; 가 있습니다. 정의. 이 구성은 생성 된 생성 된 클래스 / 객체를 중첩하는 데 사용됩니다.

3. option java_package 정의가 있습니다. 이 구성은 생성기에서 생성 된 소스를 중첩하기 위해 사용합니다. 여기서 차이점은 이것이 Java에만 적용된다는 것입니다. Java로 코드를 작성할 때와 JavaScript로 코드를 작성할 때 생성기가 다르게 작동하도록 두 구성을 사용했습니다. 즉, Java 클래스는 com.auth0.protobuf 패키지에서 작성되었으며 JavaScript 오브젝트는 demo에서 작성되었습니다.

 

Protobuf에는 더 많은 옵션과 데이터 유형이 있습니다. Google은 여기에서 이와 관련하여 매우 훌륭한 문서를 보유하고 있습니다.

 

메시지 구현

 

proto 메시지에 대한 소스 코드를 생성하기 위해 두 개의 라이브러리를 사용했습니다.

 

1. Java의 경우 Google에서 제공 한 프로토콜 컴파일러를 사용했습니다. 프로토콜 버퍼 설명서의이 페이지는 설치 방법을 설명합니다. MacBook에서 Brew를 사용하는 것은 Brew Install Protobuf를 발행하는 문제였습니다.

2. JavaScript의 경우 protobuf.js를 사용했습니다. 여기에서 소스와 지침을 찾을 수 있습니다.

 

Python, C # 등과 같은 대부분의 지원되는 프로그래밍 언어의 경우 Google의 프로토콜 컴파일러로 충분합니다. 그러나 JavaScript의 경우 protobuf.js가 더 좋습니다. 더 나은 문서화, 더 나은 지원 및 더 나은 성능을 제공하기 때문입니다. 또한 Google에서 제공하는 기본 라이브러리를 사용하여 성능 테스트를 실행했지만 JSON을 사용하는 것보다 결과가 나쁩니다.

 

자바를 이용한 파싱 및 직렬화

 

Protocol Compiler를 설치 한 후 다음 명령으로 Java 소스 코드를 생성했습니다.

protoc --java_out=./src/main/java/ ./src/main/resources/people.proto

프로젝트의 루트 경로에서이 명령을 실행하고 두 개의 매개 변수를 추가했습니다. java_out, ./src/main/java/를 Java 코드의 출력 디렉토리로 정의했습니다. 및 ./src/main/resources/people.proto는 .proto 파일의 경로였습니다.

 

생성 된 코드는 상당히 복잡하지만 다행스럽게도 사용법은 다릅니다. 컴파일 된 각 메시지에 대해 빌더가 생성됩니다. 그것이 얼마나 쉬운 지 확인하십시오 :

final Address address1 = Address.newBuilder()
        .setStreet("Street Number " + i)
        .setNumber(i)
        .build();

final Address address2 = Address.newBuilder()
        .setStreet("Street Number " + i)
        .setNumber(i)
        .build();

final Person person = Person.newBuilder()
        .setName("Person Number " + i)
        .addMobile("111111" + i)
        .addMobile("222222" + i)
        .addEmail("emailperson" + i + "@somewhere.com")
        .addEmail("otheremailperson" + i + "@somewhere.com")
        .addAddress(address1)
        .addAddress(address2)
        .build();

이러한 인스턴스만으로 메시지를 나타내므로 메시지를 교환 할 방법도 필요했습니다. Spring은 Protobuf에 대한 지원을 제공하며 Spring의 블로그와 Baeldung의 것과 같은 몇 가지 리소스가있어서 그 문제에 도움이되었습니다. Java 프로젝트에서와 같이 몇 가지 종속성이 필요합니다. 이것들은 Maven 프로젝트에 추가해야했던 것들입니다.

<dependencies>
    <!-- Spring Boot deps and etc above.. -->
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.1.0</version>
    </dependency>

    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java-util</artifactId>
        <version>3.1.0</version>
    </dependency>

    <dependency>
        <groupId>com.googlecode.protobuf-java-format</groupId>
        <artifactId>protobuf-java-format</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>

JavaScript를 사용한 파싱 및 직렬화

사용 된 라이브러리 인 protobuf.js를 통해 .proto 메시지를 JavaScript로 컴파일하고 이러한 메시지를 교환 할 수있었습니다. 내가해야했던 첫 번째 일은 의존성으로 설치하는 것이 었습니다. 이를 위해 Node.js와 NPM을 사용했습니다.

npm install -g protobufjs

위의 명령으로 pbjs 명령 행 유틸리티 (CLI)를 사용하여 코드를 생성 할 수있었습니다. 다음 명령은이 CLI를 사용한 방법입니다.

pbjs -t static-module -w commonjs -o \
    ./src/main/resources/static/people.js \
    ./src/main/resources/people.proto

JavaScript 코드를 생성 한 후 다른 도구 인 browserify를 사용하여 생성 된 코드와 protobuf.js를 단일 파일로 묶었습니다.

# Installing browserify globally to use wherever I want.
npm install -g browserify
# Running browserify to bundle protobuf.js and message objects together.
browserify ./src/main/resources/static/people.js -o ./src/main/resources/static/bundle.js

이를 통해 index.html 파일에 단일 종속성을 추가 할 수있었습니다.

<html>
<body>
  <!-- This has all my protobuf dependecies: three messages and protobuf.js code. -->
  <script src="bundle.js"></script>
</body>
</html>

마지막으로 번들을 참조한 후 Protobuf 엔드 포인트에 GET 및 POST 요청을 발행 할 수있었습니다. 다음 코드는 AngularJS HTTP GET 요청이므로 이해하기 매우 쉬워야합니다.

 

// Just a shortcut.
const People = protobuf.roots.default.demo.People;

let req = {
  method: 'GET',
  responseType: 'arraybuffer', // make it clear that it can handle binary
  url: '/some-protobuf-get-endpoint'
};
return $http(req).then(function(response) {
  // We need to encapsulate the response on Uint8Array to avoid
  // getting it converted to string.
  ctrl.people = People.decode(new Uint8Array(response.data)).person;
});

POST 요청도 간단합니다.

// Just populating some usual object literals.
let address = new Address({
  street: 'Street',
  number: 100
});

let person = {
  name: 'Some person',
  address: [],
  mobile: [],
  email: []
};

person.address.push(address);
person.mobile.push('(1) 732-757-2923');
person.email.push('someone@somewhere.com');

// Encapsulating the object literal inside the protobuf object.
let people = new People({
  person: [new Person(person)]
});

// Building the POST request.
let post = {
  method: 'POST',
  url: '/some-protobuf-post-endpoint',
  // Transforming to binary.
  data: People.encode(people).finish(),
  // Avoiding AngularJS to parse the data to JSON.
  transformRequest: [],
  headers: {
    // Tells the server that a protobuf message is being transmitted.
    'Content-Type': 'application/x-protobuf'
  }
};

// Issuing the POST request built above.
return $http(post).then(function() {
  console.log('Everything went just fine');
});

바이너리 데이터를 교환하기 위해 protobuf.js 라이브러리를 사용하는 것이 어렵지 않습니까? 원하는 경우 GitHub 리포지토리에서 직접 Protobuf와 JSON 성능을 비교하는 데 사용되는 JavaScript 코드를 확인할 수도 있습니다.

 

원문: https://auth0.com/blog/beating-json-performance-with-protobuf/

불러오는 중입니다...