パート1で18件の公正性問題を発見し方法論を書き直した。パート2でオーバーヘッドの原因をPydanticに特定しエクスポートを修正した。パート3で5つのマイルストーンにわたり4つのボトルネックを修正した。この記事でフルスイートを最後に一度実行する。
パート4/5。パート1: 方法論 | パート2: オーバーヘッド | パート3: 修正 | パート5: カラムナーベースライン
実行内容
24シナリオ、各20イテレーション、3ウォームアップ、--storage both、アーム交互実行、GC分離。4スケール:
| スケール | 計測件数 | 実行時間 |
|---|---|---|
| Medium (50K) | 435 | ~18分 |
| Large (100K) | 432 | ~40分 |
| Xlarge (500K) | 429 | ~3時間 |
| Xxlarge (1M) | 429 | ~6.5時間 |
| 合計 | 1,725 | ~10.5時間 |
最適化前の検証と合わせると: シリーズ全体で3,288件の計測。
環境: Python 3.12.11 | sqler 1.2026.3.5 | SQLite 3.50.4 | Linux x86_64(8コア)
スコアカード

1M行、ディスクモード:
| カテゴリ | 比率 | 評価 |
|---|---|---|
| バルクインサート | 0.89x | 生のsqliteより速い |
| Export JSON | 0.97x | パリティ |
| JSONL(IDなし) | 0.98x | パリティ |
| FTSランク | 1.00x | 完全パリティ |
| バックアップ/リストア | 1.01x | 透過的 |
| FTS再構築 | 1.03x | ほぼパリティ |
| any_where | 1.04x | ほぼパリティ |
| Export JSONL | 1.06x | ほぼパリティ |
| クエリ | 1.03〜1.15x | 安定したオーバーヘッド |
| 集計 | 1.06〜1.13x | 安定したオーバーヘッド |
| JSON操作 | 1.07〜1.14x | 安定したオーバーヘッド |
| Export CSV | 1.34x | 縮小不能 |
CSVエクスポートを除いて全て≤1.15x。クエリレイヤーは5〜12%のオーバーヘッドを加える。これはクエリのコンパイル、アダプターレイヤー、パート1で記録されたrow factoryアーティファクトのコストだ。いずれもスケールで増大しない — パーセンテージ自体よりその事実の方が重要だ。
全最適化が保持される
スケール横断検証のヘッドライン: どのスケールでもリグレッションなし。
| 最適化 | 50K | 100K | 500K | 1M | 修正前 |
|---|---|---|---|---|---|
| バルクインサート(M-3) | 0.96x | 0.91x | 0.91x | 0.89x | 1.87〜1.92x |
| any_where(M-2) | 1.02x | 1.03x | 1.05x | 1.04x | 1.47〜1.51x |
| FTS再構築(M-1) | 1.04x | 1.02x | 1.07x | 1.03x | 3.78〜4.65x |
| FTSランク(M-4) | 0.98x | 1.01x | 1.00x | 1.00x | 500K以上で1.50x |
| ハイドレーション(M-5) | 5.15x | 4.62x | 5.01x | 4.97x | (新機能) |
バルクインサートはスケールが大きくなるほど効率が上がる: 50Kで0.96x → 1Mで0.89x。マルチ行INSERTパターンのパーサー節約は行数が増えるほど複利になる。50回のSQL呼び出しと100万回の個別文では話が違う。
FTSランクの方がより重要な検証だ。1M行で: 1,037ms対1,033ms。これは以前唯一スケールとともに悪化していたシナリオ(50Kの0.95xから1Mの1.50xへ)で、パート3のシングルJOIN修正がリグレッションを完全に解消した。
スケールを横断した比率の安定性

比率はフラットな線だ。バルクインサートはスケールでわずかに改善し、他は全て計測ノイズの範囲内で保持される。これはオーバーヘッドが比例的であることを意味する: 行ごとのコストであり、アルゴリズム的なものではない。ORMは基礎となるSQL作業より速く増大する複雑性を導入していない。
クエリ比率についての注記: パート1のv1.2データはメモリモードでクエリが0.95〜0.97xを示しており、sqlerが生のsqlite3より速いように見えた。最終スイートはディスクモードを計測しており、パート1で記録されたrow factoryアーティファクトがもはや真のオーバーヘッドを隠せない。修正後の数値は1.03〜1.15x。これが最適化パスで導入されたリグレッションではなく、クエリコンパイルとアダプターラッピングの実際のコストだ。
詳細内訳(ディスクモード)
クエリ:
| 種類 | 50K | 100K | 500K | 1M |
|---|---|---|---|---|
| 等値フィルタ(インデックスなし) | 1.13x | 1.21x | 1.24x | 1.14x |
| 範囲50%選択率 | 1.15x | 1.15x | 1.15x | 1.11x |
| 複合5述語 | 1.03x | 1.06x | 1.04x | 1.08x |
| Top-N(1000) | 1.12x | 1.11x | 1.12x | 1.11x |
インデックスなし等値フィルタが最大の分散を示す(スケールで1.13〜1.24x)。より多くの述語を持つ複合クエリはオーバーヘッドが小さい。SQLiteがsqlerの固定per-queryコストに対して条件評価に多くの時間を費やすからだ。
JSON操作:
| 種類 | 50K | 100K | 500K | 1M |
|---|---|---|---|---|
| 配列contains | 1.05x | 1.05x | 1.05x | 1.09x |
| 配列isin | 1.01x | 1.03x | 1.02x | 1.07x |
| ネストフィールド(深さ3) | 1.13x | 1.12x | 1.12x | 1.14x |
深さ3のネストフィールドアクセスは一貫して~13%のオーバーヘッド。フラットな配列操作はパリティに近い。
集計:
| 種類 | 50K | 100K | 500K | 1M |
|---|---|---|---|---|
| sum | 1.17x | 1.15x | 1.17x | 1.13x |
| avg | 1.19x | 1.13x | 1.17x | 1.13x |
| min | 1.08x | 1.16x | 1.15x | 1.09x |
| max | 1.11x | 1.07x | 1.12x | 1.06x |
集計はスケールとともにパリティに収束する。1M行では全集計が1.13x未満。
1M行での絶対ウォールクロック時間
比率は割合を示す。絶対値は気にする必要があるかを示す。
| 操作 | sqler | sqlite | オーバーヘッド |
|---|---|---|---|
| FTS再構築 | 32.3s | 31.4s | +0.9s |
| バルクインサート | 7.0s | 7.9s | −0.9s |
| any_where | 6.5s | 6.2s | +0.3s |
| Export CSV | 10.3s | 7.7s | +2.6s |
| 複合5述語クエリ | 2.4s | 2.2s | +0.2s |
| FTSランク | 1.04s | 1.03s | +0.01s |
| 等値フィルタ(インデックスなし) | 1.2s | 1.1s | +0.1s |
| バックアップ | 614ms | 610ms | +4ms |
最大の絶対ペナルティはCSVエクスポート: 1M行でフィールドごとの抽出オーバーヘッドによる2.6秒の追加コスト。他は全て100万行で1秒未満の追加コスト。数百〜数千行を返すクエリ(実際のほとんどのワークロード)では、オーバーヘッドは1桁ミリ秒だ。
バルクインサートはexecutemany()の代わりにチャンク化マルチ行INSERTを使うことで1Mで0.9秒節約する。
縮小不能なもの
CSVエクスポート(1.34x): JSONdictからのフィールドごとの抽出と、フィールドごとの_serialize_value()。両アームともJSONをパースする。sqlerのオーバーヘッドはCSVフォーマットが要求するdict-to-row変換だ。C levelのJSON-to-CSVコンバーターで縮小できるかもしれないが、Pythonでは構造的な問題だ。
クエリオーバーヘッド(5〜12%): クエリのコンパイル、アダプターラッピング、パート1で記録されたrow factoryアーティファクト。1M行で5述語の複合クエリでは、絶対オーバーヘッドは200ms。インデックス付きルックアップとTop-Nクエリでは絶対オーバーヘッドは10ms未満。10msが問題かどうかはユースケース次第だ。
集計オーバーヘッド(6〜13%): クエリオーバーヘッドと同じ原因。1M行で最も高価な集計(sum)は1.13x、約100ms追加。
msgspecリードパス
M-5でSQLerMsgspecModelをSQLerLiteModelのオプトイン代替として追加した。ハイドレーションが重要なリード重視ワークロード向け:
| パス | Liteデフォルト比 |
|---|---|
SQLerLiteModel付きqueryset.all() | 1.0x(ベースライン) |
SQLerMsgspecModel付きqueryset.all() | 1.46x速い |
queryset.as_dicts() | ~2.8x速い |
as_dicts()パスはモデルインスタンスが不要な場合に最速の生dictを返す。msgspecパスは完全な型安全性をdataclassの1.46倍の速度で提供する。詳細はパート3で。
ハイドレーションの再現性(4回の独立実行)
| 実行コンテキスト | 純粋validate | エンドツーエンドall()(ディスク) |
|---|---|---|
| Mediumスイート | 5.15x | 1.47x |
| Largeスイート | 4.62x | 1.43x |
| Xlargeスイート | 5.01x | 1.46x |
| Xxlargeスイート | 4.97x | 1.43x |
ハイドレーションベンチマークはスケールパラメータに関わらず固定50K行を使う。各スケールのフルスイートの一部として実行することで4回の独立した計測が得られる。比率は高い再現性を示す。
既知の注意点
-
Row factoryアーティファクト(3〜6%): ベースラインは文字列キーアクセスで
sqlite3.Rowを使い、sqlerは整数インデックスアクセスを使う。これにより一貫してsqlerが読み取りで~5%速く見えるが、実際にはそうではない。パート1で記録済み。 -
単一マシン: 全計測が1台のマシン(Linux x86_64、8コア)で実施。異なるハードウェア、OS、Pythonバージョンでは比率が変わる可能性がある。
-
update/deleteベンチマークなし: スイートはinsertとqueryを広範囲に計測しているが、スケールでの
update()やdelete()はベンチマークしていない。 -
同時書き込み競合なし: 楽観的ロックは正確性についてテストされているが、競合下でのスループットはテストしていない。
-
100K CSVの異常値: 100Kの1回でCSVエクスポートが0.32xを示した(sqliteベースラインが期待される~716msの代わりに2,979msかかった)。環境的な外れ値で他のスケールでは再現不可。トレンド分析から除外。
自分で実行する
# ベンチマーク依存関係を含めてインストール
uv sync --all-groups
# mediumスケールで実行(~20分、メモリ + ディスク)
uv run python -m benchmarks run --scale medium --storage both
# largeスケールで実行(~40分)
uv run python -m benchmarks run --scale large --storage both
# 最新結果からチャート生成
uv run --group benchmarks python -m benchmarks plot
# 全シナリオをリスト
uv run python -m benchmarks list
ベンチマークスイートはPRAGMAの一致、アーム交互実行、GC分離を自動で適用する。結果はbenchmarks/results/にJSONで保存される。
この旅の全体像
このシリーズは同じストーリーを語る22のベンチマークから始まった: sqlerは速い。そのストーリーは間違っていた。正確には、誰も問わなかった質問への答えだった。「sqlerはベースラインより良い設定を持っていると速いか?」は有用な問いではない。
敵対的な監査で4ラウンドにわたり23件の公正性問題が発見された。公正な書き直しで全ての数値が悪化した。最適化パスで5つの実際のボトルネックを修正した。そのうち2つはコードのバグではなくベンチマークのバグだった。
最終的な数値: バルクインサート0.89x(生より速い)、FTSランク1.00x(完全パリティ)、他は全て≤1.15x、1.34xの縮小不能なギャップが1つ。50K、100K、500K、1M行で、1,725件の計測と10.5時間の実行時間をかけて確認。
これらの数値が十分かどうかは何を作っているかによる。ほとんどのワークロード(数千行をクエリしてエクスポートする類)では、オーバーヘッドは絶対値で10ms未満だ。100万行のバルク操作では、sqlerはナイーブなexecutemany()アプローチと同等かそれより速い。ORMレイヤーは無料ではないが、コストは安定していて予測可能で、その下にあるSQL作業に比べて小さい。
最初から読む: パート1: 方法論。
