search for standard sites pub-search.waow.tech
search zig blog atproto
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: separate read/write sqlite connections to eliminate search lock contention

Search queries were blocked 2-4s waiting on the mutex while sync held
the write lock for batch inserts. WAL mode supports concurrent readers
with separate connections — search now uses a dedicated read-only
connection with no mutex, so it never blocks on writes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz 12684618 d179d203

+14 -7
+14 -7
backend/src/db/LocalDb.zig
··· 10 10 const LocalDb = @This(); 11 11 12 12 conn: ?zqlite.Conn = null, 13 + read_conn: ?zqlite.Conn = null, // separate read connection — never blocked by writes in WAL mode 13 14 allocator: Allocator, 14 15 is_ready: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), 15 16 needs_resync: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), 16 - mutex: std.Thread.Mutex = .{}, 17 + mutex: std.Thread.Mutex = .{}, // protects write conn only 17 18 path: []const u8 = "", 18 19 consecutive_errors: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), 19 20 ··· 70 71 71 72 const flags = zqlite.OpenFlags.Create | zqlite.OpenFlags.ReadWrite; 72 73 self.conn = zqlite.open(path, flags) catch |err| { 73 - std.debug.print("local db: failed to open: {}\n", .{err}); 74 + std.debug.print("local db: failed to open write conn: {}\n", .{err}); 74 75 return err; 75 76 }; 76 77 77 78 // enable WAL for better concurrency 78 79 _ = self.conn.?.exec("PRAGMA journal_mode=WAL", .{}) catch {}; 79 80 _ = self.conn.?.exec("PRAGMA busy_timeout=5000", .{}) catch {}; 81 + 82 + // open separate read connection — WAL mode allows concurrent reads + writes 83 + self.read_conn = zqlite.open(path, zqlite.OpenFlags.ReadOnly) catch |err| { 84 + std.debug.print("local db: failed to open read conn: {}\n", .{err}); 85 + return err; 86 + }; 87 + _ = self.read_conn.?.exec("PRAGMA busy_timeout=1000", .{}) catch {}; 80 88 81 89 // check integrity - if corrupt, delete and recreate 82 90 if (!self.checkIntegrity()) { ··· 96 104 } 97 105 98 106 pub fn deinit(self: *LocalDb) void { 107 + if (self.read_conn) |c| c.close(); 108 + self.read_conn = null; 99 109 if (self.conn) |c| c.close(); 100 110 self.conn = null; 101 111 } ··· 269 279 } 270 280 }; 271 281 272 - /// Execute a SELECT query with comptime SQL, returns row iterator 282 + /// Execute a SELECT query using the read connection (never blocked by writes) 273 283 pub fn query(self: *LocalDb, comptime sql: []const u8, args: anytype) !Rows { 274 284 const span = logfire.span("db.local.query", .{ 275 285 .sql = truncateSql(sql), 276 286 }); 277 287 defer span.end(); 278 288 279 - self.mutex.lock(); 280 - defer self.mutex.unlock(); 281 - 282 - const c = self.conn orelse return error.NotOpen; 289 + const c = self.read_conn orelse return error.NotOpen; 283 290 const rows = c.rows(sql, args) catch |e| { 284 291 logfire.err("db.local.query failed: {s} | sql: {s}", .{ @errorName(e), truncateSql(sql) }); 285 292 return e;