パフォーマンスチューニングガイド
NanaSQLiteは、標準構成でも高速に動作するように設計されていますが、適切な開発パターンと設定を選択することで、その性能を数倍〜数十倍に引き出すことが可能です。
🚀 核心となる最適化: バッチ操作の活用
SQLiteにおいて最も実行コストが高いのは「トランザクションの開始とコミット」です。
❌ アンチパターン: ループ内での個別書き込み
以下のコードは、1回のループごとにディスクI/Oが発生するため非常に低速です。
# 1000回のディスクコミットが発生(数秒〜数十秒かかる場合がある)
for i in range(1000):
db[f"key_{i}"] = i✅ 推奨パターン: batch_update / batch_get
NanaSQLiteのバッチ操作用メソッドを使用すると、1回のトランザクションでまとめて処理されるため、劇的に高速化します。
# 1回のディスクコミットで完了(ミリ秒単位で終了)
data = {f"key_{i}": i for i in range(1000)}
db.batch_update(data)ベンチマーク指標: 大量書き込みにおいて、個別更新に比べ 10倍〜100倍以上 の高速化が期待できます。
⚡ データベース設定の最適化
WAL (Write-Ahead Logging) モード
NanaSQLiteはデフォルトで optimize=True 設定時に WALモード を有効にします。
- メリット: 書き込み中も読み込みがブロックされず、並行性が向上します。
注意
: ネットワークドライブ(NFS/SMB)上ではWALモードが動作しない、または不安定になる場合があります。
メモリマップドI/O (mmap)
読み込みパフォーマンスを向上させるために、SQLiteの mmap_size を活用しています。デフォルトでは256MBが割り当てられています。
🧠 キャッシュ戦略 (v1.3.0+)
NanaSQLiteは、メモリ使用量と速度のバランスを最適化するために、複数のキャッシュ戦略を提供しています。
1. 無制限キャッシュ (CacheType.UNBOUNDED)
デフォルトの動作です。一度アクセスしたデータは、メモリが許す限りすべてキャッシュされます。
- メリット: 同一キーへの再アクセスが最速。
注意
: データ量が非常に多い場合、メモリ不足(OOM)の原因になる可能性があります。
2. LRUキャッシュ (CacheType.LRU)
v1.3.0で導入されました。キャッシュサイズ(項目数)に上限を設け、古いデータから自動的に破棄します。
- 使用方法:
cache_strategy=CacheType.LRU, cache_size=1000のように指定します。 - メリット: メモリ使用量を一定に保つことができます。
⚡ 高速化オプション: orjson + lru-dict
JSON のシリアライズ・デシリアライズを高速化するために orjson を活用します。
- orjson: 標準
jsonモジュールと比較して 3~5倍高速 です。 - lru-dict: C拡張で実装された超高速な LRU データ構造。
# クォート推奨(Zsh等のシェル対策)
pip install "nanasqlite[speed]"インストールされている場合、NanaSQLite は自動的にこれらを検知して使用します。未インストールの場合は標準ライブラリ(json, OrderedDict)へフォールバックします。
3. TTLキャッシュ (CacheType.TTL)
v1.3.1で導入されました。データの有効期限を設定し、古いデータを自動的に無効化します。
- 使用方法:
cache_strategy=CacheType.TTL, cache_ttl=3600(1時間) - Persistence TTL:
cache_persistence_ttl=Trueを設定すると、キャッシュ失効時に SQLite からも自動的に削除されます。セッション管理などに最適です。
📌 テーブルごとの個別設定
特定のテーブル(巨大なログテーブルなど)だけメモリ使用を抑えたい場合に有効です。
# メインDBは無制限、logsテーブルだけ最新100件をキャッシュ
logs = db.table("logs", cache_strategy=CacheType.LRU, cache_size=100)
# セッションテーブルには 30分(1800s) の TTL を設定し、DBからも自動削除
sessions = db.table("sessions",
cache_strategy=CacheType.TTL,
cache_ttl=1800,
cache_persistence_ttl=True
)⚡ キャッシュのロード方法
- bulk_load=True (初期化時):
- 起動時に全データをメモリに読み込みます。
- ユースケース: データ量が数万件程度で、起動直後から高速なランダムアクセスが必要な場合。
- デフォルト (遅延ロード):
- アクセスされたデータのみをメモリに保持します。
TIP
キャッシュを強制的に最新状態にしたい場合は db.refresh(key) または最新値をDBから直接引く db.get_fresh(key) を使用してください。 メモリ内の全キャッシュをクリアしたい場合は db.clear_cache() を呼び出します。
🧠 CRUD 速度特化: memory_first=True (v1.5.7dev1+)
CRUD の読み書きを最優先したい場合は、メモリ優先モードを使用できます。
from nanasqlite import NanaSQLite
db = NanaSQLite("app.db", memory_first=True)
db["user:1"] = {"name": "Alice"}
user = db["user:1"] # メモリから即座に取得
db.flush(wait=True) # 重要な区切りでは明示的に永続化
db.close() # 終了時にも残りの差分をフラッシュmemory_first=True は起動時に KVS 全体をメモリへ読み込み、その後の CRUD 操作をメモリ上で完結させます。変更差分は v2 エンジンによりバックグラウンドでまとめて SQLite へフラッシュされます。デフォルトでは変更がある場合に約 5 秒ごとにフラッシュします。
db = NanaSQLite(
"app.db",
memory_first=True,
memory_flush_interval=2.0, # 2秒ごとに差分をフラッシュ
)向いている用途
- 単一プロセスで動くアプリケーション
- CRUD が非常に多く、読み書きレイテンシを最小化したい場合
- データセット全体をメモリに載せられる規模
- 重要なタイミングで
flush(wait=True)またはclose()を呼べる設計
注意点
- LRU / TTL /
cache_size/cache_persistence_ttlとは併用できません。メモリ優先モードでは無制限キャッシュが前提です。 - プロセスが強制終了された場合、最後のフラッシュ以降の差分は失われる可能性があります。
- 別プロセスや外部ツールが同じ SQLite ファイルを更新しても、メモリ上の値には自動反映されません。
- マルチプロセス環境では使用しないでください。通常の v2 モードと同様にシングルプロセス専用です。
🔍 インデックスによる検索の高速化
query() や query_with_pagination() を使用して、Key-Valueペア以外のデータ(JSON内の特定フィールドなど)を検索する場合、インデックスが不可欠です。
# 検索対象となるJSONフィールドにインデックスを貼る
db.create_index("idx_user_age", "data", ["age"])インデックス作成の目安:
- 数千件を超えるデータに対して
WHERE句での検索を頻繁に行う場合。 - データの挿入速度よりも検索速度を優先したい場合。
💻 OS・環境固有の注意点
Windows環境
- アンチウイルスソフト: SQLiteファイルの書き込み時にウイルススキャンが走ると、
database is lockedが発生しやすくなります。DBファイル(.db, .db-wal, .db-shm)をスキャン除外対象に設定することを推奨します。
SSD vs HDD
- SQLite はトランザクション毎に fsync を多用するため、ディスクの永続化レイテンシに強く依存します。HDD 環境ではこの同期コストが支配的となり、synchronous=OFF など耐障害性を犠牲にした設定が必要になる場合があります。可能であれば fsync 性能の高い SSD 上での運用を推奨します。
チェックリスト
- [ ] 大量処理に
batch_updateを使っているか? - [ ] 頻繁な検索に
create_indexを適用しているか? - [ ]
optimize=True(デフォルト)を使用しているか? - [ ] CRUD 速度最優先かつシングルプロセスなら
memory_first=Trueを検討したか? - [ ] SSD環境で動作させているか?