프로토콜 버퍼(Protocol Buffers, Protobuf)란?

  • Google에서 개발한 데이터 직렬화 도구입니다.
  • Json이나 XML 같은 다른 직렬화 형식에 비해 작고, 빠르며, 효율적입니다.
  • Protobuf는 주로 원격 프로시저 호출(RPC)에서 사용되며, 특히 gRPC에서 중요한 역할을 합니다.

출처: [https://narup.tistory.com/234](https://narup.tistory.com/234)

위 이미지는 첫번째 json 형태를 직렬화 Protobuf를 이용하여 직렬화 되는 과정을 나타내는 이미지 입니다.

프로토콜 버퍼(Protocol Buffers, Protobuf) 특징

1. 언어 중립적

  • 프로토콜 버퍼 정의는 특정 언어와 독립적입니다. 생성된 소스 코드는 다양한 프로그래밍 언어로 제공됩니다.(java, c++, python 등..)

2. 이진 포맷(데이터를 0, 1의 연속으로 표현하는 방식)

  • 데이터는 이진 포맷으로 직렬화되므로 전송 및 저장이 효율적입니다.

3. 확장 가능(필드 번호 이용)

  • 데이터 구조를 변경하더라도 이전 버전의 구조와 호환성을 유지할 수 있습니다.
  • 확장성과 호환성은 특히 분산 시스템, 마이크로서비스 아키텍처, 또는 단순히 시간이 지나면서 시스템의 요구 사항이 변경되는 경우에 중요한 특성입니다.
  • 프로토콜 버퍼(Protobuf)와 같은 이진 직렬화 포맷들은 확장성을 명확하게 지원하도록 설계되었습니다.
  • 이를 통해 개발자들은 시스템이 성장하고 변화해도 서로 다른 버전의 애플리케이션 간에 데이터를 안전하게 교환할 수 있습니다.
필드 번호란?
  • 프로토콜 버퍼에서는 각 필드에 고유한 번호가 할당됩니다.
  • 필드의 식별자로 사용되며, 각 번호는 직렬화와 역직렬화 과정에서 중요한 역할을 합니다.
  • 일단 번호가 할당되면 번호를 변경해서는 안됩니다.
필드 번호 역할 및 사용 방식

1) 식별자로서의 역할

필드 번호는 해당 필드의 고유 식별자로 동작합니다.
직렬화 시, 필드 이름이 아닌 필드 번호가 사용되어 데이터 스트림에 기록됩니다.
이렇게 함으로써, 데이터 전송 시의 크기가 줄어들게 됩니다.

 

2) 직렬화 및 역직렬화

데이터를 이진 형태로 직렬화 할 때, 필드 번호가 이진 스트림에 함께 기록됩니다.
반대로 데이터를 역직렬화 할 때, 이진 스트림에 기록된 필드 번호를 사용하여 해당 필드의 데이터를 올바르게 복원합니다.

 

3) 확장성 및 호환성

데이터 구조가 시간이 지나면서 변경되더라도 필드 번호는 일정하게 유지됩니다.
즉, 새로운 필드를 추가하거나 기존의 필드를 제거해도 해당 필드 번호는 바뀌지 않습니다.
이렇게 하면, 서로 다른 버전의 프로토콜 버퍼 구조 간에도 데이터의 호환성을 유지할 수 있습니다.

예를 들어, 초기 버전의 메시지 구조가 다음과 같다고 가정합니다.

 

message Person{
    string name = 1;
    int32 age = 2;
}

시간이 지나면서 'age' 필드가 더 이상 필요하지 않을 때, 그 필드를 제거할 수 있지만 필드 번호 2는 재사용해서는 안됩니다.

message Person{
    string name = 1;
    reserved 2;
}

또는 새로운 필드를 추가할 수도 있습니다.

message Person{
    string name = 1;
    int32 age = 2;
    string address = 3;
}

기존 'age'에 할당된 필드 번호 2는 변경되지 않았으므로, 오래된 버전과 새로운 버전모두 호환됩니다.

 

4) 필드 순서의 중요성

필드 번호는 필드의 순서나 위치와는 무관하게 동작합니다.
따라서 메시지 정의에서 필드의 순서가 바뀌더라도 필드 번호는 일정하게 유지되어야 합니다.

4. 정의된 스키마

'.proto' 파일에서 데이터 구조를 정의하게 됩니다.
이 파일은 데이터의 구조와 타입 정보를 포함하며, 이를 바탕으로 코드를 생성할 수 있습니다.

.proto 파일
  • 프로토콜 버퍼(Protobuf) 에서 데이터 구조를 정의하는 곳입니다.
  • 이 파일을 이용해서 특정 언어의 코드를 생성할 수 있습니다.
  • 데이터 교환의 규약 또는 계약처럼 동작하며, 다양한 시스템이나 언어 간의 통신에 있어서 일관된 데이터 구조를 보장합니다.

1) 메시지 정의

 

메시지는 여러 필드를 포함하는 구조체와 같습니다.
각 필드에는 데이터 타입, 필드 이름, 고유한 번호가 지정됩니다.

message Person{
    string name = 1;
    int32 age = 2;
}

 

2) 필드 타입

 

a. 정수 타입

  • int32, int64 : 순서대로 32비트, 64비트의 일반적인 정수 타입입니다.

b. 실수 타입

  • float, dobule : 순서대로 32비트, 64 비트의 부동소수점 타입입니다.

c. 문자열 및 바이트

  • string : UTF-8로 인코딩된 문자열 타입입니다.
  • bytes : 임의의 바이트 데이터를 포함하는 타입입니다.

d. bool(참, 거짓)

  • bool : 참 또는 거짓 값을 나타내는 타입입니다.

e. 열거형

  • enum : 명명된 값의 집합을 나타내는 타입입니다.

f. 필드 타입

  • 단일 필드
    기본적인 데이터 타입이나 복합 메시지 타입을 직접 사용하여 필드를 정의할 수 있습니다.
message Sample{
    int32 id = 1;
    string name = 2;
}
  • 반복 필드 (Repeated Fields)
    특정 필드 타입의 여러 값을 배열 또는 리스트 형태로 포함시킬 수 있습니다.
message Sample{
    repeated string tags = 1;
}
  • 옵셔널 필드
    Protobuf v2 에서는 optional 키워드를 사용하여 필드가 선택적으로 포함될 수 있음을 나타내야 했지만 Protobuf v3에서는 모든 필드는 기본적으로 선택적(옵셔널) 입니다.
  • 임베디드 메시지

다른 메시지 타입을 필드 타입으로 사용하여 중첩된 복합 구조를 만들 수 있습니다.

message Address{
    string city = 1;
    string state = 2;
}

message Person{
    string name = 1;
    Address address = 2;
}
  • Oneof

여러 필드 중 하나만 값을 가질 수 있을 때 사용하는 구조입니다.

message ContactInfo{
    oneof method{
        string phone_number = 1;
        string email = 2;
        string address = 3;
    }
}

위의 예에서 ContactInfo 메시지는 method라는 oneof 필드를 가지며, 이 필드는 phone_number, email, address 중 하나만 값을 가질 수 있습니다.

 

oneof를 사용하면 어떤 필드 중에서 하나만 값이 있어야 함을 명확하게 나타낼 수 있습니다.
이는 데이터의 무결성을 보장합니다.

 

oneof 내의 필드들은 공유 메모리를 사용하기 때문에 메모리 사용량이 절약됩니다.
즉, 한번에 하나의 필드만 값이 할당되므로, 여러 필드에 동시에 메모리를 할당할 필요가 없습니다.

 

만약 프로그램이 실수로 두 개 이상의 필드에 값을 할당하려고 하면, 마지막으로 할당된 필드의 값만 유지되며, 그 전의 필드 값들은 모두 삭제됩니다.

이를 통해 오류를 방지할 수 있습니다.

 

3) 서비스 정의

.proto 파일에서는 RPC(Remote Procedure Call) 서비스를 정의할 수도 있습니다.
이를 통해 클라이언트와 서버 간의 통신 규약을 명확하게 지정할 수 있습니다.

service UserService{
    rpc CreateUser (UserRequest) returns (UserResponse);
}

message UserRequest{
    string username = 1;
}

message UserResponse{
    int32 user_id = 1;
}

 

4) 코드 생성

.proto 파일을 정의한 후, 프로토콜 버퍼 컴파일러(protoc)를 사용하여 특정 언어로 코드를 생성할 수 있습니다.
예를 들어, Java 용 코드를 생성하려면

protoc --java_out=./ yourfile.proto

이렇게 생성된 코드는 프로토콜 버퍼 메시지의 직렬화, 역직렬화, 데이터 접근 등의 기능을 제공합니다.

 

5) 확장 및 임포트

.proto 파일 내에서 다른 .proto 파일을 임포트하여 정의된 메시지나 타입을 사용하거나 확장할 수 있습니다.

import "other_definitions.proto";

message ExtendedPerson {
    Person base = 1;
    string address = 2;
}
반응형

'개발 > MSA' 카테고리의 다른 글

ConnectionPool 이란? - 설정 방법  (0) 2023.09.18

+ Recent posts