RustでSQLiteを使うとき、SQLxを選ぶ方は多いと思います。非同期対応で使いやすく、型安全なクエリも書けるので重宝しますよね。ただ、ライトトランザクションの扱いに関して、かなり厄介な落とし穴があることが最近話題になりました。
結論から言うと、非同期ランタイムでSQLxのライトトランザクションを使うと、ロックスタベーション(ロック飢餓)が発生して深刻なパフォーマンス劣化を引き起こす可能性があるんです。
SQLiteのロックモデルを理解する
まず前提として、SQLiteはシングルライターです。WALモードでも、読み取りと書き込みを同時に行えますが、書き込みは常に1つだけ。書き込みを行うにはEXCLUSIVEロックの取得が必要になります。
SELECTで始まったリードトランザクションは、INSERT/UPDATE/DELETEなどの書き込み文が来るとライトトランザクションにアップグレードされます。また、BEGIN IMMEDIATEやBEGIN EXCLUSIVEで開始したトランザクションは、即座にEXCLUSIVEロックを取得します。
問題の本質:awaitとロックの衝突
SQLxの非同期トランザクションで問題が発生するのは、ライトトランザクション内でawaitを呼ぶ場面です。こんなコードを想像してみてください。
let mut tx = db_connection.begin().await?;
for (id, value) in values {
sqlx::query("INSERT INTO table (id, some_field) VALUES ($1, $2)")
.bind(id).bind(value)
.execute(&mut *tx).await?;
}
tx.commit().await?;
最初のINSERTでトランザクションがライトに昇格し、EXCLUSIVEロックを取得します。そしてawaitでタスクが一時中断されるわけですが、この間もロックは保持されたままです。
非同期ランタイムが別のタスクをスケジュールして、そのタスクも書き込みを行おうとすると、ロックを取得できずにbusy_timeout(デフォルト5秒)まで待機し、最終的にタイムアウトエラーになります。元のタスクも他のタスクにブロックされて進めなくなり、結果としてロックスタベーションが発生してしまうんですね。
ログでの見つけ方
本番環境でこの問題を見つけるヒントがあります。SQLxの警告ログに「slow statement: execution time exceeded alert threshold」というメッセージが頻発し、経過時間がbusy_timeout(デフォルト5秒)に非常に近い値を示していたら、この問題の可能性が高いです。
対策方法
いくつかのアプローチがあります。
1. 書き込みを1つのクエリにまとめる
ループ内で複数のINSERTを実行する代わりに、バルクINSERTやUNION ALLを使って1つのクエリにまとめます。await回数が減るので、ロック保持中にタスク切り替えが起きるリスクが下がりますね。
2. 専用の書き込み接続を使う
読み取り用と書き込み用で接続を分離し、書き込み接続のプールサイズを1にします。これにより、書き込みが直列化されてデッドロックを防げます。
3. spawn_blockingを使う
書き込みトランザクション全体をspawn_blockingで同期的に実行する方法もあります。ブロッキングスレッドでロックを保持するため、非同期ランタイムの影響を受けません。
Rustの基本をおさえている方なら、spawn_blockingの使いどころは理解しやすいはずです。また、uvのようなRust製ツールの内部でも同様の問題に対処しているケースがあります。
SQLx以外でも起きるのか
この問題はSQLx特有ではなく、非同期ランタイム+SQLiteの組み合わせで普遍的に発生し得ます。Tokioベースのアプリケーションなら、SQLxに限らずどのSQLiteクライアントでも注意が必要です。
BunのようなJavaScriptランタイムでSQLiteを使う場合にも、シングルスレッド+非同期I/Oの特性上、似た問題が起きる可能性はあります。
まとめ
SQLxとSQLiteの組み合わせでライトトランザクション内にawaitが入ると、EXCLUSIVEロックの保持中にタスク切り替えが発生し、ロックスタベーションが起きるリスクがあります。本番のログで「elapsed: ~5s」の警告が頻発していたら要チェックです。
対策としてはバルクINSERT、書き込み接続の分離、spawn_blockingの活用が有効ですね。詳しくは原文の記事で具体的なコード例とともに解説されているので、RustでSQLiteを使っている方はぜひ一読をおすすめします。