Skip to the content.

Container caching: keeping the image where the task runs

What it shows

An edge node that runs an application needs that application’s container installed first, and pulling it over the network is expensive. CACHE mode answers this with a small storage-aware cache: every node reserves a few megabytes of its storage for containers, a request whose container is already there runs without a download, and a request whose container is missing fetches it once and keeps it for the next request. When the budget is full the least recently used container is evicted.

The Python core has no hook for a custom network model or a custom computing node, so the behaviour lives in ContainerCacheSimulationManager, a subclass of SimulationManager. On a placement it calls _needs_container, which consults the destination cache: a hit returns false and skips the transfer, a miss remembers the container in pending_store. When the download has finished, _send_request_to_destination stores it, evicting the oldest entries until the new one fits, or refusing it if it is larger than the whole cache. A plain dict keeps the access order, and a lookup moves the container to the end, so the first key is always the least recently used one.

The point of the example is that the bounded cache is a middle ground, so the script runs the same workload three times on the same seed, differing by one flag:

flowchart TD
    req["Task placed on a node"] --> look{"Container in the LRU cache?"}
    look -->|Hit| run["Run now, no download"]
    look -->|Miss| dl["Download from the registry"]
    dl --> fits{"Container plus used space fits 3 MB?"}
    fits -->|Yes| keep["Keep the container, then run"]
    fits -->|No, over capacity| refuse["Refuse, download again next time"]
    fits -->|No, but it can| evict["Evict least recently used until it fits"]
    evict --> keep
    keep --> run
    refuse --> run
    classDef cost fill:#ffe0b3,stroke:#333;
    class dl,evict,refuse cost;

The scenario

settings/ sets up one scenario:

CACHE_CAPACITY_MB is 3.0, so a node can hold the two heavy containers together but not all three, which forces real evictions. A node never reserves more than its own storage, and a sensor has no storage, so only the four data centers and the 120 battery or mains devices get a cache.

Run it

From the repository root, after pip install -e .:

python examples/container_cache/container_cache.py

No-install variant:

PYTHONPATH=src python examples/container_cache/container_cache.py

What to look for

The run prints a header line, then the cache report, then the three rows:

registry mode / container handling (same seed, same workload, same architecture)
ContainerCache - container cache: 10492 hits, 6378 misses (62.19 % hit ratio), 1308 evictions, 0 refused, 8.12 MB used of 372.00 MB
cache         tasks= 16870 ok=  1770 success=10.49 % avg_delay=0.1599   wan_usage=525137.076   containers_lan=0.0      traffic_MB=50294.24   energy=75.6774
cloud_always  tasks= 16870 ok=  1684 success=9.98 % avg_delay=0.1599   wan_usage=733151.644   containers_lan=0.0      traffic_MB=62212.0    energy=75.6774
cloud         tasks= 16870 ok=  1770 success=10.49 % avg_delay=0.1599   wan_usage=468487.352   containers_lan=0.0      traffic_MB=42505.72   energy=75.6774

All three rows carry the same workload and the same average execution delay, so the columns that move are the container traffic, the request makespan, the network energy, and, for the always-download baseline, the success count.

Container handling Total traffic (MB) Container WAN time (s) Request makespan (s)
LRU cache (cache) 50294.24 178701.912 0.9976
Download every request (cloud_always) 62212.00 445211.112 1.0463
Never evict (cloud) 42505.72 391.024 1.0212

Lower CACHE_CAPACITY_MB toward 1.5 to make the heavy 2.2 MB container refusable, or raise it above 3.7 to let every node hold all three and watch the evictions drop to zero.

Results go to examples/container_cache/output/.

Files

Where to go next