feat: raise cache default to 100K entries, evict stalest instead of dropping
The 10K cap was too conservative — the blocklist alone holds 400K domains. At ~100 bytes per wire entry, 100K entries is ~10MB. When the cache is full and evict_expired doesn't free enough slots, evict_stalest removes the entry with the least remaining TTL instead of silently discarding the new insert.
This commit is contained in:
30
src/cache.rs
30
src/cache.rs
@@ -100,7 +100,7 @@ impl DnsCache {
|
|||||||
if self.entry_count >= self.max_entries {
|
if self.entry_count >= self.max_entries {
|
||||||
self.evict_expired();
|
self.evict_expired();
|
||||||
if self.entry_count >= self.max_entries {
|
if self.entry_count >= self.max_entries {
|
||||||
return;
|
self.evict_stalest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,6 +260,34 @@ impl DnsCache {
|
|||||||
});
|
});
|
||||||
self.entry_count -= count;
|
self.entry_count -= count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Evict the single entry closest to (or furthest past) expiry.
|
||||||
|
fn evict_stalest(&mut self) {
|
||||||
|
let mut worst: Option<(String, QueryType, Duration)> = None;
|
||||||
|
for (domain, type_map) in &self.entries {
|
||||||
|
for (qtype, entry) in type_map {
|
||||||
|
let age = entry.inserted_at.elapsed();
|
||||||
|
let remaining = entry.ttl.saturating_sub(age);
|
||||||
|
match &worst {
|
||||||
|
None => worst = Some((domain.clone(), *qtype, remaining)),
|
||||||
|
Some((_, _, w)) if remaining < *w => {
|
||||||
|
worst = Some((domain.clone(), *qtype, remaining));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((domain, qtype, _)) = worst {
|
||||||
|
if let Some(type_map) = self.entries.get_mut(&domain) {
|
||||||
|
if type_map.remove(&qtype).is_some() {
|
||||||
|
self.entry_count -= 1;
|
||||||
|
}
|
||||||
|
if type_map.is_empty() {
|
||||||
|
self.entries.remove(&domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CacheInfo {
|
pub struct CacheInfo {
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ impl Default for CacheConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn default_max_entries() -> usize {
|
fn default_max_entries() -> usize {
|
||||||
10000
|
100_000
|
||||||
}
|
}
|
||||||
fn default_min_ttl() -> u32 {
|
fn default_min_ttl() -> u32 {
|
||||||
60
|
60
|
||||||
|
|||||||
17
src/wire.rs
17
src/wire.rs
@@ -1350,18 +1350,25 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cache_max_entries_cap() {
|
fn cache_max_entries_evicts_stalest() {
|
||||||
let mut cache = DnsCache::new(2, 1, 3600);
|
let mut cache = DnsCache::new(2, 1, 3600);
|
||||||
for i in 0..3 {
|
// Insert with decreasing TTL so test0.com is stalest
|
||||||
|
for (i, ttl) in [(0, 60), (1, 3600)] {
|
||||||
let domain = format!("test{}.com", i);
|
let domain = format!("test{}.com", i);
|
||||||
let pkt = response(
|
let pkt = response(
|
||||||
i as u16,
|
i as u16,
|
||||||
&domain,
|
&domain,
|
||||||
vec![a_record(&domain, &format!("1.2.3.{}", i), 3600)],
|
vec![a_record(&domain, &format!("1.2.3.{}", i), ttl)],
|
||||||
);
|
);
|
||||||
cache.insert(&domain, QueryType::A, &pkt);
|
cache.insert(&domain, QueryType::A, &pkt);
|
||||||
}
|
}
|
||||||
// Should not exceed max (third insert is silently dropped or evicts)
|
assert_eq!(cache.len(), 2);
|
||||||
assert!(cache.len() <= 2);
|
|
||||||
|
// Third insert should evict test0.com (lowest remaining TTL)
|
||||||
|
let pkt = response(2, "test2.com", vec![a_record("test2.com", "1.2.3.2", 3600)]);
|
||||||
|
cache.insert("test2.com", QueryType::A, &pkt);
|
||||||
|
assert_eq!(cache.len(), 2);
|
||||||
|
assert!(cache.lookup("test0.com", QueryType::A).is_none()); // evicted
|
||||||
|
assert!(cache.lookup("test2.com", QueryType::A).is_some()); // inserted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user