Atsushi2022の日記

データエンジニアリングに関連する記事を投稿してます

Protocol Buffersを試してみる

今回は、この辺りを参考にしながらプロトコルバッファ(Protobuf)を試してみる。

tech.raksul.com

nansystem.com

developers.google.com

Protobufを調べていると、Message Packも目にすることが多い。

MessagePack、Protobufも両方ともシリアライズ・デシリアライズの機能があるので比較されるのだろう。

MessagePackがシリアライズ・デシリアライズの速度に重きを置いている。

一方、Protobufではシリアライズ・デシリアライズの速度はMessagePackと比べると遅いようだが、データ構造を記述して共有することができる。つまり、XMLJSONと同じようにスキーマを記述することができる。

uqichi.hatenablog.com

frsyuki.hatenablog.com

今回はProtobufで、どのようにスキーマを記述するのか、シリアライズ・デシリアライズをどうやるのかについて調べる。

Protobuf を Windows にインストール

何はともあれインストールする。

qiita.com

>protoc --version
libprotoc 3.21.9

>pip install protobuf
>pip list
Package                  Version
------------------------ ---------
protobuf                 4.21.6

.protoファイルを作成し、コンパイルしてみる

user.protoファイルを作成する。中身はこんな感じ。

syntax = "proto3";

package hands_on;

message GetUserRequest {
  int32 id = 1;
}

message User {
  int32 id = 1;
  string name = 2;
}

service UserApi {
  rpc GetUser (GetUserRequest) returns (User);
}

細かい説明は後述するので、ここでは概略のみ。

syntax = "proto3";は、Protocol Buffersのシンタックスのバージョンを指している。

次に、package hands_on;だが、これはhands_onという名前空間package)を定義している。

message GetUserRequest {...}では、GetUserRequestという名前のメッセージフォーマット(データ構造、スキーマ)を定義している。

次に、.protoファイルをコンパイルする。Pythonで利用できるuser_pb2.pyが作成される。

>protoc --python_out=./test user.proto
> dir test
2022/11/20  01:16    <DIR>          .
2022/11/20  01:16    <DIR>          ..
2022/11/20  01:16             1,239 user_pb2.py

user_pb2.pyの中身はコチラ。

# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler.  DO NOT EDIT!
# source: user.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nuser.proto\x12\x08hands_on\"\x1c\n\x0eGetUserRequest\x12\n\n\x02id\x18\x01 \x01(\x05\" \n\x04User\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t2>\n\x07UserApi\x12\x33\n\x07GetUser\x12\x18.hands_on.GetUserRequest\x1a\x0e.hands_on.Userb\x06proto3')

_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'user_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:

  DESCRIPTOR._options = None
  _GETUSERREQUEST._serialized_start=24
  _GETUSERREQUEST._serialized_end=52
  _USER._serialized_start=54
  _USER._serialized_end=86
  _USERAPI._serialized_start=88
  _USERAPI._serialized_end=150
# @@protoc_insertion_point(module_scope)

main.pyを作成し、user_pb2.pyをインポートしてスキーマを試してみる。

main.py

import user_pb2 

user_rq = user_pb2.GetUserRequest()
user    = user_pb2.User() 

print("Request ID is", user_rq.id)
print("User ID is", user.id)
print("Username is", user.name ,"\n")

user_rq.id = 1
user.id    = 1
user.name  = "Mazda"

print("Request ID is", user_rq.id)
print("User ID is", user.id)
print("Username is", user.name ,"\n")

user_rq.id = 1
user.id    = "Honda"
user.name  = "Mazda"

print("Request ID is", user_rq.id)
print("User ID is", user.id)
print("Username is", user.name ,"\n")

これを実行した結果がコチラ。

> python .\main.py
Request ID is 0
User ID is 0
Username is

Request ID is 1
User ID is 1
Username is Mazda

Traceback (most recent call last):
  File "C:\Users\xxx\Documents\Protobuf\test\main.py", line 19, in <module>
    user.id    = "Honda"
TypeError: 'str' object cannot be interpreted as an integer

↑から、int型の初期値は0、string型の初期値は空文字になっていることがわかる。

スキーマでint型と定義した変数にstring型を入れようとするとTypeErrorとなる。

このようにスキーマ定義に沿わないとちゃんとエラーにしてくれる。

次にmain2.pyを作成して、シリアライズ、デシリアライズを試してみる。

main2.py

import user_pb2 

user    = user_pb2.User() 

user.id    = 1
user.name  = "Mazda"

serialized = user.SerializeToString()
print(serialized)

deserialized_user = user_pb2.User() 
deserialized_user.ParseFromString(serialized)
print(deserialized_user)

実行結果は下記の通り。シリアライズして、デシリアライズできている。

> python main2.py
b'\x08\x01\x12\x05Mazda'
id: 1
name: "Mazda"

スキーマの説明

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

syntax = "proto3";は、Protocol Buffersのシンタックスのバージョンを指している。

Protocol Buffersのシンタックスバージョンは、現在proto2proto3がある。

デフォルトはproto2なので、proto3を使用する場合はこのように指定する必要がある。

メッセージSearchRequestのなかに、3つのフィールドが定義されている。フィールドにはデータ型と名前が定義される。

フィールドは、メッセージ内でユニークな番号(フィールド番号)を持っている。フィールド番号はバイナリフォーマットにしたときにフィールドを特定するのに利用される。

詳細については以下が参考になりそう。

developers.google.com

qiita.com

本格的に使うことになったら、以下のGoogleのドキュメント(詳細な言語仕様)を読む。

developers.google.com

簡単だけど以上。