0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DictSQLiteの失敗から生まれた「NanaSQLite」——計測駆動でRustを使ったら初期化7.7倍・バッチ処理19.9倍になった

0
Posted at

📝 3行まとめ

  • DictSQLiteが高機能化で破綻したので、シンプルで速いNanaSQLiteをゼロから作り直した
  • 「とりあえずRustにすれば速くなる」と思っていたら逆に遅くなった失敗をした
  • ベンチマーク計測でボトルネックを特定してから改善したら、バッチ処理が19.9倍速になった

🔗 公式サイト: https://nanasqlite.disnana.com/
🐙 GitHub: https://github.com/disnana/nanasqlite

はじめに

「SQLiteをdictで使えるライブラリを作ろう」——そのシンプルな動機から始まったDictSQLiteは、気づけば誰も幸せにしないモンスターになっていました。

そこからNanaSQLiteを作り直す中で、設計の失敗Rustの罠という2つの痛い経験をしました。
この記事は「なぜ作り直したか」「なぜRustで失敗したか」「どう立て直したか」の開発記録です。

1. DictSQLiteが破綻した理由

DictSQLiteの当初のコンセプトはシンプルでした。

db["key"] = "value"  # これだけでSQLiteに保存したい

しかし機能追加を重ねるうちに、こうなりました。

db = DictSQLite(
    "data.db",
    encryption_key="secret",
    cache_size=1000,
    journal_mode="WAL"
)

with db.transaction():
    db.set("key", "value", encrypt=True)

気がついたら「重い・複雑・バグが多い」ライブラリができあがっていた。問題を整理すると以下の通りです。

問題 具体的な症状
重い 暗号化・複雑な最適化処理がオーバーヘッドになる
複雑 オプションが多すぎて使い方がわからない
不具合 機能間の相互作用でバグが頻発
学習コスト ドキュメントを読まないと動かせない

「ただSQLiteをdictで使いたいだけ」なのに、こんなに難しくする必要があったのか?
その問いがNanaSQLiteの出発点でした。

2. 「とりあえずRustで高速化」は失敗した

最初の作り直しでは、「コア部分をRustで書けば解決する」と安直に考えていました。

失敗談:Rustにすれば無条件に速くなるわけではない

ボトルネックを計測せず、コア部分をRustで実装した結果、PythonとRust間のFFI(外部関数インターフェース)のオーバーヘッドが支配的になり、純粋なPython実装より処理速度が悪化しました。「速い言語を使う」だけでは速くならないことを身をもって体験しました。

原因は明快でした。データのやり取りのたびに発生するPython↔Rustの型変換コストが、Rustの高速化メリットを上回っていたのです。

3. 計測駆動で作り直したNanaSQLiteのアーキテクチャ

反省を踏まえ、今回は**「計測→特定→改善→再計測」**のサイクルを徹底しました。

設計の優先順位を変えた

DictSQLite: 高機能 → 複雑 → 重い
NanaSQLite: シンプル → 速い → 使いやすい

あえて削除した機能と、追加した機能を明確に決めました。

削除したもの(意図的に)

  • ❌ pickleシリアライズ(セキュリティリスクの排除)
  • ❌ 複雑な最適化オプション(設定不要にする)
  • ❌ 高度なトランザクション管理

追加したもの

  • ✅ JSON自動シリアライズ(安全・他言語との互換性)
  • ✅ 即時永続化(書いた瞬間にSQLiteへ保存)
  • ✅ スマートキャッシング(遅延ロード・一括ロードの切り替え)
  • ✅ async/await完全対応(Discord bot・FastAPIへの組み込みを想定)
  • ✅ Pydanticサポート
  • ✅ セキュリティ機能(strict_sql_validation, ReDoS対策)

内部の速度を支えるSQLite設定

-- 設定不要で自動適用される最適化
PRAGMA journal_mode=WAL;       -- 並行書き込みを可能にする
PRAGMA synchronous=NORMAL;     -- 安全性と速度のバランス
PRAGMA mmap_size=268435456;    -- 256MBメモリマップで高速化
PRAGMA cache_size=-64000;      -- 64MBキャッシュ
PRAGMA temp_store=MEMORY;      -- 一時領域はメモリに

これらを自動適用することで、ユーザーは何も意識しなくても最適化された状態で使えます。

標準のsqlite3ではなく、低レベル高速バインディングのapswを採用しているのも速度の要因のひとつです。

Rustを「使う箇所だけ」に絞った

前回の失敗を踏まえ、今回はプロファイラで計測して本当にFFIオーバーヘッドに見合う箇所のみにRustを採用しました。「とりあえず全体をRust化」ではなく、ピンポイントで効かせる設計です。

4. ベンチマーク結果

同一環境(Windows 11、Python 3.11、SSD)でDictSQLiteと比較した結果です。

操作 DictSQLite NanaSQLite 倍率
初期化 84.88ms 11.00ms ⚡ 7.7倍速
単純書き込み(1000件) 101.08ms 110.86ms ほぼ同等
バッチ書き込み(1000件) 88.88ms 4.47ms ⚡ 19.9倍速
読み取り(1000件) 31.17ms 19.25ms ⚡ 1.6倍速
一括ロード+読み取り 3.78ms
ネストデータ(100件) 10.02ms 8.84ms ⚡ 1.1倍速
混合操作(1500操作) 101.80ms 123.62ms 0.8倍

単純書き込みと混合操作はDictSQLiteと同等〜若干遅い

1件ずつの書き込みは、SQLiteのトランザクションオーバーヘッドが支配的なため速度差が出にくいです。大量データにはbatch_update()を必ず使ってください

スループット比較

操作 DictSQLite NanaSQLite
バッチ書き込み 11,251件/秒 223,494件/秒
読み取り(遅延ロード) 32,079件/秒 51,961件/秒
読み取り(一括ロード) 4,570,384件/秒

なぜバッチ処理でここまで差が出るのか

# DictSQLite — 1件ずつ処理するたびに余分なロジックが走る
for key, value in data.items():
    # 暗号化チェック → pickle/JSON判定 → トランザクション管理 → 保存
    db[key] = value

# NanaSQLite — JSON一括変換 → SQLite一括INSERTで直行
db.batch_update(data)

余計な判定処理を削ぎ落としたことで、バッチ処理時のオーバーヘッドが激減しました。
「機能を削る」ことがパフォーマンス向上に直結した典型例です。

5. 実装例

基本的な使い方(3行)

from nanasqlite import NanaSQLite

db = NanaSQLite("myapp.db")
db["user"] = {"name": "Nana", "age": 20, "tags": ["admin"]}
print(db["user"]["name"])  # 'Nana'

Pydanticモデルと組み合わせる

from pydantic import BaseModel
from nanasqlite import NanaSQLite

class User(BaseModel):
    name: str
    age: int

with NanaSQLite("app.db") as db:
    db.set_model("user_1", User(name="Nana", age=20))
    user = db.get_model("user_1", User)
    print(type(user))  # <class 'User'>  型付きで返ってくる

Discord bot / FastAPIでの非同期対応

from nanasqlite import AsyncNanaSQLite
import asyncio

async def main():
    async with AsyncNanaSQLite("bot.db", max_workers=10) as db:
        await db.aset("guild_123", {"prefix": "!", "lang": "ja"})
        config = await db.aget("guild_123")
        print(config)

asyncio.run(main())

6. 開発環境

pip install nanasqlite

1日で集中開発し、以下を整備してリリースしました。

  • pytest による自動テスト完備
  • GitHub Actions によるCI/CD(push → 自動テスト → PyPI自動デプロイ)
  • Dependabot によるライブラリの自動更新

実際に自分が運営しているDiscordボット・VPSホスティングサービス「Disnana」の本番環境でも使用中です。

おわりに

DictSQLiteでの設計的な失敗、無計画なRust化での速度悪化、その2つの失敗があったからこそNanaSQLiteは生まれました。

「シンプルにすること」と「機能を削ること」は勇気が要りますが、それが最大のパフォーマンス改善につながるという実感を得られた開発でした。

「SQLiteをdictで使いたいだけ」な方は、ぜひ触ってみてください。

🔗 公式サイト: https://nanasqlite.disnana.com/
🐙 GitHub: https://github.com/disnana/nanasqlite (Starいただけると泣いて喜びます!)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?