sqlite: enable WAL + busy_timeout in connect(); 408/413/429 transient

The DB connection helper now sets `journal_mode=WAL`, `busy_timeout=5000`,
and `synchronous=NORMAL` on every connection. 13+ DAOs each open their
own connection through this helper and share one SQLite file — without
WAL, a writer's exclusive lock blocks readers and `load_persons` racing
the face-watch write storm errored instantly with "database is locked".
GPU face inference made this visible by speeding detect ~10× and
flooding the writer side. WAL persists in the file once set so the
debug binaries that bypass connect() inherit it automatically.

Also widen face_client.rs's classifier: 408 / 413 / 429 are now Transient
instead of Permanent. These are operator-fixable proxy/infra errors;
marking them Permanent poisons every affected photo with status='failed'
and requires manual SQL to recover. Specifically, Apollo's nginx
defaulted to a 1 MB body cap and silently rejected normal-size photos
before they reached the backend — the deferred-and-retry contract is
the right behavior for that class of fault.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cameron Cordes
2026-04-30 18:10:59 +00:00
parent 9443c91f88
commit db9dc63e5e
2 changed files with 45 additions and 1 deletions

View File

@@ -125,7 +125,24 @@ impl UserDao for SqliteUserDao {
pub fn connect() -> SqliteConnection {
let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
SqliteConnection::establish(&db_url).expect("Error connecting to DB")
let mut conn = SqliteConnection::establish(&db_url).expect("Error connecting to DB");
// Each DAO opens its own connection (13+ across the app) and they all
// share one DB file. Without WAL, a writer holds an exclusive lock
// that blocks readers — `load_persons` racing the face-watch write
// storm errors instantly with `database is locked`. WAL lets readers
// and one writer coexist; busy_timeout makes any remaining
// writer-vs-writer contention wait instead of failing fast.
// synchronous=NORMAL is the standard WAL pairing (FULL is for
// rollback-journal durability; we accept the narrow last-fsync
// window for the 210× write throughput).
use diesel::connection::SimpleConnection;
conn.batch_execute(
"PRAGMA journal_mode = WAL; \
PRAGMA busy_timeout = 5000; \
PRAGMA synchronous = NORMAL;",
)
.expect("set sqlite pragmas");
conn
}
#[derive(Debug)]