ブログ・ア・ラ・クレーム

技術的なメモとかライフログとか。

protobuf の Marshal/Unmarshal や Any 型について確認していた

あるメッセージの送り元と送り先でスキーマを共有してそのスキーマに基づいたメッセージをやり取りしたいことが多々あると思います。 そんな際にスキーマの記述を protobuf で行い、かつメッセージをシリアライズして送る構想について調査したメモを本稿で記載します。

背景

IoT だとか Microservices Architecture だとかウェブ・アプリサービスのユーザ行動分析だとか、AWS,GCP だとか何とか、世の中には多くのデータを生成するソースやデータ格納ストレージが生まれてきています。 そんな中、どのようなスキーマ・フォーマットでデータを送信するかという点は未だ多くの開発者を悩ませているのではと考えます。

これがビジネス上スケジュールの要件が厳しかったり、プロダクトの変化が大きかったり、データ送信ロジックを書く開発者が蓄積したデータを活用するシーンでサポートできるのであれば、スキーマレスデータベースにひとまずデータを格納しまくるのも良いかと思います。 しかしながら個人的にはそうではない状況や、将来的な組織・人員の拡充を考えるとスキーマがあって欲しい気持ちが高まります。 とはいえ、なるべくスキーマの変更には柔軟に対応できるようにあって欲しいものです。

f:id:syu_cream:20180605015503p:plain

スキーマ定義をどうするかについて最初に思いついたのは JSON Schema なのですが、人が読み書きするのには適さないと感じます。 その次に本稿で触れる、最近は gRPC メソッド定義などでもよく用いられる protobuf によるスキーマ定義を思いつきました。 (実際にいくつかのサンプルに触れる前に、以下の記事に影響を受けました。 protobuf にこれから入門してみたい方は読んでみていただけると!)

今さらProtocol Buffersと、手に馴染む道具の話

確認したかったこと

protobuf によるスキーマ定義自体は上記の記事より良さげだなと思っていたのですが、スキーマの更新の柔軟性、具体的には以下の点が気になっていました。

  • メッセージのフィールドを増やした際の受信側の挙動
  • タグの値が書き換わるような更新が起こった際の受信側の挙動
  • Any による型情報付きメッセージの受信側の挙動

これらを以下のシナリオのもと動作確認してみることにします。

  • Twitter みたいなサービスのユーザの行動ログを protobuf で定義する
  • 最初は v1 として Event 型で行動ログのスキーマを定義する
  • ある程度時期が経過した際、ツイートの URI のような追加情報を足したくなったので RichEvent という新しい型でログを扱うことにした
  • 更に時期が経過した際、根本的に構造を見直したくなり、新しい v1 Event 型を定義してログを扱うことにした
  • ログ送信コードを触る人と送信されたログを収集する人は異なり、システムも異なり、うまく連携できず受信側のスキーマ更新作業が遅れる場合がある

確認結果

NOTE: ここでは Go の protobuf packages を使ったりすること前提で書いています

実験用コードを以下に記載します。

github.com

異なるバージョン間の Marshal/Unmarshal

あまり綺麗にまとまっていませんが、以下のような形になりました。フィールドのタグと型が一致している部分に関してはバージョン更新が行われなかった場合でも最低限拾ってくれます。 しかしながら protobuf は NON NULL 指定をして Unmarshal 時にエラーを起こすようなことができない(はず?なにか良い方法があればコメントください)ので各言語向けに protoc plugin が吐き出す getter などで自前でチェックする必要がありそうです。

Check handling between messages might be compatible :
v1 -> v1.5
id:1 created_at:<seconds:1528128844 nanos:914007623 > event_type:TWEET user_id:1 value:"test tweet!"
v1.5 -> v1
id:1 created_at:<seconds:1528128844 nanos:914007623 > event_type:TWEET user_id:1 value:"test tweet!"

Check handling between messages might be NOT compatible :
v1 -> v2
id:1 event_at:<seconds:1528128844 nanos:914007623 > event_type:TYPE_TWEET
v2 -> v1
id:1 created_at:<seconds:1528128844 nanos:914007623 > user_id:1

Any

protobuf の Any 型は message の型情報を付与してくれるので、 Marshal するメッセージをこれで wrap してみます。 またメッセージを受け取る側では Unmarshal 前に型チェックを行うようにしてみます。 型チェックは Go では ptypes packageptypes.Is() で行えます。

これに基づき、 v2 Event を Unmarshal する際の動作は以下のようになりました。 v2 のメッセージを v1, v1.5 のメッセージとして Unmarshal しようとした際に ptypes.Is() のチェックが通らなくなっています。

Check handling Any :
type_url:"type.googleapis.com/logging.v2.Event" value:"\010\001\022\014\010\314\312\325\330\005\020\307\314\352\263\003\032\014\010\314\312\325\330\005\020\307\314\352\263\003 \001(\0020\0018\002B\013test tweet!"
Any is not loggingv1.Event
Any is not loggingv1.RichEvent
id:1 event_at:<seconds:1528128844 nanos:914007623 > processed_at:<seconds:1528128844 nanos:914007623 > event_type:TYPE_TWEET event_source:SOURCE_PUBLIC_TIMELINE user_id:1 user_agent:UA_IOS value:"test tweet!"

Any に wrap することによるバイナリサイズ増加

実験に用いた v1 Event 型で言うと、そのメッセージを直接 Marshal した場合と Any で wrap した場合とでは以下のようなサイズ差がありました。 当然ながら Any だと多少サイズが増えています。が、メッセージのフィールドが大きければ相対的に増加具合が目立たなくなるレベルになるかもしれません。

Check marshaled size w/ Any :
len(v1bin) : 33
len(v1anyBin) : 73

protobuf の周辺環境

夜に出ている protoc plugin を使って、 protobuf によるスキーマ定義を周辺ツールと連携して行うなど強力な開発・データ分析環境が構築できるかと思われます。

おわりに

protobuf によるスキーマ定義、なかなか良さそうに感じます。特に(メッセージサイズが増えることが許容できるなら) Any 型で包んでやり取りすると必要な際に簡単にメッセージ型チェックを行うことができ、便利だと思われます。 また protoc plugin も多数存在し、単に protobuf を直接使う以外にも他のスキーマ定義を生成するシンプルな DSL として使うのもありかも知れません(JSON Schema とか)。

この分野では Apache Avro と比較した検証ができていないので、どこかのタイミングでしてみたい気持ちもあります。

MySQL のテーブル定義から外部キー制約を推測するツールを作った

GW 中の自分へのプチ課題として、表題の通りのツール hakagi(葉鍵, Leaf and Key) を作りました。ツール名には特に意味はありません。

github.com

テーブル名やカラム名から外部キー制約を張れるような気がするカラムを選出し、制約追加のための ALTER TABLE をするクエリを吐き出したりします。

なぜ作ったのか

外部キー制約は言うまでもなくデータの不整合を避けるのに有用ですが、いくつかの理由からこれを用いない選択肢も存在すると思われます。 この辺りについては以下の記事なども参考にすると良さそうです。

外部キー制約を設けないことによるデメリットも幾つか生じると思っていて、その一つで自分が気になっている点として、「ER 図をツールで自動生成しにくい」があります。 例えば MySQL Workbenchsql ファイルや稼働する MySQL データベースから ER 図を自動生成してくれる機能を持ちますが、エンティティ間のリレーションは外部キー制約を無くすと追えなくなります。 この問題に対する緩和策として SchemaSpy を用いれば主キーをベースに外部キー制約を推測して ER 図生成を行ってくれます。 しかしながらこの推測のルールはあまり小回りが効くものではありません。

SchemaSpy のやっているような制約の推測を、図の自動生成とは分離してシンプルに扱い、かつルールを細かく追加したり ON/OFF したりしたい。 そう考え今回紹介するツールの実装を開始しました。

実装など

ワンバイナリでサクッと動かしたい希望があったため Go で書いています。 MySQL データベースからテーブル定義や主キーの情報を集めるため information_schema データベースを参照し、推測を行って制約追加クエリを標準出力に出します。 現在サポートする推測ルールは以下の通りです。

  • あるカラム名と同じ名前の、別のテーブルのカラムが存在し、かつそれが id という名前ではなくそのテーブルで主キーである場合外部キー制約を追加する
  • あるカラム名<別のテーブル名の単数形>_id であり、かつ別のテーブルで id が主キーである場合外部キー制約を追加する

標準出力に最終的に吐き出すフォーマットとしては今後は必要であれば PlantUML をサポートしたりしてもいいかも、と考えております。

動作例

以下のような、外部キー制約が全く設けられていないテーブル定義があったとします。

CREATE TABLE IF NOT EXISTS users (
  id    INT UNSIGNED NOT NULL AUTO_INCREMENT,
  name  VARCHAR(255) NOT NULL,
  age   INT UNSIGNED NOT NULL,
  email VARCHAR(255) NOT NULL,
  PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS contents (
  id    INT UNSIGNED NOT NULL AUTO_INCREMENT,
  title VARCHAR(255) NOT NULL,
  text  TEXT NOT NULL,
  user_id INT UNSIGNED NOT NULL,
  PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS content_comments (
  content_comment_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  text               TEXT NOT NULL,
  content_id         INT UNSIGNED NOT NULL,
  user_id            INT UNSIGNED NOT NULL,
  PRIMARY KEY(content_comment_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS content_comment_reactions (
  content_comment_id INT UNSIGNED NOT NULL,
  user_id            INT UNSIGNED NOT NULL,
  reaction_id        INT UNSIGNED NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS reactions (
  id        INT UNSIGNED NOT NULL AUTO_INCREMENT,
  image_url VARCHAR(255) NOT NULL,
  PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

これをベースに MySQL Workbench で ER 図を生成すると以下のようになります。

f:id:syu_cream:20180506200831p:plain

これに対し hakagi はいくつか外部キー制約を追加するクエリを吐き出してくれます。

$ ./hakagi -dbuser root -targets hakagi_example
ALTER TABLE content_comment_reactions ADD CONSTRAINT FOREIGN KEY (content_comment_id) REFERENCES content_comments(content_comment_id);
ALTER TABLE content_comment_reactions ADD CONSTRAINT FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE content_comment_reactions ADD CONSTRAINT FOREIGN KEY (reaction_id) REFERENCES reactions(id);
ALTER TABLE content_comments ADD CONSTRAINT FOREIGN KEY (content_id) REFERENCES contents(id);
ALTER TABLE content_comments ADD CONSTRAINT FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE contents ADD CONSTRAINT FOREIGN KEY (user_id) REFERENCES users(id);

このクエリも用いることで、以下のような ER 図を生成することができました。

f:id:syu_cream:20180506201000p:plain

おわりに

まだ少ないサンプルに対して少ない推測ルールを適用しているだけなので、誤った推測結果が出たり推測が足りなかったりする場面があるかもしれません。 このツールを用いる場面が数多くあれば、そこで出てきたユースケースを元にツールを機能拡張していきたいところです。

技術書典4にサークル名「まいにちがきんようび。」で暗号通貨とDNSの本を配布します 

直前のお知らせになりますが、 技術書典4 に「まいにちがきんようび。」というサークル名で参加して技術系薄い本を配布します!

techbookfest.org

以下のような内容になっています!

  1. 基礎から説明する暗号通貨 @lunatic_star
  2. 今日から始める IOTA @syu_cream
  3. エンジニアのための DNS(再)入門(初級者編) @m_bird
  4. エンジニアのための DNS(再)入門(中級者編) @kdmn

(表紙イラスト @fuga_romantica )

配布部数は 50 程度になる予定です。また販売価格は一冊 500 円を予定しています。 電子書籍での販売も予定しているため、もし売り切れるようなことがあれば(あるのかな...)そちらをご検討頂ければ幸いです。 ぜひ当日、会場にてお会い出来れば嬉しいです!!!

何気ない発言で師匠を傷付けるスマートコントラクトを実装してみた

はじめに

インチェックの例の騒動を受け、なんだか僕もブロックチェーンネットワーク上で何かワチャワチャしたくなってみました。 ちょうど Ethereum が気になっていたこともあり、ちまたには傑作アニメの発言が流行っていることも助けて、試しに発言ボット的なスマートコントラクトを実装してみることにしました。

ツール群

geth という Go 製 Ethereum CLI が有名っぽいのでこれを利用してみます。 --dev オプションを付与することで開発用の一時ネットワークを構築できるようです。

またスマートコントラクトの実装には Ethereum VM のコードを書くのに一番使われているらしい、 JavaScript っぽい構文にも見える Solidity を使ってみます。

ハマったこと

着手してみたのですが、Solidity 固有の?独特な attribute 指定とかがあってつらい感じでした。また文字列結合が + 演算子でサクッとできたりもしないようでつらみを覚えます。 とりあえず今回は solidity-stringutils でお茶を濁します。

成果物

以下になります。

github.com

不用意なメソッドをコールするといろいろな師匠を傷つけてしまいます。

> BBNM.mambo.call()
"Your casual \"mambo\" hurt a samba master."
> BBNM.ole.call()
"Your casual \"ole\" hurt a flamenco master."

ご注意ください。

第1回WSA研究会に参加してみた振り返り

あけましておめでとうございます。年末年始はほぼ寝て過ごしていました。

表題の通り、去年に 第1回WSA研究会 というイベントに参加したのでその振り返りなどをさくっと書いていきます。

第1回WSA研究会全体への雑感

発表内容は以下の通りで、名前の通り Web System Architecture の体系化や選定、流行りの Microservices 、ストレージに関する内容が多かった印象です。

websystemarchitecture.hatenablog.jp

イベント中ははてなさんの会議室の一角を利用して割と近い距離感のなか発表者が事前準備したスライドと共に発表し、聴衆が様々なコメントをしていく形式です。 発表時間15分,質疑応答15分というスケジュールのなか、発表自体も質疑もなかなかの盛り上がりをみせ、充実した時間になったのではないかと感じます。

このイベントで個人的に興味深かった特徴の一つとして、実稼働させた、あるいはさせようとしているシステムで抱える課題の延長線上に発表内容が立っていることが多かった点があります。 (特に matsumotory さんの FastContainer の話などは実稼働しているサービスで評価もしている)

個人的な振り返り

"Reusable TDI infrastructure" と題して、 markdown 付きインフラテストコードを実行する構想について話しました。予稿はこちら (業務と絡めるロジックが思いつかずプライベートで行ってます、ということで内容は個人の意見ですということで)

speakerdeck.com

反省点としてやはり既存技術のサーベイが結構不足していた点があります。 後から知ったことですが、 NII が Literate Computing for Reproducible Infrastructure とうたう構想で Jupyter Notebook ベースの Infrastructure as code 基盤を作っているようだったり、関連するソリューションは探せばもっと出てきそうです。

合わせて他の参加者の方へのコメントも積極的に行ってみたつもりなのですが、果たしてそれらがどれほど良いフィードバックになったかどうか。 余計なことを言っていないかどうかは内省すべきところかな・・・と感じました。

おわりに

普段あまり意識することの無い観点を意識でき、良い刺激になる会でした。 折角こういった議論の場が発足したわけですし、日々の業務の中でも課題の一般化を行いアウトプットをしていくという活動をより多く取り入れ、成果を持ち込めるようにしていきたいところです。

WSA 研 #1 予稿 "再利用性の高い Test Drive Intrastructure 実行環境に関する取り組み"

第1回WSA研究会 という催し物に出る予定でそれ用の予稿を書いたのですが、折角なのでブログにも掲載しておこうと思います。あとで更新するかもです。

再利用性の高い Test Drive Intrastructure 実行環境に関する取り組み

 Infrastructure as code や CI/CD の文化の広がりやインフラ構成の複雑化に伴い、 Test Driven Infrastructure といったインフラ構成をテスト可能にすることで安定稼働を目指す取り組みがなされている。

 Test Driven Infrastructure の実践方法としては 1) AnsibleChef などといった構成管理ツールに特化したテストツールを使う 2) Serverspec などといった構成管理ツールとは独立したツールを使う方針が考えられるが、前者はツールに非常に依存しかつ独特の DSL を要求される、後者はツール特有の学習コストが要求されるといった課題がある。

 実践方法とは別に、インフラ構成やテストに関する情報を残すことも重要である。これは GoogleSite Reliability Engineering の序論に記されている "実装は一時的なものだが文書化された理論は重要" との記述や、 Serverspec が可読性を重要視 して実装されたことからも汲み取れる。

 本研究ではこれらの背景を加味しつつ、前述の実践方針の 2) のような構成管理ツールとは独立した形で、かつ低い学習コストで Test Driven Infrastructure を目指す。 その実現方法として現在、 Harmonium というツールを試験的に実装している。このツールは現状、 Markdown に埋め込まれたシェルスクリプトを実行するツールである。 Harmonium によるテストコードの実行は bats と似たアプローチであるが、ドキュメントを Markdown で書けてかつ独自の記法を要求しないという差分がある。 MarkdownGitHub などのサービスの WebUI 上でレンダリング可能で運用上親和性も高く、テストコードに対する情報を付与しやすい。またシェルスクリプトを用いることで運用者はシェルの知識をそのまま利用することが可能で、別途 Ruby や Serverspec などのシンタックスを習得する必要がなくなる。 現在 Harmonium は golang で実装しており、ツールはワンバイナリで実行可能となるため、 Serverspec をローカルで実行する際に発生する Ruby や依存ライブラリを導入する手間などが軽減される。

参考

 参考にした文書やソフトウェア。本研究で提案する Harmonium は特に Jupyter Notebook に大きく影響を受けている。 Jupyter Notebook はノートブック形式で実装とは別に Markdown で文書を残し、他者と共有ができる。 Jupyter は Cloud Datalab でベースに用いられるなど、データ分析でよく使われるツールとなっている。

 余談だが Harmonium はブラウザ自動テストのためのツール Selenium やアプリ自動テストのためのツール Appium に触発されて命名した。

RocksDB の mruby binding "mruby-rocksdb" を作った

これは mruby Advent Calendar 2017 17 日目の記事です。

本記事は表題の通りの内容になります。 Advent Calendar のネタがどうしても思い浮かばず、ふと去年の自分の記事を振り返ってきたところ KVS の mrbgem を書いてた ので、コードをある程度使いまわして似たようなネタで行くかと考えた次第です。

RocksDB について

Facebook が公開している KVS です。 LMDB とか LevelDB 、 K2HASH と同じような、組み込みの KVS 型 NoSQL って分類できるものかなと思います。

http://rocksdb.org/

特徴としては、フラッシュストレージに対する書き込みのパフォーマンスに配慮されている点があるかなと思います。 さらなる詳細に関してはおそらく web 上に資料やベンチマーク結果などがシェアされていると思うのでそれらに譲ることにします。

mruby-rocksdb について

概要

RocksDB の mruby binding です。 リポジトリは以下の通り。

github.com

RocksDB が C++11 以降を要求することもあり C で書くのも面倒だな・・・と思ってしまったので C++11 以降前提で実装しています。 とは言っても半分くらいのメソッドは Ruby で実装してしまいました。だらしねぇな。

また mruby-k2hash と同様、サポートするメソッドは RubyDBM class ライクにしてあります。

使用例

mirb で触ってみるテストです。

$ ./bin/mirb
mirb - Embeddable Interactive Ruby Shell

# open
> db = RocksDB.new("./rocksdb")
 => #<RocksDB:0x101501570>

# store
> db['key1'] = 'value1'
 => "value1"

# fetch
> db['key1']
 => "value1"

> db['key2'] = 'value2'
 => "value2"

# each
> db.each do |k,v|; puts "key: #{k}, value: #{v}"; end
key: key1, value: value1
key: key2, value: value2
 => #<RocksDB:0x101501570>

# delete
> db.delete('key2')
 => nil

# etc ...
> db.to_hash
 => {"key1"=>"value1"}
> db.to_a
 => [["key1", "value1"]]
> db.map do |k,v|; k + ' map to ' + v; end
 => ["key1 map to value1"]

mirb で触ってみるテストでした。

おわりに

次はもっと書いたこと無いタイプの mrbgem 書いてみたいものです。