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.

perf: use local SQLite for dashboard/stats queries

routes dashboard and stats queries to local SQLite first,
falling back to Turso only when local db unavailable.
reduces dashboard latency from ~6s to <100ms.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

zzstoatzz 79c55f48 197bea88

+135
+100
backend/src/dashboard.zig
··· 67 67 ; 68 68 69 69 pub fn fetch(alloc: Allocator) !Data { 70 + // try local SQLite first (fast) 71 + if (db.getLocalDb()) |local| { 72 + if (fetchLocal(alloc, local)) |result| { 73 + return result; 74 + } else |_| {} 75 + } 76 + 77 + // fall back to Turso (slow) 70 78 const client = db.getClient() orelse return error.NotInitialized; 71 79 72 80 // batch all 5 queries into one HTTP request ··· 97 105 .platforms_json = try formatPlatformsJson(alloc, batch.get(1)), 98 106 .timing_json = try formatTimingJson(alloc), 99 107 }; 108 + } 109 + 110 + fn fetchLocal(alloc: Allocator, local: *db.LocalDb) !Data { 111 + // stats query 112 + var stats_rows = try local.query(STATS_SQL, .{}); 113 + defer stats_rows.deinit(); 114 + const stats_row = stats_rows.next() orelse return error.NoStats; 115 + 116 + const started_at = stats_row.int(4); 117 + const searches = stats_row.int(2); 118 + const publications = stats_row.int(1); 119 + const documents = stats_row.int(0); 120 + 121 + // platforms query 122 + var platforms_rows = try local.query(PLATFORMS_SQL, .{}); 123 + defer platforms_rows.deinit(); 124 + const platforms_json = try formatPlatformsJsonLocal(alloc, &platforms_rows); 125 + 126 + // tags query 127 + var tags_rows = try local.query(TAGS_SQL, .{}); 128 + defer tags_rows.deinit(); 129 + const tags_json = try formatTagsJsonLocal(alloc, &tags_rows); 130 + 131 + // timeline query 132 + var timeline_rows = try local.query(TIMELINE_SQL, .{}); 133 + defer timeline_rows.deinit(); 134 + const timeline_json = try formatTimelineJsonLocal(alloc, &timeline_rows); 135 + 136 + // top pubs query 137 + var pubs_rows = try local.query(TOP_PUBS_SQL, .{}); 138 + defer pubs_rows.deinit(); 139 + const top_pubs_json = try formatPubsJsonLocal(alloc, &pubs_rows); 140 + 141 + return .{ 142 + .started_at = started_at, 143 + .searches = searches, 144 + .publications = publications, 145 + .documents = documents, 146 + .tags_json = tags_json, 147 + .timeline_json = timeline_json, 148 + .top_pubs_json = top_pubs_json, 149 + .platforms_json = platforms_json, 150 + .timing_json = try formatTimingJson(alloc), 151 + }; 152 + } 153 + 154 + fn formatTagsJsonLocal(alloc: Allocator, rows: *db.LocalDb.Rows) ![]const u8 { 155 + var output: std.Io.Writer.Allocating = .init(alloc); 156 + errdefer output.deinit(); 157 + var jw: json.Stringify = .{ .writer = &output.writer }; 158 + try jw.beginArray(); 159 + while (rows.next()) |row| { 160 + try jw.write(TagJson{ .tag = row.text(0), .count = row.int(1) }); 161 + } 162 + try jw.endArray(); 163 + return try output.toOwnedSlice(); 164 + } 165 + 166 + fn formatTimelineJsonLocal(alloc: Allocator, rows: *db.LocalDb.Rows) ![]const u8 { 167 + var output: std.Io.Writer.Allocating = .init(alloc); 168 + errdefer output.deinit(); 169 + var jw: json.Stringify = .{ .writer = &output.writer }; 170 + try jw.beginArray(); 171 + while (rows.next()) |row| { 172 + try jw.write(TimelineJson{ .date = row.text(0), .count = row.int(1) }); 173 + } 174 + try jw.endArray(); 175 + return try output.toOwnedSlice(); 176 + } 177 + 178 + fn formatPubsJsonLocal(alloc: Allocator, rows: *db.LocalDb.Rows) ![]const u8 { 179 + var output: std.Io.Writer.Allocating = .init(alloc); 180 + errdefer output.deinit(); 181 + var jw: json.Stringify = .{ .writer = &output.writer }; 182 + try jw.beginArray(); 183 + while (rows.next()) |row| { 184 + try jw.write(PubJson{ .name = row.text(0), .basePath = row.text(1), .count = row.int(2) }); 185 + } 186 + try jw.endArray(); 187 + return try output.toOwnedSlice(); 188 + } 189 + 190 + fn formatPlatformsJsonLocal(alloc: Allocator, rows: *db.LocalDb.Rows) ![]const u8 { 191 + var output: std.Io.Writer.Allocating = .init(alloc); 192 + errdefer output.deinit(); 193 + var jw: json.Stringify = .{ .writer = &output.writer }; 194 + try jw.beginArray(); 195 + while (rows.next()) |row| { 196 + try jw.write(PlatformJson{ .platform = row.text(0), .count = row.int(1) }); 197 + } 198 + try jw.endArray(); 199 + return try output.toOwnedSlice(); 100 200 } 101 201 102 202 fn formatTagsJson(alloc: Allocator, rows: []const db.Row) ![]const u8 {
+35
backend/src/stats.zig
··· 79 79 const default_stats: Stats = .{ .documents = 0, .publications = 0, .embeddings = 0, .searches = 0, .errors = 0, .started_at = 0, .cache_hits = 0, .cache_misses = 0 }; 80 80 81 81 pub fn getStats() Stats { 82 + // try local SQLite first (fast) 83 + if (db.getLocalDb()) |local| { 84 + if (getStatsLocal(local)) |result| { 85 + return result; 86 + } else |_| {} 87 + } 88 + 89 + // fall back to Turso (slow) 82 90 const c = db.getClient() orelse return default_stats; 83 91 84 92 var res = c.query( ··· 95 103 defer res.deinit(); 96 104 97 105 const row = res.first() orelse return default_stats; 106 + return .{ 107 + .documents = row.int(0), 108 + .publications = row.int(1), 109 + .embeddings = row.int(2), 110 + .searches = row.int(3), 111 + .errors = row.int(4), 112 + .started_at = row.int(5), 113 + .cache_hits = row.int(6), 114 + .cache_misses = row.int(7), 115 + }; 116 + } 117 + 118 + fn getStatsLocal(local: *db.LocalDb) !Stats { 119 + var rows = try local.query( 120 + \\SELECT 121 + \\ (SELECT COUNT(*) FROM documents) as docs, 122 + \\ (SELECT COUNT(*) FROM publications) as pubs, 123 + \\ (SELECT COUNT(*) FROM documents WHERE embedding IS NOT NULL) as embeddings, 124 + \\ (SELECT total_searches FROM stats WHERE id = 1) as searches, 125 + \\ (SELECT total_errors FROM stats WHERE id = 1) as errors, 126 + \\ (SELECT service_started_at FROM stats WHERE id = 1) as started_at, 127 + \\ (SELECT COALESCE(cache_hits, 0) FROM stats WHERE id = 1) as cache_hits, 128 + \\ (SELECT COALESCE(cache_misses, 0) FROM stats WHERE id = 1) as cache_misses 129 + , .{}); 130 + defer rows.deinit(); 131 + 132 + const row = rows.next() orelse return error.NoRows; 98 133 return .{ 99 134 .documents = row.int(0), 100 135 .publications = row.int(1),