Apache Avro について知っていることを書いていく その1
Apache Avro になにかと縁があり、かつ普及しているテクノロジーの割に日本語の情報がそんなにない(個人の意見です、意外とあるかも)のでつらつら書いてみます。 整理はされておらずシーケンシャルに要素を並べています。 実装についてとくに言及がされていなければ、 Java の 1.8.2 について触れているものとします。
Apache Avro はデータフォーマットとエコシステムのひとつ
Apache Avro は Apache トップレベルプロジェクトのひとつでファイルフォーマットとその周辺エコシステムです。 比較される技術として Protocol Buffers や Message Pack が挙げられると思います。 Apache Avro はそれらの中でも主に Hadoop エコシステムなどビッグデータ絡みの文脈でよく登場するように思えます。
以下のディレクトリ構成の通り、公式でいくつかのプログラミング言語をサポートしているようです。
筆者はほとんど Java のパッケージしか使わず正確な比較も行ってないのですが、おそらく Java 向けの機能がサポート厚めだと思われます。
avro-tools
が Java で書かれてたり avro-protobuf
という Protocol Buffers との変換ライブラリが提供されていたりするので。
また非公式ですが以下のような他言語向けライブラリもあったりします。
Avro はスキーマの表現力が強い
Avro はスキーマに従いシリアライゼーションとでシリアライゼーションをするわけですが、このスキーマの表現力が割と強いです。 雑に列挙すると以下の通りです。
record 型(構造体)のサポート
- エイリアスや doc 、 namespace の宣言もできる(オプション)
array 型(配列)のサポート
union 型(共用体)のサポート
- null 型との union を取り、デフォルト値を null にすることで nullable な型を表現できる
map 型のサポート
- enum 型のサポート
- fixed 型という固定長バイト配列のサポート(使ったこと無いのでよくわからん)
- デフォルト値が設定できる
logical types を使うことで型に別の解釈をもたせることができる
- メジャーな使い方として long 型に timestamp-millis logical type をもたせるなど
- timestamp の制度は micros も選べる
これらにより、かなりリッチなスキーマを記述することができるはずです。
Avro のスキーマを記述するのがつらい
表現力が高いこともあるのですが、基本的にスキーマは JSON で記述することになる上か結構冗長でかつ人間にとって読みにくい内容になりがちです。 公式リポジトリに簡単な例があるのですが、簡単なものでもある程度事前知識を求められるでしょう。 さらに型をネストし始めたら地獄です。 筆者は特殊な訓練を繰り返すことにより最近手でスキーマを書けるようになってきた気がしますが、このスキーマの記載を同僚に求めるのは酷なものです。
この問題はかなり認知されているのか、公式が IDL を提供しています。 一見 Protocol Buffers を彷彿とさせる気がするシンタックスですね。
また、プログラムから動的にスキーマを記述、生成することもできます。
Java であれば Schema.createRecord()
して List<Schema.Field>
オブジェクトを埋めて・・・といった流れで実現できます。
Avro 自体の圧縮効率はそんな高くない?
int, long は zigzag encoding してくれますが、 bytes, string は length + バイナリなどかなりシンプルなシリアライズをします。
結果としてシリアライズされたあとのバイナリのサイズは Protocol Buffers や Message Pack と比較してそんなに潰れないと思います。
(実測した訳でなく、単にこれまでの経験則程度の話です)
ただ Object Container Files
フォーマットを取るとブロック単位に圧縮をかけてくれたりします。
Object Container Files フォーマットの便利さ
あなたがデータアナリストやデータサイエンティストなどのポジションで働いているなら、このフォーマットで Avro と対面することが多いかもしれません。
.avro
拡張子をよく取る、 Object Container Files フォーマットに従うレイアウトを取ったファイルです。
実体は well-known な Avro スキーマに従ってシリアライズされたファイルになります。 そのスキーマの中にはレコードのスキーマとコーデック、フィンガープリント情報などが含まれます、 ここのスキーマ情報により、ファイルを読むことで中のバイナリがどんなスキーマでシリアライズされたかわかるので動的にデシリアライザを生成して読める状態になってるわけですね!すぐれもの!
このファイルフォーマットは BigQuery や Redshift でもサポートされており、 Hadoop エコシステムにあまり関わらずとも Avro のファイルをオブジェクトストレージに書き出しておけば、あとで DHW に統合して安全便利にクエリを投げられるわけですね!すぐれもの!
avro-tools が開発運用に便利
あまり言及されてない気がしますが、 avro-tools
という Java 製のツールが便利です。
事前定義した Avro のスキーマを記載した.avsc
ファイルから Specific クラスを自動生成するのはもちろん、 Object Container Files フォーマットのファイルから schema を読んで吐いてくれたりレコードを json に変換して出力してくれたりします。
特にレコードを json で出力してくれると、 jq などと組み合わせてフィルタしたり内容確認できたりして便利ですね。
飽きてきたので
本記事ではこれくらいにしておきます。 気力があったり次回書きたいことがまとまってきたらまた書きます。
「データとML周辺エンジニアリングを考える会」という勉強会の第二回を開催しました
TL;DR
2019/07/19(金)に、ヤフー株式会社様コワーキングスペースの LODGE において、「データとML周辺エンジニアリングを考える会」という勉強会の第二回目を開催しました。
データエンジニアリングとサイエンス、アナリティクスにざっくり被るような少し広く曖昧なドメインで開始した勉強会です。
今回は前回より少々多めで、 40 人強の参加者が参加してくれました。 発表、 LT に合わせて懇親会も議論が弾み、主催のひとりとしては実現したかったことがある程度できたのではないかと考えています。
つらつら発表内容
せっかくなので覚えているうちに発表資料のリンク記載と超個人的な感想載せてみます。
15 分枠
GCPでStreamなデータパイプライン運用しはじめた by @shoe116
メルカリでのログ収集のためのパイプラインの構築の話です。 マイクロサービスアーキテクチャへの移行やビジネス・組織のスケールに合わせた試行錯誤の跡が伺えます。 というかわたしも業務で参加してるやつです。実際上記の試行錯誤をしています。
行動ログ処理基盤の構築 by @hirosassa
サービスにおける行動ログの収集基盤の刷新の話です。 現行システムが pull 型で基盤側がサービスの内情を知ってしまう問題を、 push 型のアーキテクチャにしたあたりが今後の投資になりうるおもしろい点なのではと思います。 (基盤システムってどこまでサービスのことを知るか、責任分界をどこでするかしばしば悩ましくなりますよね)
LT 枠
Google Cloud ML Engineに浸かってみる by @yudeayase
GCP の ML Engine の話です。 ML Engine, 便利そうではあるもののこの仕組みに特化してしまうのは良いのか?などと考えさせられました。
(資料アップロードなし?あとで調べる
PoC案件が多すぎてつらいので、パイプラインを使いまわすツールを入れた。 by @mori_kaz0429
繰り返し発生する PoC 案件で、似たようなクエリを投げたりすることが多い処理を共通化、再利用可能にする話です。 最後の方には Apache Airflow などワークフローエンジンを今後使ってみたい話も。
(資料アップロードなし?あとで調べる
Cloud Composer & Cloud Dataflow によるバッチETLの再構築 by @yuzutas0
Cloud Composer (Apache Airflow のマネージドサービス) を使って壊れかけのデータ同期の仕組みを立て直す話です。 データエンジニアとデータアナリスト、両方に対してヒアリングをかけつつ現状を鑑みて良いバランスのところを攻めるというマネジメントに近い側面もあれば、 Cloud Dataflow によるクレンジング処理に触れたりする話もあり盛りだくさんでした。
DigdagでETL処理をする by @nakano_shota
今度は Digdag でワークフローを組んだ話です。 s3_touch というオペレーターを開発して、 s3_wait と組み合わせてプロジェクト間依存関係も対応できるようにしていて良い。 あまり深く触れられなかったけどリトライ処理や冪等性担保大事ですよね。もっとお話聞いてみたい気がします。
Comet.ml で AutoML ライブラリ開発(仮) by @Y_oHr_N
Comet.ml 、初めて知ったのですがかなり良さそう! codecov でカバレッジを可視化するのと同じようにモデルの可視化をできるのは好感触!
データ活用の際にハマってしまったログ・データスキーマ設計 by @yu-ya4
この手の苦労話、これこそこういう勉強会で話し合いたかったことな気がします。 テーブルの日付が何の時間を表すか問題、スキーマ更新にどう立ち向かうか、 STRING 型フィールドに JSON 突っ込むのどうなんだ話、 null の扱いと結構あるある話な気がするが・・・。 二番目の話題は個人的にも刺さるものがありました。簡易にアプリケーションからログを出力して、読み出す時に苦労するスキーマオンリードの戦略自体は間違ってはいないはずだし・・・。 触れられていなかった別の点として、 BigQuery はカラムナでデータを持ってくれているはずなのですが、 JSON を突っ込んで読む時にパースするではパフォーマンスが落ちる(課金が増える?)かもしれないなと思いました。
今後に向けて
幸いなことに参加者の方々からもそれなりに好評を得られたようだし、自分としても知見を得たい気持ちもあるため、主催メンバーで話し合いつつ第三回を企画していきたいと考えています。 ジャストアイデアですが、初心者枠?というかこれからデータ基盤を作っていこうとする人たちが発表しやすい枠を設けるとかもあるとイベントの雰囲気変わるかなとか。 その時が来たらまたアナウンスしますので、ご興味ある方々いらっしゃいましたらぜひぜひ!!
技術書典5で配布した同人誌の原稿データを GitHub で公開しました
Kubernetes と CSI(Container Storage Interface) について
この記事は Kubernetes2 Advent Calendar 4 日目の記事です。
本記事は CSI(Container Storage Interface) と Kubernetes での CSI のサポートについて触れます。 執筆に時間があまり割けなかった為、後でもう少し加筆する、あるいは別途続きの記事を書くかもしれません。
CSI(Container Storage Interface) とは
CSI(Container Storage Interface, 以下 CSI) は Kubernetes など複数の Container Orchestration (CO) から共通のプラグインを使って SP (Storage Provider) とやり取りするためのインタフェース仕様です。 より具体的には、 CO からストレージを抽象化した Volume をアタッチ・デタッチしたりスナップショットを取ったりする機能のためのインタフェースを提供します。 CSI は Kubernetes とは独立したプロジェクトで進行しております。
また Kubernetes のプロジェクト以下で、 Kubernetes から SP が提供する CSI プラグインへ通信するための CSIDriver と関連実装を以下プロジェクトで進行しています。
Kubertenes としては CSI は 1.9 で Alpha 、 1.10 で Beta サポート対象になっています また Kubernetes での CSI 利用については他に既に試されている方もいるようです。 こちらの記事は詳細に踏み込んだ構成説明などもされており、オススメできる内容になっています。
CSI の仕様
CSI のインタフェース仕様は Protocol Buffer で定義されており、以下リポジトリで管理されています。
またこのリポジトリ内の lib/go/
ディレクトリには .proto ファイルの定義に従ったライブラリの Go 実装が配置されています。
CSI のプラグインは Controller plugin, Node plugin の二種類が想定されております。 また CO とこれら CSI プラグイン間は gRPC で通信することが想定されています。
CSI のサービス
CSI のサービスとして以下三種類が想定されています。
構成によっては実装しなくても良いサービスやメソッドがありますが、少なくとも Node Service の NodePublishVolume
, NodeUnpublishVolume
は実装する必要があります。
(でないと Node から Volume を参照できないはず)
- Identity Service
- CO から Plugin のケーパビリティ (後述) やヘルスチェック、メタデータ参照を可能にするサービス
- Controller Service
- Node をまたいで Volume の構成を管理するためのサービス
- Volume のスナップショット操作もサポートする
- Node Service
- 各 Node から Volume の操作を可能にするためのサービス
CSI における Volume の状態遷移
CSI で Node から Volume を利用されるまでに、 Volume は基本的に以下のような状態遷移をしていきます。 幾つかの状態は、プラグインが示す後述の CSI ケーパビリティによって存在しなかったりします。
CreateVolume +------------+ DeleteVolume +------------->| CREATED +--------------+ | +---+----+---+ | | Controller | | Controller v +++ Publish | | Unpublish +++ |X| Volume | | Volume | | +-+ +---v----+---+ +-+ | NODE_READY | +---+----^---+ Node | | Node Stage | | Unstage Volume | | Volume +---v----+---+ | VOL_READY | +------------+ Node | | Node Publish | | Unpublish Volume | | Volume +---v----+---+ | PUBLISHED | +------------+
ref. https://github.com/container-storage-interface/spec/blob/master/spec.md#volume-lifecycle
CSI のケーパビリティ
CSI プラグインでどのような操作をサポートするかの情報です。 CSI の仕様としては以下 4 種類のケーパビリティをサポートします。
- PluginCapability
- VolumeCapability
- Volume のファイルシステムのタイプやアクセス制御(リードオンリーなのかなど)、容量などの情報を含む
- CO は
CreateVolume
,ControllerPublishVolume
などのメソッドで指定や値の検証が可能
- ControllerServiceCapability
- Create/Delete , Publish/Unpublish などの操作をサポートするかの情報を含む
- CO は
ControllerGetCapabilities
メソッドで取得可能
- NodeServiceCapability
- Stage/Unstage などの操作をサポートするかの情報を含む
- CO は
NodeGetCapabilities
メソッドで取得可能
CSI の利用用途
CSI プラグインとしてはすでに、 NFS や iSCSI などの公式のサンプルプラグインを含め、 AWS や GCP のストレージサービスの利用を可能とするプラグインが存在します。 以下の公式ドキュメントに既に存在する CSI プラグインに関する記述があります。
簡単な CSI Plugin の実装を追ってみる
以上だけだと CSI プラグインの動作が把握し難いので、 kubernetes-csi の drivers リポジトリにある HostPath plugin の動作を追ってみます。 このプラグインはリポジトリに kubernetes への deploy 用 yaml ファイルも同梱されており動作を追うのに便利です。
HostPath plugin では CSI の Identity, Controller, Node 三種類の gRPC サービスを提供します。
その実装は github.com/kubernetes-csi/drivers/tree/master/pkg/hostpath
に存在します。
Identity Server は特に特殊な実装をしていません。
github.com/kubernetes-csi/drivers/blob/master/pkg/csi-common
に存在する、他の Driver と共通のデフォルトメソッド実装をそのまま使用しています。
Controller Server では CreateVolume
, DeleteVolume
, CreateSnapshot
, DeleteSnapshot
, ListSnapshots
を実装します。
CreateVolume
, DeleteVolume
では複雑なことをしておらず、 UUID 付きの名前のディレクトリを掘って Volume に見立て、 map で Volume の管理を行います。
CreateSnapshot
, DeleteSnapshot
はそれに似て、 Volume のディレクトリを tar czf
で固めて同じく UUID 付きのファイルに保存・管理します。
Node Server では NodePublishVolume
, NodeUnpublishVolume
, NodeStageVolume
, NodeUnstageVolume
を実装します。
NodePublishVolume
, NodeUnpublishVolume
は作成された Volume のディレクトリを k8s.io/kubernetes/pkg/util/mount
パッケージを介して mount/unmount します。
NodeStageVolume
, NodeUnstageVolume
は単にリクエストパラメータ中の Volume や ターゲットパスが空でないことをチェックしているだけになります。
HostPath プラグインでは他に複雑なチェックをする必要が無いためこうなっているのではと思われます。
kubernetes 上で想定される構成は以下の通りになっているようです。
- Controller Server のメソッドの参照
CreateVolume
など Volume の作成・削除は https://github.com/kubernetes-csi/external-provisioner コンテナを利用ControllerPublishVolume
など Volume の Publish 操作は https://github.com/kubernetes-csi/external-attacher を利用CreateSnapshot
などスナップショットの操作は https://github.com/kubernetes-csi/external-snapshotter を利用
- Node Server のメソッド参照
- https://github.com/kubernetes-csi/driver-registrar を使って CSIDriver を kubelet から参照可能にする
- kubelet から CSIDriver をプラグインにという格好で読み出し参照するロジックは Kubernetes 本体リポジトリの kubelet と関連パッケージに存在
- https://github.com/kubernetes-csi/driver-registrar を使って CSIDriver を kubelet から参照可能にする
おわりに
以上、 CSI とその Kubernetes へのインテグレーションに関する記事でした。 CSI は可搬性を担保しつつ Kubernetes での利用を重要視されて作られているようですが、 CSI の仕様含めエコシステムもまだ枯れているとは言えず、機能も多くはありません。 Kubernetes 用の単なるサンプルとはいえ HostPath プラグインで tar コマンドを実行してスナップショットを作成したりなど、もう少し抽象化されてほしい箇所もまだ存在します。
しかしながら個人的には Kubernetes での柔軟なストレージ利用には、以前の記事で挙げた通りまだまだ課題があると思っており、今後の発展を期待したいと考えています。
Kubernetes 上で動作するコンテナから安全に FUSE を利用したかった
本題の通りの気持ちがあったのですが、結論としては手軽にできる良い方法は無いようでした。 備忘録的に挑戦した事を記録しておきます。
背景: FUSE 利用のモチベーション
言うまでもなくファイル I/O はシステム開発においてよく使われる機構であり、多くのプログラミング言語において標準ライブラリまたはそれに類するライブラリでファイル I/O をサポートしていると思われます。
さて Kubernetes 上でマイクロサービスを実装していく事が多々あるこのご時世、各マイクロサービスで実装言語非依存で使えるデータの読み書きのロジックがあると、例えば何らかの設定ファイルの動的受け渡しやロギングにおいて便利な可能性があります。 これに似た課題を抱えて解決に向かったプロダクトとして Envoy Proxy があるかと思われます。 これは元々ネットワーキングやトレーシングの課題などを各言語ごとのライブラリで対応していたもののつらくなり、 HTTP や gRPC を解釈するプロキシを導入することで緩和に成功しています。
前述の通りファイル I/O は HTTP や gRPC をしゃべるより更にリーズナブルな共通プロトコルであると考えられもしそうです。 さらに FUSE を使えばファイルシステムとしてインタフェースを提供して、かつ裏側で複雑なロジックを動かすこともできるように考えられます。 そんなわけで、 Kubernetes 上で動作するコンテナからいい感じに FUSE で実装したファイルシステムをマウントして利用できると良いかもと着想した次第です。
FUSE 利用の限界
同じような思考をしたり、あるいは既存 FUSE 資産を利用したい人たちが多々居たようで、 Kubernetes 公式リポジトリの Issue でも議論がされていたりします。
残念ながらこの 2015 年に open した issue は 2018 年も終わりを迎える今日においても close されていません。 どういう点がネックになるかというと
/dev/fuse
を open するのに特権が必要- mount, umount するのに SYS_ADMIN ケーパビリティが必要
が挙げられ、一応利用できなくは無いものの利用条件を満たすため現状は privileged mode でコンテナを動かす羽目になり、やや安全面でリスクがあるように思えます。
FUSE 利用パターンいくつか
前述の Issue の中で幾つかのアイデアも提示されています。少しこれらを実際に試してみることにしました。 多少このリスクを緩和するため、大きく分けて以下の方針がありそうです。
- コンテナ外でなんとかするパターン
- 特権を持つコンテナをアプリケーションを動作させるコンテナと分離するパターン
前者は場合によっては可搬性を損なうか導入が非常に困難になる恐れがあり、また後者であればコンテナ内の世界で完結させることができそうです。 従ってまずは後者のアイデアを試してみます。
以下に検証用に用意した Dockerfile や設定ファイルを配置しました。
FUSE でマウントするのは libfuse に含まれる、ファイルを read すると "Hello, World!" を返す hello.c
を利用しておきます。
こちらのリポジトリでは initContainers
で mount するのと、 postStart
, preStop
でサイドカーコンテナで mount/umount する案を試してみました。
が、結局マウント先を参照するアプリケーションコンテナからはマウントした先は参照できなくなります。
結局この例だと特権を渡さざるを得ませんでした。
次の一手のアイデア
今回特権を要求されたのが、コンテナ内から /dev/fuse
が open できない点なので、この点を何とかできれば 多少課題は緩和されるかもです。
Kubernetes は Device Plugin という機構を最近サポートしているようで、これで何とかできる可能性があるような全く無いような気がしています。
また Kubernetes v1.10 からベータになったというストレージ接続インタフェース CSI は将来も見据えてこの問題に取り組むのに、もしかしたら良い機構かもしれません。
いずれにせよ Kubernetes 自体の調査をほとんどやっていないので次の一手はこれら周辺技術の調査からかと考えております。
ISUCON8 に参加して最終成績が本戦3位だった
タイトルのとおりです。何やかんやあり ISUCON8 の予選を無事突破した後に 10/20(土) LINE さんのオフィスにて本戦に参加して、最終成績 3 位に収まりました。 3 位だと特に表彰されるわけでもなく気持ちのみなのですが。
チーム構成と役割分担について
職場のよしみで cubicdaiya さん、 catatsuy さんと何らか SRE チームらしき雰囲気を醸し出しながら参戦しました。 大枠としては以下の役割分担を組んでいました。
- インフラ担当
- cubicdaiya
- アプリ担当
- catatsuy
- syucream
全体チーム構成やツール・環境構築お膳立ては catatsuy さんがいい感じにやってくれていました。
予選について
本記事執筆にあたり予選実施日と期間が空いて、ほぼすべての記憶を損失しました。 様子はたぶん catatsuy さんの以下記事にある気がします。
本戦について
だいたい出題された課題の要点は以下記事で説明されています。
3 位に入賞できた決めては突出したコレという点は無く、チームでそれぞれ貢献できた、バランス的には良い塩梅なのではと感じました。 具体的なタイムラインとしては覚えている限り以下のとおりです。
前半: 好調な滑り出し
とりあえず各々初期準備をする
- 自分はここでは DB スキーマを取り出して共有したり WebUI 調べたり
catatsuy さんが早期にログ分析APIの叩き方問題に気づく
- 着想としてはrate limitとかより重要なパスでI/Oしていそうなことがやばそう
- 11 時台に議論して、ハマりそうだから手を打とうと結論づけて catatsuy さんが着手しだす
syucream が LIMIT 1 問題に気づく
- 最初はサブクエリ書く感じで改善したが LIMIT 1 するのと課題的に差分がそれほどなかった
- ここで一時的に 5000 点ほどスコアを叩き出しトップに躍り出る
- 時間軸が入れ替わるが、この改善の実施直前に cubicdaiya さんが slow query を出してくれていてプロファイルの裏付けもした上で実施した
cubicdaiya さんがインフラ周り、特に nginx 周辺のチューニングを徐々にする
- 冗長そうなコンテナ化をやめる
- 静的ファイルを nginx で配信
中盤: 次ステップへの到達との葛藤
会場提供でランチとして弁当が出る
- AbemaTV のニュースで鯖の漁獲量が減って値段が若干上がった話を思い出し、僕は思わず鯖味噌煮弁当を確保
catatsuy さんのログの集約送信が動き出す
send_bulk
API の利用がうまくいかず、時間ももったいないのでバッファリングして send するだけにとどめたり- この瞬間はスコアに響かなかったが、後々ボディブローのように効いてきた感じがある
syucream が地味に無駄そうな user table のレコードロックを外す
- slow query ではあったが、全くスコアに響かなかった
cubicdaiya さんがそろそろいいでしょと良い SNS share 機能を有効化する
syucream が雑にロウソク足チャートのオンメモリキャッシュを入れる
- マージした後に、一度キャッシュをすると別タイミングに対してのクエリにもキャッシュヒットしてしまうバグに気づく
- が、ベンチマーカが文句を言わなかった!
- 一瞬チームで相談して、問題が出てないなら後で考えようと結論づける
cubicdaiya さんが複数台構成を考え始める
- 特に異論無く、 1: DB, 2: App, 1: nginx という意思決定が即なされる
後半: 着眼点は悪くなかったがもう一歩だったか
catatsuy & syucream で orders & trade の課題について考え出す
その裏で cubicdaiya さんが App サーバを 2 台構成にしていた
- このあたり?でスコアが 10000 近くに到達
catatsuy さんがミクロな最適化したり、 syucream が銀行 API アクセスを並列化したり N+1 を無くそうとして爆死したり
その裏で cubicdaiya さんが 4 台フルに使い切る構成への移行を追えてチューニングに入ったり
DB の負荷をさげたくて ORDER BY を使うクエリを Go のアプリレベルに移植して時間がたりなかったり
一部さばけない負荷に対して cubicdaiya さんが async sleep 入れたり
- syucream はこれが大きな差別化ポイントだった気がしている
そうこうしている間に 17:50 頃になって収束させて終了
'もう一歩' に対する所感
- 着眼点は非常に良くて、 PARTITION に対する疑惑や
POST /orders
、RunTrade()
最適化は課題に感じていて一部取り組んでいました - あらかじめ配布された spec を読み込んだり、外部 API に対して向ける疑惑がもっとあっても良かったかも
結論: ISUCON はいいぞ
今回の出題の外部 API が絡む話は個人的によく練られていて、個人的に非常に好ましい話でした。なぜならこういう外部APIとその挙動の差異は現実問題に起こりうりそうな話だからです。 そういう観点から言うと、学生チームが優勝したという展開はとても興味深いようにも思えます。 個人的課題として、自分が当日見ていた箇所で見どころは誤っていないけど一歩足りなかったことが挙げられ、狂おしいほど悔しいです。
もし次回参加して同じメンバーでチームを組むのなら、 catatsuy さんが優勝するのに並々ならぬ熱意を抱いているのでたぶん僕らが優勝するでしょう。
POSIX message queue を Go のコードから利用するためのライブラリ posix_mq を作った
表題の通りです。
cgo を使って POSIX message queue の基本的な操作、 open/close と send/receive とその他細々とした機能を実装しています。 とは言っても、それほど複雑なことはしておらず、 POSIX の関数呼び出しを愚直に Go の func にラップしているだけなのですけどね。
なぜやったのか
入門 Kubernetes などを読むと、 Pod 内のコンテナ同士では SysV / POSIX の IPC namespace を共有している記述があります。
Pod 内の別コンテナへの通信となると、 sidecar パターンで Envoy を動かすようなネットワーキング用プロキシを介するのに利用したり fluentd などのロギングエージェントにログを送ったり、お決まりのパターンがあると思います。 そういった際に低コストで非言語依存なプロトコルが欲しくなることが多々あるように考えられます。
POSIX message queue は POSIX としての仕様も存在し、低コストな IPC 手段のひとつです。 これを選択肢のひとつとして用意しておくことは発生する課題に柔軟に対応するのに重要であると考えます。 手段は他にもあるし、同様の機能であれば SySV message queue やいっそ AMQP などをしゃべっても良い気もするのですが、手段を増やす意味でも今回のライブラリを開発してみた次第です。
動作例
sender / receiver の通信例
シンプルなsender と receiver であれば以下のコードで実装してやり取りできます。
- sender.go
package main import ( "fmt" "log" "time" "github.com/syucream/posix_mq/src/posix_mq" ) const maxTickNum = 10 func main() { oflag := posix_mq.O_WRONLY | posix_mq.O_CREAT mq, err := posix_mq.NewMessageQueue("/posix_mq_example", oflag, 0666, nil) if err != nil { log.Fatal(err) } defer mq.Close() count := 0 for { count++ mq.Send([]byte(fmt.Sprintf("Hello, World : %d\n", count)), 0) fmt.Println("Sent a new message") if count >= maxTickNum { break } time.Sleep(1 * time.Second) } }
- receiver.go
package main import ( "fmt" "log" "github.com/syucream/posix_mq/src/posix_mq" ) const maxTickNum = 10 func main() { oflag := posix_mq.O_RDONLY mq, err := posix_mq.NewMessageQueue("/posix_mq_example", oflag, 0666, nil) if err != nil { log.Fatal(err) } defer mq.Close() fmt.Println("Start receiving messages") count := 0 for { count++ msg, _, err := mq.Receive() if err != nil { log.Fatal(err) } fmt.Printf(string(msg)) if count >= maxTickNum { break } } }
Kubernetes の同一 Pod 上 container 通信例
折角なので上記の sender / receiver を Kubernetes の Pod に押し込んで通信させてみます。 あらかじめてきとうに sender / receiver 用の Docker イメージを作っておいてください。
Pod の定義なのですが、愚直に container の設定を羅列していくだけです。 IPC namespace は勝手に共有されるのでそれに関する設定や準備は必要ありません。
apiVersion: v1 kind: Pod metadata: name: posixmq-pod spec: containers: - name: posixmq-sender image: "posix_mq_sender" imagePullPolicy: IfNotPresent - name: posixmq-receiver image: "posix_mq_receiver" imagePullPolicy: IfNotPresent restartPolicy: Never
Pod の動作確認をさくっとしてみましょう。
$ kubectl apply -f example/kubernetes/pod-posixmq.yaml pod "posixmq-pod" created ... $ kubectl logs posixmq-pod -c posixmq-sender go run example/exec/sender.go Sent a new message Sent a new message Sent a new message Sent a new message Sent a new message Sent a new message Sent a new message Sent a new message Sent a new message Sent a new message $ kubectl logs posixmq-pod -c posixmq-receiver go run example/exec/receiver.go Start receiving messages Hello, World : 1 Hello, World : 2 Hello, World : 3 Hello, World : 4 Hello, World : 5 Hello, World : 6 Hello, World : 7 Hello, World : 8 Hello, World : 9 Hello, World : 10
この出力結果を見るに、 sender の送ったメッセージがちゃんと receiver に届いていそうです!
余談
POSIX の機能となると可搬性を期待してしまいますが、 POSIX message queue は darwin や windows では実装されていなかったりと意外に可搬性に欠けます。 対して SysV の message queue はこれに比べて可搬性が高く、より多くの環境でサポートされています。 (このあたりは Linuxプログラミングインタフェース にも記述されていますね!)
とは言っても本記事で書くようにあらかじめ環境が定められている Kubernetes クラスタ上で動かす場合は、それほど気にすることでも無いのかもしれません。 また、 SysV message queue の Go ラッパーライブラリは Shopify により実装されているのでこれを試すのもアリかもです。
ちなみに少し前のベンチマーク内容ですが、 POSIX message queue は IPC の手段として結構高パフォーマンスであるような調査結果もあります。