프로그래밍/grpc

protocol buffers(protobuf) 톺아보자. (는 훼이크, 간단히 알아보자)

seungdols 2023. 7. 21. 16:03

https://protobuf.dev/

Protocol Buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.
It’s like JSON, except it’s smaller and faster, and it generates native language bindings. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
Protocol buffers are a combination of the definition language (created in .proto files), the code that the proto compiler generates to interface with data, language-specific runtime libraries, and the serialization format for data that is written to a file (or sent across a network connection).

Kotlin Generated Code Guide

Java Generated Code Guide

gradle plugin

protobuf2 vs protobuf3

protobuf2와 protobuf3는 모두 Google에서 개발한 데이터 직렬화 형식입니다. protobuf2는 2008년에 출시되었고, protobuf3는 2015년에 출시되었습니다. protobuf2와 protobuf3의 주요 차이점은 다음과 같습니다.

  • protobuf2는 필드 옵션이 제한적입니다. 예를 들어, protobuf2는 필드의 기본값을 설정할 수 없습니다.
  • protobuf2는 메시지의 순서를 보장하지 않습니다.
  • protobuf3는 필드 옵션이 풍부합니다. 예를 들어, protobuf3는 필드의 기본값을 설정할 수 있습니다.
  • protobuf3는 메시지의 순서를 보장합니다.

protobuf3는 protobuf2의 모든 기능을 포함하고 있으며, 그 외에도 여러 가지 새로운 기능을 제공합니다. 따라서 protobuf3는 protobuf2보다 더 강력하고 유연한 데이터 직렬화 형식입니다.

protobuf 파일 생성 규칙

protobuf 파일 이름의 규칙은 다음과 같습니다.

  • 파일 이름은 소문자로 시작해야 합니다.
  • 파일 이름은 공백이나 특수 문자를 포함해서는 안 됩니다.
  • 파일 이름은 .proto로 끝나야 합니다.

예를 들어, protobuf 파일의 이름은 다음과 같습니다.

  • my_proto.proto
  • my_service.proto
  • my_message.proto

protobuf 파일 이름은 정의하는 메시지 또는 서비스의 이름을 나타내야 합니다.

schema registry with protobuf

protobuf syntax

syntax = "proto3"

message SearchReqeust {
    string query = 1;
    int32 page_number = 2;
    int32 results_per_page = 3;
}
  • 첫 줄에는 proto3를 명시하는 구문을 넣어주어야 프로토콜 버퍼 컴퍼일러가 syntax의 버전을 유추 할 수 있다. 비어 있으면, proto2 버전으로 해석한다.
  • message 정의 구문에 3개의 필드가 정의 되어 있는데, 각 필드는 이름과 타입을 갖는다.
    • scalar type만 가지고 있다.
  • 필드 번호를 지정 해주어야 하는데, 해당 번호는 해당 메시지의 모든 필드에서 유니크 해야 한다.
  • 1 ~ 526,870,911 까지 지정 가능하다.
  • 필드 번호를 재사용하면 안된다.

Field Labels

  • optional
    • 값이 설정 되어 있는 경우, 직렬화 됨.
    • 값이 설정 되어 있지 않은 경우, 기본 값 설정 되며, 직렬화 되지 않음.
  • repeated
    • 0회 이상 반복 가능
  • map
  • implicit field presence
    • 명시적인 라벨은 아니지만, 암시적인 필드 존재로 인지,
    • 기본 값이 아닌 이상 직렬화 됨.

Message Types

/*
 * Comments
 */
message SearchRequest {
  string query = 1;
  int32 page_number = 2; // page_number is number of current page
  int32 results_per_page = 3;
}

message SearchResponse {
 ...
}

Deleting Fields

  • 클라이언트 참조 된 필드가 모두 삭제 되면, 필드 삭제 가능
  • 삭제 된 필드의 넘버는 예약 해두어야 함.

Reserved Fields

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

What's Generated From your .proto?

Scalar Value Type

Default value

  • String => empty string ""
  • bytes => empty bytes []
  • bools -> false
  • numeric => 0
  • enums => 첫번째 enum value의 값이고 무조건 0이어야 한다.
  • message filed => 필드가 설정 되지 않으면, 언어에 의존적이다. (ref. https://protobuf.dev/reference/)

Enumerations

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 results_per_page = 3;
  Corpus corpus = 4;
}

---

enum EnumAllowingAlias {
  option allow_alias = true;
  EAA_UNSPECIFIED = 0;
  EAA_STARTED = 1;
  EAA_RUNNING = 1;
  EAA_FINISHED = 2;
}

enum EnumNotAllowingAlias {
  ENAA_UNSPECIFIED = 0;
  ENAA_STARTED = 1;
  // ENAA_RUNNING = 1;  // Uncommenting this line will cause a warning message.
  ENAA_FINISHED = 2;
}

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}
  • enum의 첫번째 상수는 0에 매핑 된다. 모든 열거형 정의에는 첫번째 요소로 0에 매핑 되는 상수가 포함 되어야 한다.
  • proto2의 sematic 호환성을 위한 규칙
  • option allow_alias = true;를 넣으면 별칭을 사용 할 수 있음.
    • 그렇지 않으면, 컴파일러가 오류를 명시하게 될 수 있음. (proto 파일 여러곳에 있는 경우)
      • 직렬화/역직렬화 시, 언어에 의존 되는 경향이 있으므로 언어적 특성에 따라 enum이 변환 됨.
      • https://protobuf.dev/programming-guides/enum/
      • 필드 넘버를 max까지 예약 상태로 만들 수 있음.

Using Other Message Types

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

import "myproject/other_protos.proto";

Using proto2 Message Types

  • proto2에서 작성 된 파일을 가져올 수 있으며, 그 반대도 가능하다.
    • 단, enum의 경우 proto3 문법에서 직접 호출 할 수 없다.

Nested Types

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}
message SomeOtherMessage {
  SearchResponse.Result result = 1;
}
message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

Updating A Message Type

  • https://protobuf.dev/programming-guides/proto3/#updating
  • 기존 필드의 필드 번호는 변경하지 마세요. "필드 번호를 '변경'하는 것은 필드를 삭제하고 동일한 유형의 새 필드를 추가하는 것과 같습니다. 필드 번호를 변경하려면 필드 삭제 지침을 참조하세요.
  • 새 필드를 추가하는 경우 "이전" 메시지 형식을 사용하여 코드로 직렬화된 모든 메시지는 새로 생성된 코드에서 계속 구문 분석할 수 있습니다. 새 코드가 이전 코드에서 생성된 메시지와 제대로 상호 작용할 수 있도록 이러한 요소의 기본값을 염두에 두어야 합니다. 마찬가지로 새 코드에서 생성된 메시지도 이전 코드에서 구문 분석할 수 있습니다. 이전 바이너리는 구문 분석 시 새 필드를 무시하기만 하면 됩니다. 자세한 내용은 알 수 없는 필드 섹션을 참조하세요.
  • 필드 번호가 업데이트된 메시지 유형에서 다시 사용되지 않는 한 필드를 제거할 수 있습니다. 대신 필드 이름을 바꾸거나 접두사 "OBSOLETE_"를 추가하거나 필드 번호를 예약으로 설정하여 향후 .proto 사용자가 실수로 번호를 재사용하지 못하도록 할 수 있습니다.
  • int32, uint32, int64, uint64, bool은 모두 호환되므로, 앞뒤 호환성을 깨뜨리지 않고도 이러한 유형 중 하나에서 다른 유형으로 필드를 변경할 수 있습니다. 와이어에서 해당 유형에 맞지 않는 숫자를 구문 분석하면 C++에서 해당 유형으로 숫자를 캐스팅한 것과 동일한 효과를 얻을 수 있습니다(예: 64비트 숫자를 int32로 읽으면 32비트로 잘립니다).
  • sint32와 sint64는 서로 호환되지만 다른 정수 유형과는 호환되지 않습니다.
  • 문자열과 바이트는 바이트가 유효한 UTF-8인 한 호환됩니다.
  • 포함된 메시지는 바이트에 메시지의 인코딩된 버전이 포함된 경우 바이트와 호환됩니다.
  • fixed32는 sfixed32와 호환되며, fixed64는 sfixed64와 호환됩니다.
  • 문자열, 바이트 및 메시지 필드의 경우 optional는 repeated와 호환됩니다. 반복되는 필드의 직렬화된 데이터가 입력으로 주어지면 이 필드가 선택 사항일 것으로 예상하는 클라이언트는 기본 유형 필드인 경우 마지막 입력 값을 가져오고 메시지 유형 필드인 경우 모든 입력 요소를 병합합니다. 일반적으로 부울 및 열거 형을 포함한 숫자 유형의 경우 이러한 방식은 안전하지 않습니다. 숫자 유형의 반복 필드는 팩 형식으로 직렬화될 수 있으며, 선택적 필드가 예상되는 경우 올바르게 구문 분석되지 않습니다.
  • 열거형은 와이어 형식 측면에서 int32, uint32, int64 및 uint64와 호환됩니다(맞지 않는 경우 값이 잘립니다. 참고). 그러나 메시지가 역직렬화될 때 클라이언트 코드에서 다르게 처리될 수 있다는 점에 유의하세요. 예를 들어, 인식할 수 없는 proto3 열거형 유형은 메시지에 보존되지만 메시지가 역직렬화될 때 이를 표현하는 방식은 언어에 따라 달라집니다. Int 필드는 항상 해당 값만 보존됩니다.
  • 단일 선택적 필드 또는 확장자를 새 필드의 멤버로 변경하는 것은 바이너리 호환이 가능하지만, 일부 언어(특히 Go)의 경우 생성된 코드의 API가 호환되지 않는 방식으로 변경됩니다. 이러한 이유로 Google은 AIP-180에 문서화되어 있는 대로 공개 API에서 이러한 변경을 하지 않습니다. 소스 호환성에 대한 동일한 주의 사항으로, 한 번에 두 개 이상의 필드를 설정하는 코드가 없다는 것이 확실하다면 여러 필드를 새 필드로 이동하는 것이 안전할 수 있습니다. 기존 필드로 필드를 이동하는 것은 안전하지 않습니다. 마찬가지로 단일 필드 중 하나를 선택적 필드 또는 확장으로 변경하는 것은 안전합니다.

protobuf에서 field number를 변경하면 안되는 이유는 다음과 같습니다.
  • protobuf는 field number를 기반으로 메시지 데이터를 저장하고 처리합니다. 따라서 field number를 변경하면 메시지 데이터의 구조가 변경되어 메시지를 읽거나 쓰는 데 문제가 발생할 수 있습니다.
  • protobuf는 field number를 기반으로 메시지 데이터를 비교합니다. 따라서 field number를 변경하면 메시지 데이터가 서로 다른 것으로 간주되어 오류가 발생할 수 있습니다.
  • protobuf는 field number를 기반으로 메시지 데이터를 검색합니다. 따라서 field number를 변경하면 메시지 데이터에서 특정 필드를 찾을 수 없게 되어 오류가 발생할 수 있습니다.

따라서 protobuf에서 field number를 변경하는 것은 피해야 합니다. 만약 field number를 변경해야 한다면, 해당 메시지를 사용하는 모든 클라이언트와 서버를 업데이트해야 합니다.

protobuf에서 field name을 재사용할 수 있습니다. field name은 메시지에서 필드의 이름을 나타내는 데 사용됩니다. field name이 재사용되더라도 메시지에서 필드의 순서가 변경되지 않으므로 오류가 발생하지 않습니다.

그러나, field name을 재사용할 때는 다음과 같은 점에 유의해야 합니다.

  • field name은 중복되지 않도록 해야 합니다.
  • field name은 의미 있는 이름을 지정해야 합니다.
  • field name은 대소문자를 구분합니다.

field name을 재사용할 때는 이러한 점에 유의하여 중복되지 않고 의미 있는 이름을 지정해야 합니다.

protobuf에서 message Type을 업데이트하는 경우가 몇 가지 있습니다.
  • 새로운 필드를 추가하는 경우
  • 기존 필드의 데이터 타입을 변경하는 경우
  • 기존 필드를 제거하는 경우
  • message의 이름을 변경하는 경우

이러한 경우 message Type을 업데이트해야 합니다. message Type을 업데이트하지 않으면, 해당 메시지를 사용하는 클라이언트와 서버에서 오류가 발생할 수 있습니다.

message Type을 업데이트할 때는 다음과 같은 점에 유의해야 합니다.

  • message Type을 업데이트한 후에는 해당 메시지를 사용하는 클라이언트와 서버를 모두 업데이트해야 합니다.
  • message Type을 업데이트한 후에는 기존 데이터와의 호환성이 보장되지 않을 수 있습니다.

message Type을 업데이트할 때는 이러한 점에 유의하여 신중하게 업데이트해야 합니다.

unknown fields

  • 이전 바이너리가 새 필드를 포함된 새 바이너리가 보낸 데이터를 구문 분석 할때, 새 필드는 이전 바이너리에서 알 수 없는 필드가 된다.

unknown field는 다음과 같은 경우에 사용될 수 있습니다.

  • 메시지 형식이 변경되었지만, 기존의 데이터를 처리해야 하는 경우
  • 다른 프로토콜 버퍼 형식과 데이터를 교환해야 하는 경우
  • 특정 목적으로 사용되는 필드를 추가해야 하는 경우

Any

Currently the runtime libraries for working with Any types are under development.

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

다양한 유형의 protobuf 값을 보유할 수 있는 값입니다. Any는 protobuf 메시지, 숫자, 문자열 또는 기타 Any를 포함할 수 있습니다. Any는 다른 시스템과 통신할 때 유용합니다. 예를 들어, Any를 사용하여 다른 시스템에서 생성된 protobuf 메시지를 수신하고 처리할 수 있습니다. Any는 또한 protobuf 메시지를 저장하고 전송할 때 유용합니다. Any를 사용하여 protobuf 메시지의 유형을 명시하지 않고도 protobuf 메시지를 저장하고 전송할 수 있습니다.

Any는 다음과 같이 사용됩니다.

  • 다른 시스템과 통신할 때
  • protobuf 메시지를 저장하고 전송할 때
  • protobuf 메시지의 유형을 명시하지 않고도 protobuf 메시지를 저장하고 전송할 때

Any는 다음과 같은 이점을 제공합니다.

  • 다양한 유형의 protobuf 값을 보유할 수 있습니다.
  • 다른 시스템과 통신할 때 유용합니다.
  • protobuf 메시지를 저장하고 전송할 때 유용합니다.
  • protobuf 메시지의 유형을 명시하지 않고도 protobuf 메시지를 저장하고 전송할 수 있습니다.

Oneof

많은 필드가 있고 최대 하나의 필드만 동시에 설정되는 메시지가 있는 경우 oneof 기능을 사용하여 이 동작을 적용하고 메모리를 절약할 수 있습니다.

하나의 필드는 하나의 공유 메모리에 있는 모든 필드를 제외하고는 일반 필드와 같으며, 최대 하나의 필드만 동시에 설정할 수 있습니다. oneof의 멤버를 설정하면 다른 모든 멤버가 자동으로 지워집니다. 선택한 언어에 따라 특수 케이스() 또는 WhichOneof() 메서드를 사용하여 oneof의 어떤 값이 설정되어 있는지(있는 경우) 확인할 수 있습니다.

여러 값이 설정된 경우 프로토의 순서에 따라 결정된 마지막 설정 값이 이전의 모든 값을 덮어씁니다.

oneof 필드의 필드 번호는 묶는 메시지 내에서 고유해야 합니다.

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}
SampleMessage message;
message.set_name("name");
CHECK_EQ(message.name(), "name");
// Calling mutable_sub_message() will clear the name field and will set
// sub_message to a new instance of SubMessage with none of its fields set.
message.mutable_sub_message();
CHECK(message.name().empty());
  • 하나의 열거형 필드를 설정하면 열거형의 다른 모든 멤버가 자동으로 지워집니다. 따라서 여러 개의 oneof 필드를 설정한 경우 마지막으로 설정한 필드에만 값이 남게 됩니다.
  • 구문 분석기가 와이어에서 동일한 원 오브의 멤버를 여러 개 발견하면 마지막으로 표시된 멤버만 구문 분석된 메시지에 사용됩니다.
  • oneof는 repeated 될 수 없음.

Maps

map<key_type, value_type> map_field = N;
  • 맵 필드는 반복할 수 없습니다.
  • 맵 값의 와이어 형식 순서 및 맵 반복 순서는 정의되지 않았으므로 맵 항목이 특정 순서로 정렬되는 것에 의존할 수 없습니다.
  • .proto에 대한 텍스트 형식을 생성할 때 맵은 키별로 정렬됩니다. 숫자 키는 숫자순으로 정렬됩니다.
  • 와이어에서 구문 분석할 때 또는 병합할 때 중복된 맵 키가 있는 경우 마지막으로 표시된 키가 사용됩니다. 텍스트 형식에서 맵을 구문 분석할 때 키가 중복되면 구문 분석에 실패할 수 있습니다.
  • 맵 필드에 키만 제공하고 값을 제공하지 않는 경우 필드가 직렬화될 때의 동작은 언어에 따라 달라집니다. C++, Java, Kotlin 및 Python에서는 유형의 기본값이 직렬화되지만 다른 언어에서는 아무 것도 직렬화되지 않습니다.

JSON mapping

https://protobuf.dev/programming-guides/proto3/#json

QnA

service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

ref. https://stackoverflow.com/questions/30106667/grpc-protobuf-3-syntax-what-is-the-difference-between-rpc-lines-that-end-with-s

options

  • timeout
  • retry_codes
  • retry_intervals
  • failure_message_for_code
  • log_to_stderr
  • stub_status_on_completion

A)

service Greeter { 
// Sends a greeting 
    rpc SayHello (HelloRequest) returns (HelloReply) { 
        // Description of the return value 
        description = "A greeting message." 
    } 
}

service Greeter { // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) { 
        // Deprecated return value 
        @deprecated("Use the new greeting method instead.") 
        description = "A greeting message." 
    } 
}
반응형

'프로그래밍 > grpc' 카테고리의 다른 글

Grpc framework, protobuf 관리, grpc 기본 톺아보기  (0) 2023.07.31
grpc ui client (kreya, postman)  (0) 2023.07.21