Course contentsShow
← Rust
Lesson 1081 of 1,9109. Smart Pointers and Interior MutabilityFree lesson

Managing Long-Running Heap State

Implement periodic cleanup for persistent heap-allocated data structures.

In this lesson, you will learn how to implement periodic cleanup strategies to prevent memory bloat in persistent, heap-allocated data structures.

When building long-running services, you often maintain caches or registries in the heap using collections like HashMap or custom Vec-based buffers. If these structures grow indefinitely without a mechanism to discard stale items, your application will eventually suffer from "memory creep," leading to performance degradation or OOM (Out of Memory) kills.

The Strategy: TTL-Based Pruning

To manage this, we implement a cleanup strategy where the data structure tracks the "last used" or "inserted" timestamp of its items. Rather than relying on a complex background thread, you can trigger a "maintenance pass" during standard mutation operations.

Consider a simple Registry that cleans itself when it reaches a certain size or time threshold:

use std::collections::HashMap;
use std::time::{Instant, Duration};

struct Registry<T> {
    data: HashMap<String, (T, Instant)>,
    ttl: Duration,
}

impl<T> Registry<T> {
    fn insert(&mut self, key: String, value: T) {
        // Simple heuristic: cleanup every time we add a new item
        if self.data.len() > 1000 {
            self.prune();
        }
        self.data.insert(key, (value, Instant::now()));
    }

    fn prune(&mut self) {
        let now = Instant::now();
        self.data.retain(|_, (_, timestamp)| now.duration_since(*timestamp) < self.ttl);
    }
}

Why This Works

By invoking prune inside insert, you amortize the cost of memory management. Instead of stopping the world for a massive cleanup, you perform small, incremental removals that keep the heap footprint stable.

When choosing when to clean, follow these rules:

  1. Capacity-based: Prune when the number of elements exceeds a predefined threshold.
  2. Time-based: Only prune if a minimum duration has passed since the last cleanup to avoid excessive re-traversal of the collection.
  3. Lazy vs. Eager: Use lazy cleanup (only when accessing/inserting) for low-traffic systems, and move to dedicated background tasks for high-concurrency systems where blocking a write operation is unacceptable.

Key Takeaway: Prevent heap bloat by integrating periodic pruning into your data structure’s mutation methods, ensuring memory usage stays proportional to your active workload rather than your total historical activity.