remove follows cache, fetch fresh each time

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

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

Changed files
+2 -71
src
+2 -71
src/db.zig
··· 10 10 // static buffer for db path (env vars return slices, zqlite needs sentinel) 11 11 var db_path_buf: [256]u8 = undefined; 12 12 13 - // follows cache TTL: 5 minutes 14 - const FOLLOWS_CACHE_TTL_MS: i64 = 5 * 60 * 1000; 15 13 16 14 pub fn init() !void { 17 15 const db_path_env = posix.getenv("DATABASE_PATH") orelse "/data/feed.db"; ··· 249 247 return rest[0..end]; 250 248 } 251 249 252 - /// get follows set for a DID, using cache if fresh 250 + /// get follows set for a DID 253 251 fn getFollowsSet(alloc: std.mem.Allocator, requester_did: []const u8) !std.StringHashMap(void) { 254 252 var set = std.StringHashMap(void).init(alloc); 255 253 errdefer set.deinit(); 256 254 257 - const now = std.time.milliTimestamp(); 258 - 259 - // check cache freshness 260 - mutex.lock(); 261 - const conn = db orelse { 262 - mutex.unlock(); 263 - return error.NotInitialized; 264 - }; 265 - 266 - var cache_fresh = false; 267 - var cache_rows = conn.rows( 268 - "SELECT cached_at FROM follows_cache WHERE requester_did = ? LIMIT 1", 269 - .{requester_did}, 270 - ) catch { 271 - mutex.unlock(); 272 - return set; 273 - }; 274 - 275 - if (cache_rows.next()) |row| { 276 - const cached_at = row.int(0); 277 - if (now - cached_at < FOLLOWS_CACHE_TTL_MS) { 278 - cache_fresh = true; 279 - } 280 - } 281 - cache_rows.deinit(); 282 - 283 - if (cache_fresh) { 284 - // load from cache 285 - var follows_rows = conn.rows( 286 - "SELECT follows_did FROM follows_cache WHERE requester_did = ?", 287 - .{requester_did}, 288 - ) catch { 289 - mutex.unlock(); 290 - return set; 291 - }; 292 - 293 - while (follows_rows.next()) |row| { 294 - const did = row.text(0); 295 - const owned = try alloc.dupe(u8, did); 296 - try set.put(owned, {}); 297 - } 298 - follows_rows.deinit(); 299 - mutex.unlock(); 300 - 301 - std.debug.print("loaded {d} follows from cache for {s}\n", .{ set.count(), requester_did }); 302 - return set; 303 - } 304 - mutex.unlock(); 305 - 306 - // fetch fresh follows from API 307 255 std.debug.print("fetching follows for {s}...\n", .{requester_did}); 308 256 const follows = atproto.getFollows(alloc, requester_did) catch |err| { 309 257 std.debug.print("failed to fetch follows: {}\n", .{err}); ··· 311 259 }; 312 260 defer alloc.free(follows); 313 261 314 - // parse follows and populate set 315 262 var it = std.mem.splitScalar(u8, follows, '\n'); 316 263 while (it.next()) |did| { 317 264 if (did.len == 0) continue; ··· 321 268 322 269 std.debug.print("fetched {d} follows for {s}\n", .{ set.count(), requester_did }); 323 270 324 - // trigger lazy backfill for follows we haven't seen before 271 + // trigger backfill for follows we haven't seen before 325 272 var dids_to_backfill: std.ArrayList([]const u8) = .empty; 326 273 defer dids_to_backfill.deinit(alloc); 327 274 var key_it = set.keyIterator(); ··· 330 277 } 331 278 if (dids_to_backfill.items.len > 0) { 332 279 triggerBackfill(alloc, dids_to_backfill.items); 333 - } 334 - 335 - // update cache 336 - mutex.lock(); 337 - defer mutex.unlock(); 338 - 339 - // clear old cache for this user 340 - conn.exec("DELETE FROM follows_cache WHERE requester_did = ?", .{requester_did}) catch {}; 341 - 342 - // insert new follows 343 - var set_it = set.keyIterator(); 344 - while (set_it.next()) |key| { 345 - conn.exec( 346 - "INSERT INTO follows_cache (requester_did, follows_did, cached_at) VALUES (?, ?, ?)", 347 - .{ requester_did, key.*, now }, 348 - ) catch {}; 349 280 } 350 281 351 282 return set;