diff --git a/src/event_log.zig b/src/event_log.zig index ff47dbe..dc47bfd 100644 --- a/src/event_log.zig +++ b/src/event_log.zig @@ -94,6 +94,7 @@ pub const DiskPersist = struct { // DID → UID cache (matches indigo's bidirectional ARC cache) did_cache: std.StringHashMapUnmanaged(u64) = .{}, did_cache_mutex: std.Thread.Mutex = .{}, + max_did_cache_size: u32 = 500_000, // write buffer (flushed periodically or when threshold hit) outbuf: std.ArrayListUnmanaged(u8) = .{}, @@ -276,13 +277,7 @@ pub const DiskPersist = struct { var r = row; defer r.deinit() catch {}; const uid: u64 = @intCast(r.get(i64, 0)); - // populate cache - const did_duped = try self.allocator.dupe(u8, did); - self.did_cache_mutex.lock(); - defer self.did_cache_mutex.unlock(); - self.did_cache.put(self.allocator, did_duped, uid) catch { - self.allocator.free(did_duped); - }; + self.didCachePut(did, uid); return uid; } @@ -303,15 +298,31 @@ pub const DiskPersist = struct { defer row.deinit() catch {}; const uid: u64 = @intCast(row.get(i64, 0)); - // populate cache - const did_duped = try self.allocator.dupe(u8, did); + self.didCachePut(did, uid); + return uid; + } + + /// insert into did_cache, evicting if at capacity. + /// this is a pure lookup cache over postgres — clearing it only costs + /// ~0.5ms per miss on the next lookup for that DID. + fn didCachePut(self: *DiskPersist, did: []const u8, uid: u64) void { self.did_cache_mutex.lock(); defer self.did_cache_mutex.unlock(); + + // evict when at capacity: free all keys and clear the map. + // unlike the validator cache there's no per-entry timestamp to sort by, + // and the postgres fallback is fast enough that a full clear is fine. + if (self.did_cache.count() >= self.max_did_cache_size) { + log.info("did_cache at capacity ({d}), clearing", .{self.did_cache.count()}); + var it = self.did_cache.iterator(); + while (it.next()) |entry| self.allocator.free(entry.key_ptr.*); + self.did_cache.clearRetainingCapacity(); + } + + const did_duped = self.allocator.dupe(u8, did) catch return; self.did_cache.put(self.allocator, did_duped, uid) catch { self.allocator.free(did_duped); }; - - return uid; } /// per-DID sync state for chain tracking