ProtoBuf에 대한 정리 와 예제를 통한 사용 방법 확인

4 minute read

Protocol Buffer를 왜 쓰는가?

Protocol Buffer가 무엇인지 알기 위해 Protocol Buffer를 사용하지 않을 경우 어떻게 다른 서비스 간 데이터 교환을 하는지 알고 있으면 이해하기 쉽다.

기존 방식의 구조화 된 데이터 Serialize 방법

  1. Java Serialization 과 같은 언어에서 지원하는 Serialization 사용
  2. JSON or XML을 이용하는 방법

개발을 하다 보면 흔히 우리는 위 2가지 방식을 사용해서 데이터를 주고 받을 것이다. 하지만 위 2가지는 아래와 같은 단점을 가진다.

기존 방식의 문제점

  1. Java Serialization 과 같은 특정 언어에서 지원하는 직렬화를 사용시 특정 언어에 종속된다. 예를 들어 Java의 직렬화를 사용하면 데이터를 받는 쪽도 Java로 구현되어야 데이터를 주고 받을 수 있다.
  2. JSON or XML 사용 방식은 큰 부하를 발생 시킨다. 예를 들어 JSON을 응답 결과로 전달 한다고 생각해보자. 우리는 Spring이나 django와 같은 웹 프레임워크의 도움을 받아 간편한게 객체 값을 JSON으로 파싱하거나 JSON을 객체로 파싱하여 사용 할 수 있다. 하지만 내부적으로는 파싱을 위해 상당한 리소스를 사용해야 할 수 도 있다.

위 문제점을 해결하기 위해 사용하는게 Protocol Buffer이다.

Protocol Buffer란?

위의 Protocol Buffer를 왜 쓰는가?를 보면 대충 Protocol Buffer가 무엇인지 감을 잡을 수 있을거 같다.
좀 더 확실하게 정리해보자면 Protocol Buffer는 언어 및 플랫폼 중립적인 직렬화 메커니즘이다. 간단하게 JSON과 XML 정도로 생각해도 되지만 동작 방식은 전혀 다르다. 바로 아래에서 동작 방식에 대해서 알아보자.

Protocol Buffer 동작 방식

{
    "userName": "Martin",
    "favouriteNumber": 1337,
    "interests": ["daydreaming", "hacking"]
}

위와 같은 JSON 결과 값이 있다고 하자. 이 결과 값을 응답 결과로 전송하게 되면 82바이트가 사용된다.

message Person {
    required string user_name        = 1;
    optional int64  favourite_number = 2;
    repeated string interests        = 3;
}

위와 같은 Person이라는 Proto 정의가 있을 때 ProtoCol Buffer는 아래 그림 처럼 동작하게 된다.

우선 JSON은 데이터 값을 제외하고도 userName, favouriteNumber와 같은 필드 값도 포함을 하지만 Protocol Buffer의 경우 해당 필드 명을 Person 메시지의 변수에 정의한 숫자 1,2,3를 사용해 필드명을 대체 한다. 이런 식으로 줄일 수 있는 부분들은 최대한 줄여서 데이터의 용량을 줄이는 것이다.

Protocol Buffer 특징

  1. 바이너리 전송
    Protocol Buffer는 데이터가 바이너리 형식으로 전송되어 적은 공간과 대역폭을 차지하기 때문에 JSON 혹은 XML으로 파싱된 문자열보다 전송 속도를 향상 시킨다. 대신 바이너리로 전송되기 때문에 사람이 읽을 수 없는 단점이 존재하지만, 성능을 고려해 볼 때 충분히 감안할 수 있는 단점이라고 생각이 든다.
  2. 필드와 값의 분리 동작 방식에서 보았듯이 JSON의 경우 필드값과 데이터 값을 가지지만, Protocol Buffer의 경우 정의된 플래그 값으로 대체 시켜 버린다.
  3. 언어 및 플랫폼 선택에 자유롭다. 언어 및 플랫폼에 종속된 직렬화 방식이 아니라, Protocol Buffer의 고유 직렬화 방식이므로 언어 및 플롯폼과 관련이 없다.

Protocol Buffer의 .proto 분석하기

아래는 Protocol Buffer를 사용하기 위해 메시지를 정의한 .proto 파일의 내용이다.
주석을 이용해서 각 선언이 무슨 의미를 가지는지 설명한다.

// 사용할 proto 버전에 따른 syntax 정의, proto2 와 proto3이 있다. 
syntax = "proto2";

//다른 프로젝트간 이름 충돌방지를 위한 선언
package tutorial;

//java_package 선언은 생성될 메시지 관련 클래스가 위치할 패키지 경로를 명시한다. 
//명시적으로 선언하지 않으면 package에 선언된 내용이 자동으로 설정된다. 
//예제의 경우 tutorial 로 설정이 된다. 
option java_package = "com.example.tutorial";
//java_outer_classname 선언은 생성 될 클래스 이름을 명시한다. 
//명시적으로 선언하지 않으면 .proto 파일의 파일명이 자동으로 설정된다. 
//예를 들어 MyTest.proto 라는 파일 명이라면 java_outer_classname은 MyTest가 된다.  
option java_outer_classname = "AddressBookProtos";

//message를 정의하는 방법
message Person {
  //required : 필수 요소라는 의미
  required string name = 1; // 숫자는 unique tag로서 이진 인코딩 시 사용된다. 
  required int32 id = 2;
  //optional : 선택적 요소라는 의미
  optional string email = 3;

  //enum을 사용할 수 있다. 
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  //메시지 안에 메시지를 표현 할 수 있다. 
  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  //repeated : 복수개를 가진다는 의미, java에서는 List<Person> 으로 표현된다.
  repeated Person people = 1;
}

Protocol Buffer 사용해보기

이제 Protocol Buffer를 사용해서 자바에서 객체를 만드는 간단한 예제를 만들어 보자.

proto compile 설치

우선 Protocol Buffer를 사용하기에 앞서 .proto 파일을 컴파일 하기 위한 컴파일러를 다운 받아야 한다. 여기에 접속하여 사용하는 운영체제에 맞는 컴파일러를 다운 받아서 환경 변수에 등록해준다. 정상적으로 환경 변수가 등록되면 같은 protoc --version 명령어를 입력하면 버전 정보를 확인 할 수 있을 것이다.

.proto 파일 작성하기

프로젝트 특정 위치에 Person.proto 파일을 만들고 아래의 내용을 저장한다.

syntax = "proto3";

package com.cwh.protobuf.tutorial;

option java_package = "com.cwh.protobuf.tutorial";
option java_outer_classname = "PersionProtos";

message Person {
    required string name = 1;
    int32 id = 2;
    string email = 3;
}

해당 예제의 프로젝트 구성은 다음 그림과 같다.

이제 resources폴더에 있는 Person.proto 파일을 컴파일 해보자. 컴파일 명령어는 아래와 같다.

protoc –java_out=./java ./resources/Person.proto

이 명령어를 동작 시키면 .proto 안에 명시한 java_package 경로에 java_outer_classname 이름을 가진 클래스가 자동으로 만들어 지는 것을 확인 할 수 있고, 컴파일을 통해 생성된 클래스(PersonProtos)로 객체(Person)를 만들어 낼 수 있다. 객체를 만드는 방법을 new 키워드를 통해 생성이 불가능 하며 빌더를 이용해서 객체를 생성해야 한다.

package com.cwh.protobuf.tutorial;

import com.cwh.protobuf.tutorial.PersonProtos.Person;

public class ProtoBufTutorialApp {
  public static void main(String[] args) {
    //빌더를 이용한 객체 생성
    Person person = Person.newBuilder()
            .setId(1)
            .setName("조우현")
            .setEmail("aq3aq4@gmail.com")
            .build();
    
    System.out.println("-------");
    System.out.println(person.getId());
    System.out.println(person.getName());
    System.out.println(person.getEmail());
    System.out.println("-------");
  }
}

결론

여기까지 간단하게 Protocol Buffer가 무엇이며 어떻게 사용하는지에 대해서 알아보았다. 아직 예제가 미흡한 부분이 너무 많기 때문에 Protocol Buffer를 활용해 내가 궁금한 몇가지 상황에서의 예제를 좀 더 만들어 보고자 한다.
해당 예제는 여기서 확인 할 수 있다.

참고 자료

공식문서
https://codeclimate.com/blog/choose-protocol-buffers/
벤치마크 관련
https://developers.google.com/protocol-buffers/docs/javatutorial

Comments