1use crate::types::Value;
26
27pub const MEMORY_SAFETY_MARGIN_PERCENT: usize = 90;
35
36pub fn effective_limit(max_bytes: usize) -> usize {
41 ((max_bytes as u128) * (MEMORY_SAFETY_MARGIN_PERCENT as u128) / 100) as usize
44}
45
46pub(crate) const ENTRY_OVERHEAD: usize = 128;
62
63#[derive(Debug)]
68pub struct MemoryTracker {
69 used_bytes: usize,
70 key_count: usize,
71}
72
73impl MemoryTracker {
74 pub fn new() -> Self {
76 Self {
77 used_bytes: 0,
78 key_count: 0,
79 }
80 }
81
82 pub fn reset(&mut self) {
84 self.used_bytes = 0;
85 self.key_count = 0;
86 }
87
88 pub fn used_bytes(&self) -> usize {
90 self.used_bytes
91 }
92
93 pub fn key_count(&self) -> usize {
95 self.key_count
96 }
97
98 pub fn add(&mut self, key: &str, value: &Value) {
100 self.used_bytes += entry_size(key, value);
101 self.key_count += 1;
102 }
103
104 pub fn remove(&mut self, key: &str, value: &Value) {
106 let size = entry_size(key, value);
107 self.used_bytes = self.used_bytes.saturating_sub(size);
108 self.key_count = self.key_count.saturating_sub(1);
109 }
110
111 pub fn replace(&mut self, key: &str, old_value: &Value, new_value: &Value) {
116 let old_size = entry_size(key, old_value);
117 let new_size = entry_size(key, new_value);
118 self.used_bytes = self
119 .used_bytes
120 .saturating_sub(old_size)
121 .saturating_add(new_size);
122 }
123
124 pub fn adjust(&mut self, old_entry_size: usize, new_entry_size: usize) {
130 self.used_bytes = self
131 .used_bytes
132 .saturating_sub(old_entry_size)
133 .saturating_add(new_entry_size);
134 }
135
136 pub fn remove_with_size(&mut self, size: usize) {
139 self.used_bytes = self.used_bytes.saturating_sub(size);
140 self.key_count = self.key_count.saturating_sub(1);
141 }
142}
143
144impl Default for MemoryTracker {
145 fn default() -> Self {
146 Self::new()
147 }
148}
149
150pub const LAZY_FREE_THRESHOLD: usize = 64;
155
156pub fn is_large_value(value: &Value) -> bool {
163 match value {
164 Value::String(_) => false,
165 Value::List(d) => d.len() > LAZY_FREE_THRESHOLD,
166 Value::SortedSet(ss) => ss.len() > LAZY_FREE_THRESHOLD,
167 Value::Hash(m) => m.len() > LAZY_FREE_THRESHOLD,
168 Value::Set(s) => s.len() > LAZY_FREE_THRESHOLD,
169 #[cfg(feature = "vector")]
172 Value::Vector(vs) => vs.len() > LAZY_FREE_THRESHOLD,
173 #[cfg(feature = "protobuf")]
176 Value::Proto { .. } => false,
177 }
178}
179
180pub fn entry_size(key: &str, value: &Value) -> usize {
184 key.len() + value_size(value) + ENTRY_OVERHEAD
185}
186
187pub(crate) const VECDEQUE_ELEMENT_OVERHEAD: usize = 32;
192
193pub(crate) const VECDEQUE_BASE_OVERHEAD: usize = 24;
195
196pub(crate) const HASHMAP_ENTRY_OVERHEAD: usize = 64;
201
202pub(crate) const HASHMAP_BASE_OVERHEAD: usize = 48;
204
205pub(crate) const HASHSET_MEMBER_OVERHEAD: usize = 40;
209
210pub(crate) const HASHSET_BASE_OVERHEAD: usize = 48;
212
213pub fn value_size(value: &Value) -> usize {
215 match value {
216 Value::String(data) => data.len(),
217 Value::List(deque) => {
218 let element_bytes: usize = deque
219 .iter()
220 .map(|b| b.len() + VECDEQUE_ELEMENT_OVERHEAD)
221 .sum();
222 VECDEQUE_BASE_OVERHEAD + element_bytes
223 }
224 Value::SortedSet(ss) => ss.memory_usage(),
225 Value::Hash(map) => {
226 let entry_bytes: usize = map
227 .iter()
228 .map(|(k, v)| k.len() + v.len() + HASHMAP_ENTRY_OVERHEAD)
229 .sum();
230 HASHMAP_BASE_OVERHEAD + entry_bytes
231 }
232 Value::Set(set) => {
233 let member_bytes: usize = set.iter().map(|m| m.len() + HASHSET_MEMBER_OVERHEAD).sum();
234 HASHSET_BASE_OVERHEAD + member_bytes
235 }
236 #[cfg(feature = "vector")]
237 Value::Vector(vs) => vs.memory_usage(),
238 #[cfg(feature = "protobuf")]
241 Value::Proto { type_name, data } => type_name.len() + data.len() + 48,
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use bytes::Bytes;
249
250 fn string_val(s: &str) -> Value {
251 Value::String(Bytes::from(s.to_string()))
252 }
253
254 #[test]
255 fn new_tracker_is_empty() {
256 let t = MemoryTracker::new();
257 assert_eq!(t.used_bytes(), 0);
258 assert_eq!(t.key_count(), 0);
259 }
260
261 #[test]
262 fn add_increases_usage() {
263 let mut t = MemoryTracker::new();
264 let val = string_val("hello");
265 t.add("key", &val);
266 assert_eq!(t.key_count(), 1);
267 assert_eq!(t.used_bytes(), entry_size("key", &val));
268 }
269
270 #[test]
271 fn remove_decreases_usage() {
272 let mut t = MemoryTracker::new();
273 let val = string_val("data");
274 t.add("k", &val);
275 t.remove("k", &val);
276 assert_eq!(t.used_bytes(), 0);
277 assert_eq!(t.key_count(), 0);
278 }
279
280 #[test]
281 fn replace_adjusts_usage() {
282 let mut t = MemoryTracker::new();
283 let old = string_val("short");
284 let new = string_val("a much longer value here");
285 t.add("k", &old);
286
287 let before = t.used_bytes();
288 t.replace("k", &old, &new);
289
290 assert_eq!(t.key_count(), 1);
291 assert!(t.used_bytes() > before);
293 assert_eq!(t.used_bytes(), entry_size("k", &new),);
294 }
295
296 #[test]
297 fn remove_saturates_at_zero() {
298 let mut t = MemoryTracker::new();
299 let val = string_val("x");
300 t.remove("k", &val);
302 assert_eq!(t.used_bytes(), 0);
303 assert_eq!(t.key_count(), 0);
304 }
305
306 #[test]
307 fn entry_size_accounts_for_key_and_value() {
308 let val = string_val("test");
309 let size = entry_size("mykey", &val);
310 assert_eq!(size, 5 + 4 + ENTRY_OVERHEAD);
312 }
313
314 #[test]
317 fn entry_overhead_not_too_small() {
318 use crate::keyspace::Entry;
319
320 let entry_size = std::mem::size_of::<Entry>();
321 let key_struct_size = std::mem::size_of::<String>();
322 let hashmap_per_entry = 8;
325 let minimum = entry_size + key_struct_size + hashmap_per_entry;
326
327 assert!(
328 ENTRY_OVERHEAD >= minimum,
329 "ENTRY_OVERHEAD ({ENTRY_OVERHEAD}) is less than measured minimum \
330 ({minimum} = Entry({entry_size}) + String({key_struct_size}) + \
331 hashmap({hashmap_per_entry}))"
332 );
333 }
334
335 #[test]
336 fn list_value_size() {
337 let mut deque = std::collections::VecDeque::new();
338 deque.push_back(Bytes::from("hello"));
339 deque.push_back(Bytes::from("world"));
340 let val = Value::List(deque);
341
342 let size = value_size(&val);
343 let expected = VECDEQUE_BASE_OVERHEAD
345 + (5 + VECDEQUE_ELEMENT_OVERHEAD)
346 + (5 + VECDEQUE_ELEMENT_OVERHEAD);
347 assert_eq!(size, expected);
348 }
349
350 #[test]
351 fn empty_list_value_size() {
352 let val = Value::List(std::collections::VecDeque::new());
353 assert_eq!(value_size(&val), VECDEQUE_BASE_OVERHEAD);
354 }
355
356 #[test]
357 fn multiple_entries() {
358 let mut t = MemoryTracker::new();
359 let v1 = string_val("aaa");
360 let v2 = string_val("bbbbb");
361 t.add("k1", &v1);
362 t.add("k2", &v2);
363
364 assert_eq!(t.key_count(), 2);
365 assert_eq!(
366 t.used_bytes(),
367 entry_size("k1", &v1) + entry_size("k2", &v2),
368 );
369
370 t.remove("k1", &v1);
371 assert_eq!(t.key_count(), 1);
372 assert_eq!(t.used_bytes(), entry_size("k2", &v2));
373 }
374
375 #[test]
376 fn effective_limit_applies_margin() {
377 assert_eq!(effective_limit(1000), 900);
379 }
380
381 #[test]
382 fn effective_limit_rounds_down() {
383 assert_eq!(effective_limit(1001), 900);
385 }
386
387 #[test]
388 fn effective_limit_zero() {
389 assert_eq!(effective_limit(0), 0);
390 }
391
392 #[test]
393 fn string_is_never_large() {
394 let val = Value::String(Bytes::from(vec![0u8; 10_000]));
395 assert!(!is_large_value(&val));
396 }
397
398 #[test]
399 fn small_list_is_not_large() {
400 let mut d = std::collections::VecDeque::new();
401 for _ in 0..LAZY_FREE_THRESHOLD {
402 d.push_back(Bytes::from("x"));
403 }
404 assert!(!is_large_value(&Value::List(d)));
405 }
406
407 #[test]
408 fn big_list_is_large() {
409 let mut d = std::collections::VecDeque::new();
410 for _ in 0..=LAZY_FREE_THRESHOLD {
411 d.push_back(Bytes::from("x"));
412 }
413 assert!(is_large_value(&Value::List(d)));
414 }
415
416 #[test]
417 fn big_hash_is_large() {
418 let mut m = std::collections::HashMap::new();
419 for i in 0..=LAZY_FREE_THRESHOLD {
420 m.insert(format!("f{i}"), Bytes::from("v"));
421 }
422 assert!(is_large_value(&Value::Hash(m)));
423 }
424
425 #[test]
426 fn big_set_is_large() {
427 let mut s = std::collections::HashSet::new();
428 for i in 0..=LAZY_FREE_THRESHOLD {
429 s.insert(format!("m{i}"));
430 }
431 assert!(is_large_value(&Value::Set(s)));
432 }
433}