cache: a byte budget of 256 bytes per entry bounds the cached answers, evicting by clock, and a full or failed copy is counted instead of swallowed

This commit is contained in:
2026-10-22 08:54:59 +02:00
parent d951d03b7d
commit e4e405f8e9
11 changed files with 344 additions and 96 deletions
+28 -2
View File
@@ -67,6 +67,8 @@ pub const CacheSample = struct {
stats: dns_cache.Stats,
entries: u64,
memory_bytes: u64,
response_bytes: u64,
max_bytes: u64,
};
pub const LimiterSample = struct {
@@ -226,6 +228,8 @@ pub fn collect(state: *server.WebState, io: std.Io, arena: Allocator) Allocator.
.stats = cache.stats,
.entries = cache.len(),
.memory_bytes = cache.memoryBytes(),
.response_bytes = cache.live_bytes,
.max_bytes = cache.max_bytes,
};
}
@@ -414,6 +418,13 @@ pub fn render(w: *std.Io.Writer, sample: Sample) std.Io.Writer.Error!void {
try counterGroup(w, "nxdns_cache_", "DNS cache counter", cache.stats);
try gauge(w, "nxdns_cache_entries", "Responses currently held in the DNS cache.", cache.entries);
try gauge(w, "nxdns_cache_memory_bytes", "Bytes held by the DNS cache.", cache.memory_bytes);
try gauge(
w,
"nxdns_cache_response_bytes",
"Bytes of cached responses, the part nxdns_cache_max_bytes bounds.",
cache.response_bytes,
);
try gauge(w, "nxdns_cache_max_bytes", "The DNS cache's response byte budget.", cache.max_bytes);
}
if (sample.limiter) |limiter| {
@@ -873,9 +884,20 @@ test "a full sample renders the whole exposition, byte for byte" {
.logger = .{ .queries_dropped = 1, .rows_written = 40, .batches_gated = 2 },
.log_sink = .{ .lines_written = 5, .lines_deduped = 1, .rotations = 0, .sink_errors = 0 },
.cache = .{
.stats = .{ .hits = 7, .misses = 8, .inserts = 6, .evictions = 1, .expirations = 2, .invalid_hits = 0 },
.stats = .{
.hits = 7,
.misses = 8,
.inserts = 6,
.evictions = 1,
.expirations = 2,
.invalid_hits = 0,
.oversized = 3,
.copy_failures = 4,
},
.entries = 5,
.memory_bytes = 4096,
.response_bytes = 512,
.max_bytes = 1024,
},
.limiter = .{ .stats = .{ .allowed = 20, .refused = 2, .untracked = 1 }, .tracked_clients = 3 },
.tracker = .{
@@ -915,6 +937,10 @@ test "a full sample renders the whole exposition, byte for byte" {
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_cache_hits_total 7\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_cache_entries 5\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_cache_memory_bytes 4096\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_cache_response_bytes 512\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_cache_max_bytes 1024\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_cache_oversized_total 3\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_cache_copy_failures_total 4\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_dns_rate_limit_refused_total 2\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_dns_rate_limit_tracked_clients 3\n"));
try testing.expect(std.mem.containsAtLeast(u8, text, 1, "nxdns_clients_dropped_full_total 0\n"));
@@ -1733,7 +1759,7 @@ test "collect reads the live counters of the components it is given" {
defer threaded.deinit();
const io = threaded.io();
var cache = try dns_cache.DnsCache.init(testing.allocator, .{ .size = 4 });
var cache = try dns_cache.DnsCache.init(testing.allocator, .sized(4));
defer cache.deinit();
cache.stats.hits = 11;
cache.stats.misses = 5;