MoonBitでOpenTelemetryのライブラリを作っている途中の学び
↓の続編です。
前回まででJS/Native/WASMで使えそうなライブラリとして作り出しました。
使いっぷりとしては以下のようにSpanを作ってendしたら裏側でよしなにotlpエンドポイントへSpanが送られるという雰囲気です。
WASM対応は一旦置いてJS/NativeでTraceができるところまで作れて公開してみたのでここまでの学びを書きます。
opentelemetry-protoを使うことにしてフォーマットにハマる
なんとopentelemetryにはインターフェースが受け付ける入力フォーマットがprotobufで宣言されていたのです。
そしてMoonBitにはprotobufのための実装がすでに存在していました。
途中でバグに気づいてPRを出したりしていました。
そんな中なかなか時間を溶かしたのがspanId/traceIdの扱いでした。
この2つはprotobuf上でbytes型で宣言されています。どちらの値もprotobufに閉じない概念として、バイト配列が表現上の仕様になっているので納得の宣言です。
一方でprotobufでも値をJSONにエンコードして扱う時には特別扱いするようになっています。
protobufのbytes型はJSONの値で扱う時にはbase64 stringで扱うことになっています。
最初はシンプルにprotobufライブラリのtoJSON的なメソッドを使っていましたが、このことに気づいてSpanをprotobufで定めた形式のJSONで送る際には特別に実装しているエンコードのロジックを呼び出すことにしました。
fire and forgetには非同期キューを使うことにした
Spanの送信というのは基本的には非同期で行いたいです。
たとえばHTTPリクエストを処理してレスポンスを返すまでの間にSpanの送信がOKを返すのを待つ時間は基本あってほしくないです。
JavaScriptではそんなとき以下のように非同期関数の呼び出しを関数の中で結果を待たずに行えます。(良し悪しはおいておき。)
async function sendSpan(span) {
// use fetch
}
async function handleHttpRequest(request) {
const queryResult = await db.query(...);
const span = new Span(...);
// sendSpanの結果をまたずに次へ進む
sendSpan(span);
queryResult++;
await db.exec(queryResult);
return {statusCode: 200};
}
しかしMoonBitの標準な非同期処理のライブラリの範囲ではJavaScriptのようにシュッと非同期処理の打ちっぱなし(fire and forget)ができないようです。
前提としてMoonBit標準の範囲ではasync関数の呼び出しは以下の2つの方法になります。
基本的に呼び出したらそこで待つことを求められるようです。
- async関数はasync関数から呼び出せる。呼び出したら結果を得るまで待つ。
- run_asyncで定義されている%async.runを使う。呼び出したら渡した関数の終了まで待つ。
moonbitlang/asyncにはTaskGroupという非同期処理をいくつも開始できる実装がありますが、これでも呼び出した全ての非同期処理が結果を出すまで呼び出し側は待つことになるので非同期処理の打ちっぱなしはできないようです。
TaskGroupがすべての非同期処理の完了または失敗を待つことは、ドキュメントにあるようにリソースリークの余地を残さないためなんですね。よさそう。
When with_task_group returns, it is guaranteed that all tasks spawned in the group already terminate, leaving no room for orphan task and resource leak.
ではどうしたかというと非同期キューを利用してみました。
キューへのpushはasync関数になっています。キューにメッセージを詰めるだけなので実際にspanを送るより明らかにすぐ終わることでしょう。
今回メッセージは「送信予定のSpanを追加したからN秒後にflushよろしく」という感じにしてみました。
「よろしく」する側は「手続き」をよろしくするのではなく、「メッセージ」を送ってよろしくするようになったとでも言いましょうか。
「メッセージ」はプレーンなデータと捉えてみると、手続きをよろしくするよりリソースリークに強そうな気がしますね。
OpenTelemetryの仕様とMoonBitの非同期処理について時間を溶かしたり、頭を捻ってなんとなく動きそうな感じになってきました。
引き続き作ってみる予定です。
Discussion