V2 アーキテクチャ
NanaSQLite の V2 モードは、書き込みの多いワークロードに対して非ブロッキングの書き込みを実現するオプションのアーキテクチャです。
制限事項
V2 モードはシングルプロセスでの利用を想定しています。Gunicorn のマルチワーカーなど、複数プロセスからの利用はサポートしていません。
データの永続性に関する警告 (SIGKILL)
V2 モードは atexit を使用して終了時に自動フラッシュを行いますが、OS による強制終了(SIGKILL や電源断)が発生した場合は、メモリ上のバッファが失われます。ミッションクリティカルなデータについては、db.flush(wait=True) を明示的に呼び出すか、デフォルトの immediate モードを使用してください。
概要
V2 エンジンは デュアルレーン 設計を採用しています:
アプリケーション
│
├─ KVS レーン ──────→ ステージングバッファ → コミット
│ (dict操作) (書き込み結合)
│
└─ Strict レーン ───→ 優先度キュー → 順序通り実行
(SQL操作) (FIFO)- KVS レーン:
db["key"] = valueのような辞書操作を高速に処理。同じキーへの書き込みは結合(coalescing)される - Strict レーン:
sql_insert()、sql_update()などの SQL 操作を順序を保って実行
有効化
from nanasqlite import NanaSQLite
db = NanaSQLite(
"app.db",
v2_mode=True, # V2 エンジンを有効化
flush_mode="immediate", # フラッシュモード
)CRUD 特化のメモリ優先モード
memory_first=True は V2 エンジンの time フラッシュを利用する、CRUD 速度特化のオプションです。起動時に KVS 全体をメモリへ読み込み、以後の get / set / delete / len / keys / batch_get などをメモリ上で完結させます。変更差分だけがバックグラウンドで SQLite にフラッシュされます。
from nanasqlite import NanaSQLite
db = NanaSQLite(
"app.db",
memory_first=True,
memory_flush_interval=5.0, # デフォルト: 5秒
)
db["session:1"] = {"user": "alice"}
assert db["session:1"]["user"] == "alice"
db.flush(wait=True) # 重要な区切りでは明示的に永続化
db.close()通常の v2_mode=True は「書き込みを非同期化する」ための設定です。一方、memory_first=True は「CRUD の正本をメモリ側に置き、差分だけを定期的に永続化する」ための設定です。データセット全体がメモリに載る単一プロセス用途に向いています。
注意
memory_first=True は LRU / TTL / cache_size / cache_persistence_ttl と併用できません。また、プロセスが強制終了された場合は最後のフラッシュ以降の差分が失われる可能性があります。重要な区切りでは db.flush(wait=True) を呼んでください。
フラッシュモード
V2 エンジンはデータをバックグラウンドでデータベースに書き込みます。4 つのフラッシュモードから選択できます:
| モード | 説明 | データ安全性 | パフォーマンス |
|---|---|---|---|
immediate | 操作ごとに即座にフラッシュ | ★★★★★ | ★★★ |
count | N 件蓄積後にフラッシュ | ★★★★ | ★★★★ |
time | 一定時間間隔でフラッシュ | ★★★★ | ★★★★ |
manual | 手動フラッシュのみ | ★★★ | ★★★★★ |
immediate(デフォルト)
最も安全なモード。操作ごとにデータベースへの書き込みが保証されます:
db = NanaSQLite("app.db", v2_mode=True, flush_mode="immediate")
db["key"] = "value" # 即座にDB書き込みcount
指定件数の操作が蓄積されたらフラッシュ:
db = NanaSQLite(
"app.db",
v2_mode=True,
flush_mode="count",
flush_count=100, # 100 件ごとにフラッシュ
)time
指定間隔でフラッシュ:
db = NanaSQLite(
"app.db",
v2_mode=True,
flush_mode="time",
flush_interval=3.0, # 3 秒ごとにフラッシュ
)manual
完全に手動制御:
db = NanaSQLite("app.db", v2_mode=True, flush_mode="manual")
db["k1"] = "v1"
db["k2"] = "v2"
db["k3"] = "v3"
# 明示的にフラッシュ
db.flush()manual モードの注意
flush() を呼ばずにプログラムが終了すると、バッファ内のデータが失われます。
チャンクサイズ
大量の書き込みがある場合、チャンクサイズを調整してトランザクションサイズを制御できます:
db = NanaSQLite(
"app.db",
v2_mode=True,
v2_chunk_size=1000, # 1000 件ずつトランザクション
)Dead Letter Queue (DLQ)
書き込みに失敗した操作は Dead Letter Queue に隔離されます。これにより、エラーが後続の操作をブロックしません。
DLQ はデフォルトで最大 1000 件まで保持します。上限に達した場合は最古のエントリを破棄します。V2Config(max_dlq_size=...) または v2_max_dlq_size=... で調整できます。
DLQ の確認
# DLQ の内容を取得
dlq = db.get_dlq()
for item in dlq:
print(f"失敗した操作: {item}")DLQ のリトライ
# 失敗した操作を再試行
db.retry_dlq()DLQ の消去
# DLQ のエントリをすべて消去
db.clear_dlq()メトリクス機能 (Metrics)
V2エンジンでは、処理遅延やフラッシュ回数、DLQの発生状況などの詳細なメトリクスを収集できます。メトリクス収集はオプトイン機能です。
db = NanaSQLite(
"app.db",
v2_mode=True,
v2_enable_metrics=True, # メトリクス収集を有効化
)
# メトリクスの取得
metrics = db.get_v2_metrics()
print(f"Total Flushes: {metrics['total_flushes']}")
print(f"DLQ Count: {metrics['dlq_count']}")StrictTask
Strict レーンにカスタムタスクをエンキューできます:
from nanasqlite.v2_engine import StrictTask
# 優先度付きタスクをエンキュー
task = StrictTask(
priority=1,
sequence_id=0,
task_type="sql",
sql="INSERT INTO logs (message) VALUES (?)",
parameters=["Important event"],
on_success=lambda: print("成功"),
on_error=lambda e: print(f"失敗: {e}"),
)
db._v2_engine.enqueue_strict_task(task)V2 コンストラクタパラメータ
| パラメータ | 型 | デフォルト | 説明 |
|---|---|---|---|
v2_mode | bool | False | V2 エンジンの有効化 |
flush_mode | str | "immediate" | フラッシュモード |
flush_interval | float | 3.0 | time モードの間隔(秒) |
flush_count | int | 100 | count モードの件数閾値 |
v2_chunk_size | int | 1000 | トランザクションチャンクサイズ |
v2_max_dlq_size | `int | None` | 1000 |
v2_enable_metrics | bool | False | 詳細なメトリクス収集を有効化 |
非同期での V2 利用
AsyncNanaSQLite でも V2 モードが使用できます:
from nanasqlite import AsyncNanaSQLite
async def main():
db = AsyncNanaSQLite(
"app.db",
v2_mode=True,
flush_mode="time",
flush_interval=5.0,
)
await db.aset("key", "value")
await db.aflush()
dlq = await db.aget_dlq()
db.close()V1 vs V2 比較
| 項目 | V1(デフォルト) | V2 |
|---|---|---|
| 書き込み | 同期・ブロッキング | 非ブロッキング(バッファ経由) |
| 読み取り | キャッシュ → DB | キャッシュ → バッファ → DB |
| データ安全性 | 即時保証 | フラッシュモードに依存 |
| 書き込み性能 | ベースライン | 高スループット |
| 複雑さ | シンプル | DLQ・フラッシュ管理が必要 |
| プロセス | マルチプロセス対応 | シングルプロセスのみ |
いつ V2 を使うべきか
V2 が適している場面:
- 書き込みが頻繁なアプリケーション(ログ、センサーデータ、チャット)
- 書き込みレイテンシを最小化したい場合
- シングルプロセスアプリケーション
- CRUD レイテンシを最優先し、全データをメモリに載せられる場合は
memory_first=True
V1 を使うべき場面:
- データの即時永続化が必要
- マルチプロセス環境(Gunicorn ワーカーなど)
- シンプルな CRUD アプリケーション
- データセット全体をメモリに載せられない場合