Upgrade to Pro — share decks privately, control downloads, hide ads and more …

BlockNoteとDuckDB-WASMでつくる、ブラウザ内データ分析環境の設計

 BlockNoteとDuckDB-WASMでつくる、ブラウザ内データ分析環境の設計

さくらインターネット研究所 テックトーク2026春(https://sakura-tokyo.connpass.com/event/383699/) の発表で使用した資料です。

Avatar for Ryotaro Tamura

Ryotaro Tamura

April 01, 2026
Tweet

More Decks by Ryotaro Tamura

Other Decks in Technology

Transcript

  1. 1. 背景と目指す体験 背景 実験の記録に伴う様々な工程が複数のファイル・ツールに散逸しがち 分析: Excel, SQL, Python(Pandas, Polars), …

    可視化: Excel, Python(Matplotlib, Seaborn), BI ツール, Origin, … 記録・共有: Word, PowerPoint, Notion, Markdown, … eureco が目指す体験 1 つのエディタ内でデータ分析の一連の工程が完結する データ読み込み → 集計・加工 → 表・グラフで確認 → 考察を記述 分析の結果だけでなく、工程がドキュメントに残る
  2. 3. 設計上の考慮事項 1. UI(BlockNote) と DuckDB-WASM の状態同期 BlockNote ドキュメントの状態変化をDuckDB にどう反映するか

    DuckDB 上の状態変化をリアクティブにUI に反映させるには 2. 分析工程をドキュメントにどう記録するか
  3. 3-1. 全体構成の概観 基本ルール Read とWrite の経路を分ける ブロックとカタログの1:1 対応を保つ Editor TableStore

    DuckDB-WASM BlockNote 同期⽤コンポーネント Catalog Snapshot snapshot.[contentHash] snapshot.[contentHash] snapshot.[contentHash] catalog.[blockID] catalog.[blockID] catalog.[blockID] データテーブル CSV 集計 チャート カタログ管理 カタログ管理 テーブル/ ビュー作成 EventHub Write: Read:
  4. 3-1. 状態同期: Editor → DuckDB BlockNote のイベントを起点に、DuckDB 上のテーブルやビューの状態を更新する同期用コンポーネントを実装 役割 BlockNote

    のイベントを購読 (CSV アップロード、データテーブル編集、分析パイプライン変更など) ブロック種別に応じてテーブルデータ、ビュー用SQL に変換 TableStore を介してDuckDB に登録 Editor TableStore DuckDB-WASM BlockNote 同期⽤コンポーネント Catalog Snapshot snapshot.[contentHash] snapshot.[contentHash] catalog.[blockID] catalog.[blockID] データテーブル カタログ管理 カタログ管理 テーブル/ ビュー作成 Write: Read: 1. 操作 2. イベント購読 3. 変換 + 登録
  5. 3-1. 状態同期: DuckDB → Editor 自作のTableStore を中心に、DuckDB の状態をUI に反映する仕組みを構築 カタログ機能

    ブロックID と、DuckDB 内のテーブル/ ビュー情報とのマッピングを管理するカタログ コンテンツハッシュを名前にしたスナップショットで実テーブルを保存 更新時は新スナップショット作成 → カタログの参照を切り替え ブロックからテーブル/ ビューへの安定参照に使用 イベントシステム カタログの更新時に各種イベントを発火 カタログ間の依存を見て、上流の変更で下流カタログの更新イベントも連鎖的に発火 最新のカタログ情報をUI コンポーネントに提供するためにReact フック内部で購読
  6. 3-1. 状態同期: DuckDB → Editor 各カスタムブロック(React コンポーネント) はブロックID に紐づくカタログを購読 カタログの状態が変わると、最新のテーブル情報をもとに自身を再描画

    Editor CSV ブロックなど TableStore 2. コンテンツハッシュ計算 ( 例: def456) テーブル追加⽤ エントリーポイント 1. テーブル操作を実⾏ (CSV アップロード) 4. 参照切り替え スナップショット管理 DuckDB-WASM 新スナップショット snapshot.def456 旧スナップショット snapshot.abc123 カタログ管理 5. カタログ更新 カタログテーブル 3. 実テーブルを作成 参照 以前の参照 6. カタログを購読 + UI 再レンダリング 同期⽤コンポーネント イベント購読
  7. 3-2. 分析工程の記録 集計ブロックのクエリを表現するデータ構造(JSON) をどう設計するか 開発初期 単一のクエリビルダーでJSON を構築してSQL に変換 前処理・集計・加工等の工程が混在 一部だけ変えたい・無効化したい場合、読み解く

    のが大変 現在のアプローチ ステップを積み上げていくように分析工程を構築 1. フィルタ — 期間の絞り込み 2. 計算 — 売上を算出 3. 集計 — カテゴリ別に集計 4. ソート — 売上降順 5. フィルタ — 売上上位のみ絞り込み 各ステップの意図を把握しやすい構造に 並び替えや無効化を容易にして、探索的な分析を サポート SELECT category, SUM(price * qty) AS revenue FROM raw_sales WHERE date >= '2024-01-01' AND price > 0 AND qty > 0 GROUP BY category HAVING SUM(price * qty) > 10000 ORDER BY revenue DESC
  8. 3-2. 分析パイプラインの例 数量の絞り込み → 税込金額を計算 → 税込金額で降順ソート (※ 実際の構造とは異なります) 実行時にCTE

    を使用したSQL へコンパイル { "base": { "table": "catalog.sales" }, "steps": [ { "op": "filter", "predicate": { "type": "binary", "op": "GT", "left": { "ref": { "name": "qty" } }, "right": 3 } }, { "op": "compute", "adds": [{ "alias": "tax_inc", "op": "MUL", "left": { "ref": { "name": "price" } }, "right": 1.1 { "op": "sort", "by": [{ "ref": { "name": "tax_inc" }, "order": "desc" }] } ] } WITH base AS (SELECT * FROM "catalog"."sales"), s0 AS (SELECT * FROM base WHERE "qty" > ?), s1 AS (SELECT *, ("price" * ?) AS "tax_inc" FROM s0), s2 AS (SELECT * FROM s1 ORDER BY "tax_inc" DESC) SELECT * FROM s2 -- params: [3, 1.1]
  9. 3-2. 実行までの流れ 各ステップで以下の処理を実行 1. 構造検証: ステップのJSON 構造を検証して、必要なフィールドの存在などをチェック 2. 名前解決: 式の中で使用されている列参照を、名前参照からID

    参照に変換。以後の列参照はID で行う 3. 整合性検証: ステップに含まれる式が型や参照の整合性を満たすかをチェック 4. スキーマ導出: ステップ適用後に得られる新しいスキーマ情報を算出 ( 計算・集計後の列情報など) 5. コンパイル: 解決済みステップを SQL+ 実行パラメータ に変換する 6. 逆解決: 内部ID を元の列名に戻して編集操作に対応できる形に復元する
  10. 3-2. 列ID の用途 課題: 列名で参照すると壊れやすい 上流で列名を変更すると、それを参照している下流 のステップが壊れる 1. ステップ1 で

    " 売上" 列を計算して追加 2. ステップ2 で " 売上" > 10000 でフィルタ 3. ステップ1 で " 売上" を "revenue" に列名変更 4. ステップ2 が壊れる(" 売上" が見つからない) 編集や入れ替えのたびに手動で直すのは大変 対応: 名前/ID の二層管理 保存時は列名参照、実行時は列の出自から生成した 決定論的なID で参照 JSON 上では "売上" > 10000 実行時は c_a3f8c2 > 10000 ID は正規化後の式などを用いて生成 列名を変更しても c_a3f8c2 は不変 実行後に現在の列名へ逆引きして、パイプラインを 自動更新
  11. 4. 今後 足りない機能・やりたいことは山積み… 全体的なUI/UX の改善 ステップの意図を記録するためのメタデータ拡充 より多様なステップの提供 ( ピボット、条件分岐、ウィンドウ関数、…) ステップの入れ子構造やテンプレートのサポート

    ステップ前後のテーブルプレビュー 計算式の手動入力対応 データリネージュの表示 複数ドキュメントを跨いだ参照 内部ID を利用した計算結果のキャッシュ 大規模データの扱い …
  12. 4. まとめ ブロック = table / view の 1:1 対応を設計の軸に

    同期レイヤ + カタログ機能 + イベント通知 でリアクティブにUI を更新 パイプラインJSON + CTE コンパイル で分析工程をドキュメントに記録