DuckDB/DuckDB-Wasm を利用した低コストでの可視化
前提
- ミドルウェアのログ可視化
- ログサイズは全体で 1 TB 未満
- ログは jsonl で出力される
まとめ
- 非同期での可視化
- オフラインでの可視化
- 低コスト
DuckDB とは
こちらをどうぞ。
DuckDB雑紹介(1.1対応版)@DuckDB座談会 - Speaker Deck
サンプル
とりあえず動くのが見たい人用です。ソースコードも公開してます。
DuckDB-Wasm + Parquet + S3-compatible object storage + OPFS
- S3 から Parquet ファイルを fetch して DuckDB-Wasm への登録
- OPFS へ Parquet ファイルの保存
- OPFS から Parquet ファイルの読み込み DuckDB-Wasm へ登録
- SAMPLE 1 の出力
- SAMPLE 1 での検索
- SAMPLE 1 での Parquet ファイル出力
- 雑な集計
- OPFS の削除
非同期の可視化
DuckDB を利用する事で、HTTP で可視化要求リクエストを受け取って S3 または S3 互換オブジェクトストレージ (以降 S3) から jsonl.gz ログ取得し Parquet ファイルを生成し、S3 にアップロードし、署名付き URL を発行して返すことができます。
クライアントは Parquet ファイルをダウンロードし OPFS に保存、その後は通信することなくオフラインで好き放題ブラウザ上で解析することができます。
そもそもダッシュボードの可視化を求められるのは、なにか問題があったときであって、それ以外では必要とされることは基本的にありません。
S3 の jsonl.gz や Parquet ファイルの有効期限を設定しておくことで、一定期間で削除することができます。
オフラインでの可視化
この仕組みを使う事で OPFS に保存された Parquet ファイルはいつでもオフラインで確認することができるようになります。毎回ダウンロードも発生しません。
OPFS のファイルはクライアント側の判断で好きに削除することができます。
クライアントリソース
この仕組みを使う事で集計や解析に必要なリソースをクライアント側に持たせることができます。クライアント側で Parquet で数十メガ程度のログを解析はとても軽く、負担になる範囲ではありません。
コスト
上記の構成でかかる費用は S3 のストレージ費用と、Parquet ファイルの転送量だけです。要求の処理は集約だけなのでほぼ CPU リソースを使うことはありません。
オフライン解析
DuckDB-Wasm をうまく使えば、ミドルウェアが出力した json.gz ログをブラウザに D&D で読み込ませて、それを解析することもできるようになります。
ブラウザで利用できるオフライン解析ツールが実現できるようになります。
DuckDB-Wasm の OPFS 対応
DuckDB-Wasm 利用時に OPFS を保存先とする仕組みです。この仕組みを利用することで 10 GB 近いデータをブラウザで読み込めるようになるようです。
DuckDB-Wasm でインスタントサーチ
かなり雑に作ったのですが、キー入力毎に DuckDB-Wasm で SQL を実行して、Samples (1) を実行しています。
以下のような SQL を実行しています。
当たり前ですが DuckDB-Wasm は SQL インジェクションが存在しないので最高です。
SELECT timestamp, connection_id, rtc_type
FROM rtc_stats
WHERE connection_id LIKE '%${searchTerm}%'
OR timestamp LIKE '%${searchTerm}%'
OR rtc_type LIKE '%${searchTerm}%'
USING SAMPLE 1 PERCENT (bernoulli);
以下の検索ボックスで試せます。
DuckDB-Wasm で Parquet ファイル出力
DuckDB-Wasm で Parquet ファイルを出力することができます。
COPY (SELECT * FROM rtc_stats
USING SAMPLE 1 PERCENT (bernoulli)) TO samples.parquet (FORMAT 'parquet', COMPRESSION 'zstd');
後はこんな感じで Buffer に保存して、それを Blob にしてダウンロードすることができます。
const parquet_buffer = await db.copyFileToBuffer('samples.parquet')
const blob = new Blob([parquet_buffer], { type: 'application/octet-stream' })
以下の Samples (1%) Download Parquet
ボタンで試せます。
Discussion