miyauchidp.dev

著者: miyauchi(本サイト・各ツールの開発者)

公開

Duel Simulatorのリプレイデータはgzipでどれくらい小さくなるか

Duel Simulatorの保存済みリプレイ120件をgzipで圧縮し、JSONの合計容量が約85%減ることを確認しました。サンプルの選び方、復元の検算、D1全体の容量とは分けて読むべき理由を記録します。

Duel Simulatorのリプレイは、開始時の盤面と、その後の操作をJSONにして保存しています。 共有されたリプレイが増えるにつれて、このJSONの保存容量も増えてきました。 そこで2026年10月17日に、保存形式をgzipへ変えた場合の削減量を調べました。

保存済みリプレイ120件では、JSONの合計3,761,827バイトが547,894バイトになり、85.44%減りました。 圧縮後に展開したデータは、全120件で元のバイト列と一致しています。 これは本番から読み取ったデータを手元で圧縮した結果です。この記事で扱う調査では、本番の保存形式やデータは変更していません。

何を圧縮したか

対象はCloudflare D1の保存テーブルにあるstate_jsonです。 初期盤面と操作列を含むJSON文字列で、リプレイを読み込むときに使います。 調査時点の実装では、この文字列をTEXT列へ保存していました。

操作列には同じ形式の情報が繰り返し現れます。 内容を省いたり項目名を変更したりする前に、その文字列をそのまま圧縮した場合の効果を確かめました。 gzipから元の文字列へ戻せれば、JSON内部の構造を変更せずに容量を減らす候補になります。

以前の長時間利用でメモリが増える問題の記事は、ブラウザで操作履歴をどう保持するかを扱いました。 今回の対象は共有されたリプレイをサーバーへ保存する際の容量です。 ブラウザのメモリ使用量や、ページの通信量を測った実験ではありません。

作成時期を分散させて120件を読む

直近のリプレイだけを調べると、最近の使われ方に偏る可能性があります。 そこで、作成日時とIDで並べた57,369件を12区間に分け、各区間から連続する10件ずつを読み取りました。 区間内の開始位置は乱数のseedを20261017に固定して選んでいます。

取り出したJSONはUTF-8のバイト列にし、Pythonのgzip.compressで1件ずつ圧縮しました。 圧縮レベルは6、gzipヘッダーの時刻はmtime=0です。 各リプレイを個別に保存する想定なので、120件を結合して1つの圧縮ファイルにはしていません。

これは作成順の広い範囲から採ったサンプルですが、全件を均等な確率で独立に抽出したものではありません。 特定の長さや利用方法をすべて代表できる保証はなく、全件への換算は見積もりとして扱います。

合計容量は約85%減った

測定項目 結果
対象 120件
圧縮前のJSON合計 3,761,827バイト
gzip圧縮後の合計 547,894バイト
減った容量 3,213,933バイト
合計容量に対する削減率 85.44%
元のバイト列へ復元できた件数 120件 / 120件

削減率は1 - 圧縮後の合計 / 圧縮前の合計で求めました。 個々のリプレイの削減率を単純平均した値ではありません。 1件ごとの削減率は約79.12%から87.53%で、データによって差がありました。

区間ごとの件数と容量をまとめたCSVを用意しています。 JSON本文やリプレイの識別情報は含まず、12組の容量を足すと上の合計になる集計表です。

今回の確認で使った処理の中心は、次のようなものです。 state_jsonは、読み取り済みのJSON文字列を表します。

import gzip

raw = state_json.encode("utf-8")
compressed = gzip.compress(raw, compresslevel=6, mtime=0)
restored = gzip.decompress(compressed)

assert restored == raw
print(len(raw), len(compressed))

文字数ではなくUTF-8のバイト数で比べ、展開した結果もバイト列のまま照合します。 容量だけが小さくなっていても、元へ戻せなければ保存方式として使えないためです。

JSONの削減率を、D1全体の削減率にしない

同じ日の集計では、保存済みリプレイのstate_jsonは合計約1.948GBでした。 今回のサンプルと同じ圧縮率を全件に当てはめると、約284MBになり、約1.66GB減る計算です。 ここでのGB・MBはそれぞれ10億バイト・100万バイトとして換算しています。

ただし、D1全体のサイズは当時約4.25GBありました。 リプレイJSON以外にも、閲覧ログや集計用キャッシュなどのテーブルと、各種インデックスが存在します。 Cloudflareの料金説明でも、ストレージにはテーブルとインデックスの両方が含まれます。

そのため、「JSONが85%小さくなった」を「データベース全体が85%小さくなる」とは読めません。 約1.66GBという値もJSONのバイト数の推計で、移行後にD1の物理サイズが同じだけ減った実測ではありません。 実際の削減量は保存形式を変えたあとのデータベースサイズを別に確かめる必要があります。

保存形式を変える前に残っている確認

Cloudflare Workersには、gzipを扱えるCompressionStreamとDecompressionStreamがあります。 公式の対応形式に含まれるため、Workersでの圧縮・展開を検討できます。 ただし、今回使ったPythonの圧縮レベル6と、Workersで採用する処理が同じ容量・速度になるかは別の確認です。

保存先を圧縮したバイナリにするなら、書き込み処理に加えて、読み出し時の展開とエラー処理も変わります。 移行中のデータ形式をどう識別するか、変換に失敗した行をどう扱うか、途中から再開できるかを決める必要があります。

また、D1の制限では文字列・BLOB・テーブルの1行に2,000,000バイトの上限があります。 圧縮後の値だけでなく行全体が収まるかを確認し、展開後に扱うサイズにも上限を設ける設計が必要です。 容量が減ることだけを理由に、入力を無制限に受け取るわけにはいきません。

今回分かったのは、保存済みリプレイのJSONには、内容を保ったまま大きく圧縮できる余地があることです。 本番への導入を判断するには、読み書きの所要時間、移行手順、変換後の全件照合、実際のD1容量を測るところまで進める必要があります。

参考情報

記事内容に誤りを見つけた場合は お問い合わせ から報告してください。