「緯書」八字命盤演算機能修復技術レポート
技術レポート:八字命盤ツールのWASMロード失敗問題を修復。Cloudflare CDN圧縮競合の調査とエッジコンピューティングでの解凍対策を解説。
1. 問題の背景
「緯書」ブログの八字命盤ツール(WASM版)を統一的な「帛書」ビジュアルスタイルにリファクタリングし、Cloudflare Workersへデプロイした後、ユーザーからフロントエンドページで「推算命盤」をクリックするとエラーが発生するとの報告が寄せられた:演算失敗: WASMファイルを読み込めません。同時に、コンソールにはマジックナンバー不一致エラーが出力されていた:expected magic word 00 61 73 m, found cf ff ff 7f。
この問題により、核となる八字排盤機能が完全に動作しなくなった。本レポートでは、この問題の調査プロセス、根本原因分析、そして最終的な修正ソリューションについて詳細に記録する。
2. 障害診断と原因分析
フロントエンドのロードロジック、ネットワークリクエストのパケットキャプチャ、WebAssemblyインスタンス化プロセスを深く調査した結果、この障害は単一の原因によるものではなく、3つの独立した技術的問題が重なって発生したことが判明した。
2.1 圧縮形式の競合とCDNによるインターセプト(核心問題)
現象:
フロントエンドはfetchでganzhi.wasm.gz(18MB)を取得し、DecompressionStream('gzip')で解凍しようとした。しかし、解凍後にWebAssembly.instantiateへ渡されたバイトストリームの先頭8バイトはcf ff ff 7fであり、標準的なWASMマジックナンバー00 61 73 6dではなかった。
分析:
cf ff ff 7fはBrotli圧縮アルゴリズムのファイルヘッダー識別子である。さらにパケットキャプチャを行ったところ、フロントエンドは.gzファイルをリクエストしていたにもかかわらず、ブラウザがリクエスト時に自動的にAccept-Encoding: gzip, deflate, brヘッダーを付与していたため、Cloudflare CDNのエッジノードがクライアントのBrotli対応を検知し、元のGzipファイルをさらにBrotliで再圧縮し、Content-Encoding: brを返していた。
その結果、フロントエンドのDecompressionStream('gzip')はBrotli形式のデータを受信し、解凍に失敗。WebAssemblyエンジンがマジックナンバー不一致の致命的エラーを投げることになった。
2.2 JSONデータ解析パスの異常
現象: WASMロード問題を解決した後、フロントエンドのコンソールには計算完了と表示されたが、ページ上の四柱、五行、大運などのUI要素は依然としてプレースホルダー「—」のままだった。
分析:
元のJSグルーコードは、WASMが返すJSON文字列を処理する際に、data.pillarsなどのトップレベルプロパティへ直接アクセスしていた。しかし、最新版にコンパイルされたWASMモジュールが返すデータ構造はresultオブジェクトにラップされており、実際の構造は{"result": {"pillars": {...}}, "success": true}だった。パスが誤っていたため、フロントエンドのレンダリング関数はundefinedを受信し、UIが更新できなかった。
2.3 フィールド形式の非互換
現象: 四柱の干支データが対応するDOMノードに正しく反映されなかった。
分析:
旧バージョンのレンダリングロジックは、WASMが天干と地支を分離したフィールド(例:yearStem: "庚", yearBranch: "午")で返すことを想定していた。しかし実際に返されたデータは、干支を結合した単一の文字列(例:year: "庚午")だった。そのため、フロントエンドの分割代入が失敗した。
3. 修正ソリューションと実装
上記の3つの問題に対し、「サーバーサイド解凍+フロントエンド適応」という複合的な修正戦略を採用した。
3.1 エッジコンピューティング層(Cloudflare Workers)の再構築
ブラウザとCDN間の自動圧縮コンテンツネゴシエーションの競合を完全に回避するため、フロントエンドでのDecompressionStreamによる解凍方式を断念し、Cloudflare Workersのエッジコンピューティング機能を活用することにした。
具体的な実装:
インターセプトスクリプトworker.jsを作成し、/dist/ganzhi.wasmへのリクエストを検知すると、Workerがサーバーサイドで静的アセット内のganzhi.wasm.gzを読み取り、エッジノードでGzip解凍を完了させ、Content-Type: application/wasmを強制設定して、純粋な生のWASMバイトストリームをそのままフロントエンドへ返すようにした。
このソリューションは二重圧縮の競合を解決しただけでなく、エッジノードの計算能力を活用し、クライアント側ブラウザの解凍負担も軽減した。
3.2 フロントエンドレンダリングロジックの修正
JSON構造とフィールド形式の変更に対応するため、index.html内のrenderResult関数を書き直した。
| 修正項目 | 旧ロジック | 新ロジック |
|---|---|---|
| データ階層 | const p = data.pillars; | const data = raw.result ? raw.result : raw; const p = data.pillars; |
| 四柱の分割 | [p.yearStem, p.yearBranch] | const sb = p.year; $(sId).textContent = sb[0]; $(bId).textContent = sb.slice(1); |
| 五行レンダリング | 特定の中国語文字キーに依存 | 中英辞書マッピングを追加し、対応するCSSカラークラスを持つHTMLタグを動的に生成 |
3.3 ビジュアルスタイルの統一
機能修正と同時に、八字命盤のUIスタイルを「緯書」ブログのメインサイトと深く統一した。白紙背景色(#FAF7F2)、朱砂色のアクセントカラー(#C0392B)、墨色の文字色(#1A1A1A)を導入し、上部には統一された戻るナビゲーションバーを追加。ブランドビジュアルの一貫性を確保した。
4. まとめ
今回の修正により、Cloudflare CDNの自動圧縮メカニズムとWebAssemblyロード間の競合問題を解決することに成功した。解凍ロジックをWorkersのエッジノードに前倒しし、フロントエンドのデータ解析パスを再構築したことで、八字命盤ツールは安定稼働を回復した。
この事例は、今後のServerlessアーキテクチャでの大規模WASMファイル展開において貴重な教訓を提供している:非標準的なバイナリアセットを扱う際は、CDNのデフォルトの透過的圧縮動作に注意し、必要に応じてカスタムWorkerスクリプトによる明示的な介入を行うべきである。