perf(db): reuse SQLite connections via a small per-store pool

Every DB operation (queue lease/enqueue, chat_state get/set, link_cache
read/write) used to open a fresh connection — including the busy timeout
and WAL pragma — then close it, on every message, URL job and callback.

Replace with DbPool: a tiny pool (4 connections max, semaphore-bounded
concurrency for backpressure) whose with_conn() method runs the closure on
a pooled connection inside spawn_blocking. Steady-state cost of an
operation is a list pop + semaphore acquire instead of a connection open.
This commit is contained in:
2026-08-13 22:12:34 +08:00
parent 4a467641aa
commit edb32c23b4
4 changed files with 120 additions and 47 deletions
+4 -4
View File
@@ -38,7 +38,7 @@ pub struct ChatStore {
/// Per-chat async locks serializing get→mutate→set so concurrent handler
/// tasks (batch-forwards, callbacks) cannot clobber each other's writes.
locks: Mutex<HashMap<i64, Arc<tokio::sync::Mutex<()>>>>,
db_path: String,
pool: crate::db::DbPool,
}
pub fn unix_now() -> i64 {
@@ -67,7 +67,7 @@ impl ChatStore {
Ok(ChatStore {
cache: Mutex::new(HashMap::new()),
locks: Mutex::new(HashMap::new()),
db_path: path.to_string(),
pool: crate::db::DbPool::new(path),
})
}
@@ -76,7 +76,7 @@ impl ChatStore {
return data.clone();
}
let chat_key = chat_id.to_string();
let payload = crate::db::with_conn(&self.db_path, move |conn| {
let payload = self.pool.with_conn(move |conn| {
// Concurrent handler tasks (batch-forwards) may write chat_state
// while this read runs; the shared busy timeout handles the
// write-lock collision instead of failing the query.
@@ -103,7 +103,7 @@ impl ChatStore {
self.cache.lock().insert(chat_id, data.clone());
let payload = serde_json::to_string(data).expect("chat state serializes");
let chat_id = chat_id.to_string();
let result = crate::db::with_conn(&self.db_path, move |conn| {
let result = self.pool.with_conn(move |conn| {
conn.execute(
"INSERT OR REPLACE INTO chat_state (chat_id, payload) VALUES (?1, ?2)",
params![chat_id, payload],