{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "71f85f2a-423f-44d2-b80d-da9ac8d3961a", "metadata": {}, "outputs": [], "source": [ "import simpy\n", "import random\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import pandas as pd\n", "from enum import Enum\n", "import os\n", "import shutil\n", "from tqdm import tqdm\n", "import math\n", "\n", "# Types of cache\n", "class CacheType(Enum):\n", " LRU = 1\n", " RANDOM_EVICTION = 2\n", " TTL = 3\n", "\n", "# Constants\n", "SEED = 42\n", "ACCESS_COUNT_LIMIT = 10 # Total time to run the simulation\n", "EXPERIMENT_BASE_DIR = \"./experiments/\"\n", "TEMP_BASE_DIR = \"./.aoi_cache/\"\n", "\n", "ZIPF_CONSTANT = 2 # Shape parameter for the Zipf distribution (controls skewness) Needs to be: 1< \n", "\n", "# Set random seeds\n", "random.seed(SEED)\n", "np.random.seed(SEED)\n", "\n", "os.makedirs(TEMP_BASE_DIR, exist_ok=True)" ] }, { "cell_type": "markdown", "id": "9a37d7a3-3e11-4b89-8dce-6091dd38b16f", "metadata": {}, "source": [ "How to set certain parameters for specific scenarios\n", "\n", "\n", "| Name | Cache Capacity | MAX_REFRESH_RATE | cache_type | CACHE_TTL |\n", "| -------------------- | -------------------- | ---------------- | ------------------------- | --------- |\n", "| Default | DATABASE_OBJECTS | 1< | CacheType.LRU | 5 |\n", "| No Refresh | DATABASE_OBJECTS | 0 | CacheType.LRU | 5 |\n", "| Infinite TTL | DATABASE_OBJECTS / 2 | 0 | CacheType.LRU | 0 |\n", "| Random Eviction (RE) | DATABASE_OBJECTS / 2 | 1< | CacheType.RANDOM_EVICTION | 5 |\n", "| RE without Refresh | DATABASE_OBJECTS / 2 | 0 | CacheType.RANDOM_EVICTION | 5 |\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "3d0ab5b1-162a-42c8-80a3-d31f763101f1", "metadata": {}, "outputs": [], "source": [ "# Configuration (Just example, will be overwritten in next block\n", "DATABASE_OBJECTS = 100\n", "CACHE_CAPACITY = DATABASE_OBJECTS # Maximum number of objects the cache can hold\n", "\n", "# MAX_REFRESH_RATE is used as the maximum for a uniform\n", "# distribution for mu.\n", "# If MAX_REFRESH_RATE is 0, we do not do any refreshes.\n", "MAX_REFRESH_RATE = 0\n", "\n", "cache_type = CacheType.LRU\n", "\n", "# CACHE_TTL is used to determin which TTL to set when an\n", "# object is pulled into the cache\n", "# If CACHE_TTL is set to 0, the TTL is infinite\n", "CACHE_TTL = 5\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "3ff299ca-ec65-453b-b167-9a0f7728a207", "metadata": {}, "outputs": [], "source": [ "configurations = {\n", " \"default\": (100, 100, 10, CacheType.LRU, 5),\n", " \"No Refresh\": (100, 100, 0, CacheType.LRU, 5),\n", " \"Infinite TTL\": (100, 50, 0, CacheType.LRU, 0),\n", " \"Random Eviction\": (100, 50, 10, CacheType.RANDOM_EVICTION, 5),\n", " \"RE without Refresh\": (100, 50, 0, CacheType.RANDOM_EVICTION, 5),\n", " \"No Refresh (0.5s ttl)\": (100, 100, 0, CacheType.TTL, 0.5),\n", " \"No Refresh (1.0s ttl)\": (100, 100, 0, CacheType.TTL, 1),\n", " \"No Refresh (2.0s ttl)\": (100, 100, 0, CacheType.TTL, 2),\n", " \"No Refresh (3.0s ttl)\": (100, 100, 0, CacheType.TTL, 3),\n", " \"No Refresh (4.0s ttl)\": (100, 100, 0, CacheType.TTL, 4),\n", " \"No Refresh (5.0s ttl)\": (100, 100, 0, CacheType.TTL, 5),\n", " \"No Refresh (tests ttl)\": (3, 3, 0, CacheType.TTL, 1),\n", "}\n", "\n", "experiment_name = \"No Refresh (tests ttl)\"\n", "config = configurations[experiment_name]\n", "\n", "DATABASE_OBJECTS = config[0]\n", "CACHE_CAPACITY = config[1]\n", "MAX_REFRESH_RATE = config[2]\n", "cache_type = config[3]\n", "CACHE_TTL = config[4]\n", "\n", "if cache_type == CacheType.TTL:\n", " assert CACHE_TTL > 0, \"Needs CACHE_TTL to be greater than 0 when using TTL-Cache.\"\n", " assert CACHE_CAPACITY >= DATABASE_OBJECTS, \"Cache Size needs to be greater or equal to the amount of Database Objects.\"" ] }, { "cell_type": "code", "execution_count": 4, "id": "5cea042f-e9fc-4a1e-9750-de212ca70601", "metadata": {}, "outputs": [], "source": [ "class Database:\n", " def __init__(self):\n", " # Each object now has a specific refresh rate 'mu'\n", " self.data = {i: f\"Object {i}\" for i in range(1, DATABASE_OBJECTS + 1)}\n", " self.lambda_values = {i: np.random.zipf(ZIPF_CONSTANT) for i in range(1, DATABASE_OBJECTS + 1)} # Request rate 'lambda' for each object\n", " # Refresh rate 'mu' for each object\n", " if MAX_REFRESH_RATE == 0:\n", " self.mu_values = {i: 0 for i in range(1,DATABASE_OBJECTS + 1)} \n", " else:\n", " self.mu_values = {i: np.random.uniform(1, MAX_REFRESH_RATE) for i in range(1, DATABASE_OBJECTS + 1)}\n", " self.next_request = {i: np.random.exponential(1/self.lambda_values[i]) for i in range(1, DATABASE_OBJECTS + 1)}\n", "\n", "\n", " def get_object(self, obj_id):\n", " # print(f\"[{env.now:.2f}] Database: Fetched {self.data.get(obj_id, 'Unknown')} for ID {obj_id}\")\n", " return self.data.get(obj_id, None)" ] }, { "cell_type": "code", "execution_count": 5, "id": "499bf543-b2c6-4e4d-afcc-0a6665ce3ae1", "metadata": {}, "outputs": [], "source": [ "class Cache:\n", " def __init__(self, env, db, cache_type):\n", " self.cache_type = cache_type\n", " self.env = env\n", " self.db = db\n", " self.storage = {} # Dictionary to store cached objects\n", " self.ttl = {} # Dictionary to store TTLs\n", " self.initial_fetch = {} # Dictionary to store when an object was fetched from the databse to determine the age\n", " self.cache_size_over_time = [] # To record cache state at each interval\n", " self.cache_next_request_over_time = []\n", " self.request_log = {i: [] for i in range(1, DATABASE_OBJECTS + 1)}\n", " self.hits = {i: 0 for i in range(1, DATABASE_OBJECTS + 1)} # Track hits per object\n", " self.misses = {i: 0 for i in range(1, DATABASE_OBJECTS + 1)} # Track misses per object\n", " self.cumulative_age = {i: [] for i in range(1, DATABASE_OBJECTS + 1)} # Track cumulative age per object\n", " self.access_count = {i: 0 for i in range(1, DATABASE_OBJECTS + 1)} # Track access count per object\n", " self.next_refresh = {} # Track the next refresh time for each cached object\n", " self.object_start_time = {} # Used as helper variable to determine the starting time of an object in the cache\n", " self.cumulative_cache_time = {i: 0 for i in range(1, DATABASE_OBJECTS + 1)} # Stores the cumulative time the object has spent between its eviction and when it was first pulled into the cache\n", " \n", " def get(self, obj_id):\n", " if obj_id in self.storage:\n", " # Cache hit: Refresh TTL if TTL-Cache\n", " if self.cache_type == CacheType.TTL:\n", " if self.ttl[obj_id] > env.now:\n", " self.ttl[obj_id] = env.now + CACHE_TTL\n", " \n", " # Cache hit: increment hit count and update cumulative age\n", " self.hits[obj_id] += 1\n", " self.access_count[obj_id] += 1\n", "\n", " age = env.now - self.initial_fetch[obj_id]\n", " self.cumulative_age[obj_id].append(age)\n", "\n", " assert len(self.cumulative_age[obj_id]) == self.access_count[obj_id], \"Age values collected and object access count do not match.\"\n", "\n", " print(f\"[{env.now:.2f}] {obj_id} Hit: Current Age {age:.2f} (Average: {sum(self.cumulative_age[obj_id])/len(self.cumulative_age[obj_id]):.2f}) \")\n", " # Cache hit: Refresh database object on hit\n", " # self.initial_fetch[obj_id] = env.now\n", " else:\n", " assert obj_id not in self.storage.keys(), \"Found object in cache on miss. It should've been deleted.\"\n", " assert obj_id not in self.initial_fetch.keys(), \"Found age timer on miss. It should've been deleted.\"\n", " assert obj_id not in self.object_start_time.keys(), \"Found cache time ratio timer on miss. It should've been deleted.\"\n", " # Cache miss: Add TTL if TTL-Cache\n", " # When full cache: If non-TTL-Cache: Evict. If TTL-Cache: Don't add to Cache.\n", " if self.cache_type == CacheType.TTL:\n", " assert obj_id not in self.ttl.keys(), \"Found cache time ratio timer on miss. It should've been deleted.\"\n", " self.ttl[obj_id] = env.now + CACHE_TTL\n", " else:\n", " if len(self.storage) == DATABASE_OBJECTS:\n", " if self.cache_type == CacheType.LRU:\n", " self.evict_oldest()\n", " elif self.cache_type == CacheType.RANDOM_EVICTION:\n", " self.evict_random()\n", " elif self.cache_type == CacheType.TTL:\n", " return\n", " \n", " # Cache miss: increment miss count\n", " self.misses[obj_id] += 1\n", " self.access_count[obj_id] += 1\n", " \n", " # Cache miss: Fetch the object from the database\n", " self.storage[obj_id] = self.db.get_object(obj_id)\n", " self.object_start_time[obj_id] = env.now\n", " \n", " self.initial_fetch[obj_id] = env.now\n", " age = env.now - self.initial_fetch[obj_id]\n", " assert age == 0, \"Initial age at miss is not 0\"\n", " self.cumulative_age[obj_id].append(age)\n", "\n", " assert len(self.cumulative_age[obj_id]) == self.access_count[obj_id], \"Age values collected and object access count do not match.\"\n", " \n", " print(f\"[{env.now:.2f}] {obj_id} Miss: Average Age {sum(self.cumulative_age[obj_id])/len(self.cumulative_age[obj_id]):.2f} \")\n", " \n", " if MAX_REFRESH_RATE != 0:\n", " self.next_refresh[obj_id] = env.now + np.random.exponential(1/self.db.mu_values[obj_id]) # Schedule refresh\n", " \n", " \n", " def evict_oldest(self):\n", " \"\"\"Remove the oldest item from the cache to make space.\"\"\"\n", " oldest_id = min(self.initial_fetch, key=self.initial_fetch.get) # Find the oldest item by age\n", " print(f\"[{env.now:.2f}] Cache: Evicting oldest object {oldest_id} to make space at {self.ttl[oldest_id]:.2f}\")\n", " self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n", " del self.storage[oldest_id]\n", " del self.initial_fetch[oldest_id]\n", " del self.object_start_time[obj_id]\n", "\n", " def evict_random(self):\n", " \"\"\"Remove a random item from the cache to make space.\"\"\"\n", " random_id = np.random.choice(list(self.storage.keys())) # Select a random key from the cache\n", " print(f\"[{env.now:.2f}] Cache: Evicting random object {random_id} to make space at {self.ttl[random_id]:.2f}\")\n", " self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n", " del self.storage[random_id]\n", " del self.initial_fetch[random_id]\n", " del self.object_start_time[obj_id]\n", " \n", " def refresh_object(self, obj_id):\n", " \"\"\"Refresh the object from the database to keep it up-to-date. TTL is increased on refresh.\"\"\"\n", " obj = self.db.get_object(obj_id)\n", " self.storage[obj_id] = obj\n", " if self.cache_type == CacheType.TTL:\n", " self.ttl[obj_id] = env.now + CACHE_TTL\n", " self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n", " # print(f\"[{env.now:.2f}] Cache: Refreshed object {obj_id}\")\n", " \n", " def check_expired(self):\n", " \"\"\"Increment age of each cached object.\"\"\"\n", " evicted_objects = []\n", " for obj_id in list(self.ttl.keys()):\n", " if self.ttl[obj_id] <= env.now:\n", " # Remove object if its TTL expired\n", " print(f\"[{env.now:.2f}] Cache: Object {obj_id} expired\")\n", " evicted_objects.append(obj_id)\n", " self.cumulative_cache_time[obj_id] += (env.now - self.object_start_time[obj_id])\n", " del self.storage[obj_id]\n", " del self.ttl[obj_id]\n", " del self.initial_fetch[obj_id]\n", " del self.object_start_time[obj_id]\n", " return evicted_objects\n", "\n", " \n", " def record_cache_state(self):\n", " \"\"\"Record the current cache state (number of objects in cache) over time.\"\"\"\n", " self.cache_size_over_time.append((env.now, len(self.storage)))\n", " self.cache_next_request_over_time.append((env.now, self.db.next_request.copy()))" ] }, { "cell_type": "code", "execution_count": 6, "id": "7286d498-aa6c-4efb-bb28-fe29736eab64", "metadata": {}, "outputs": [], "source": [ "def age_cache_process(env, cache):\n", " \"\"\"Process that ages cache objects over time, removes expired items, and refreshes based on object-specific intervals.\"\"\"\n", " while True:\n", " if cache.cache_type == CacheType.TTL:\n", " if cache.storage:\n", " obj_id, next_eviction = min(cache.ttl.items(), key=lambda x: x[1])\n", " print(f\"[{env.now:.2f}] Waiting for next eviction...\")\n", " yield env.timeout(next_eviction - env.now) # Wait till next request (subject to change when object has been hit)\n", "\n", " \n", " if next_eviction == cache.ttl[obj_id]:\n", " print(f\"[{env.now:.2f}] Object {obj_id} needs to be evicted (At time: {next_eviction})\")\n", " evicted_objects = cache.check_expired()\n", " print(f\"[{env.now:.2f}] Evicted {len(evicted_objects)} object(s).\")\n", " assert len(evicted_objects) != 0, \"There was no object to evict.\"\n", " else:\n", " print(f\"[{env.now:.2f}] Object TTL was extended.\")\n", " evicted_objects = cache.check_expired()\n", " assert len(evicted_objects) == 0, \"There would've been an object to evict.\"\n", " continue\n", " else:\n", " obj_id, next_request = min(cache.db.next_request.items(), key=lambda x: x[1])\n", " print(f\"[{env.now:.2f}] Waiting for next request...\")\n", " yield env.timeout(next_request - env.now) # Wait till next request (subject to change when object has been hit)\n", " \n", " evicted_objects = cache.check_expired()\n", " assert len(evicted_objects) == 0, \"There would've been an object to evict.\"\n", " evicted_objects = cache.check_expired()\n", " assert len(evicted_objects) == 0, \"There would've been an object to evict.\"\n", " else:\n", " yield env.timeout(0.05) # Run every 0.05 second\n", " \n", " if MAX_REFRESH_RATE != 0:\n", " # Refresh objects based on their individual refresh intervals\n", " for obj_id in list(cache.storage.keys()):\n", " # Check if it's time to refresh this object based on next_refresh\n", " if env.now >= cache.next_refresh[obj_id]:\n", " cache.refresh_object(obj_id)\n", " # Schedule the next refresh based on the object's mu\n", " cache.next_refresh[obj_id] = env.now + np.random.exponential(1/cache.db.mu_values[obj_id])\n", " \n", " cache.record_cache_state() # Record cache state at each time step" ] }, { "cell_type": "code", "execution_count": 7, "id": "687f5634-8edf-4337-b42f-bbb292d47f0f", "metadata": {}, "outputs": [], "source": [ "def client_request_process(env, cache, event):\n", " \"\"\"Client process that makes requests for objects from the cache.\"\"\"\n", " last_print = 0\n", " with tqdm(total=ACCESS_COUNT_LIMIT, desc=\"Progress\", leave=True) as pbar:\n", " while True:\n", " obj_id, next_request = min(cache.db.next_request.items(), key=lambda x: x[1])\n", " yield env.timeout(next_request - env.now)\n", "\n", " # For progress bar\n", " if (int(env.now) % 1) == 0 and int(env.now) != last_print:\n", " last_print = int(env.now)\n", " pbar.n = min(cache.access_count.values())\n", " pbar.refresh()\n", " \n", " if env.now >= next_request:\n", " # print(f\"[{env.now:.2f}] Client: Requesting object {obj_id}\")\n", " cache.get(obj_id)\n", " \n", " # print(f\"[{env.now:.2f}] Client: Schedule next request for {obj_id}\")\n", " next_request = env.now + np.random.exponential(1/cache.db.lambda_values[obj_id])\n", " cache.request_log[obj_id].append(next_request)\n", " cache.db.next_request[obj_id] = next_request\n", " \n", " # Simulation stop condition\n", " if all(access_count >= ACCESS_COUNT_LIMIT for access_count in cache.access_count.values()):\n", " print(f\"Simulation ended after {env.now} seconds.\")\n", " for obj_id in cache.storage.keys():\n", " cache.cumulative_cache_time[obj_id] += (env.now - cache.object_start_time[obj_id])\n", " event.succeed()" ] }, { "cell_type": "code", "execution_count": 8, "id": "c8516830-9880-4d9e-a91b-000338baf9d6", "metadata": { "scrolled": true }, "outputs": [], "source": [ "# Initialize simulation environment\n", "env = simpy.Environment()\n", "\n", "# Instantiate components\n", "db = Database()\n", "cache = Cache(env, db, cache_type)\n", "stop_event = env.event()" ] }, { "cell_type": "code", "execution_count": 9, "id": "2ba34b36-9ed5-4996-9600-11dfd25d8e60", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.00] Waiting for next request...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Progress: 70%|████████████████████████████████████████████████████████████████▍ | 7/10 [00:00<00:00, 1560.05it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[0.06] Waiting for next request...\n", "[0.06] 1 Miss: Average Age 0.00 \n", "[0.06] Waiting for next eviction...\n", "[0.67] 2 Miss: Average Age 0.00 \n", "[0.68] 2 Hit: Current Age 0.01 (Average: 0.00) \n", "[0.92] 3 Miss: Average Age 0.00 \n", "[1.06] Object 1 needs to be evicted (At time: 1.0598387686086808)\n", "[1.06] Cache: Object 1 expired\n", "[1.06] Evicted 1 object(s).\n", "[1.06] Waiting for next eviction...\n", "[1.29] 1 Miss: Average Age 0.00 \n", "[1.53] 1 Hit: Current Age 0.24 (Average: 0.08) \n", "[1.68] Object 2 needs to be evicted (At time: 1.6773433908263593)\n", "[1.68] Cache: Object 2 expired\n", "[1.68] Evicted 1 object(s).\n", "[1.68] Waiting for next eviction...\n", "[1.73] 1 Hit: Current Age 0.44 (Average: 0.17) \n", "[1.85] 2 Miss: Average Age 0.00 \n", "[1.92] Object 3 needs to be evicted (At time: 1.9190821536272646)\n", "[1.92] Cache: Object 3 expired\n", "[1.92] Evicted 1 object(s).\n", "[1.92] Waiting for next eviction...\n", "[1.93] 1 Hit: Current Age 0.64 (Average: 0.26) \n", "[1.97] 2 Hit: Current Age 0.12 (Average: 0.03) \n", "[2.15] 2 Hit: Current Age 0.31 (Average: 0.09) \n", "[2.27] 2 Hit: Current Age 0.42 (Average: 0.14) \n", "[2.58] 2 Hit: Current Age 0.74 (Average: 0.23) \n", "[2.63] 2 Hit: Current Age 0.79 (Average: 0.30) \n", "[2.68] 1 Hit: Current Age 1.39 (Average: 0.45) \n", "[2.71] 3 Miss: Average Age 0.00 \n", "[2.73] Object TTL was extended.\n", "[2.73] Waiting for next eviction...\n", "[2.75] 2 Hit: Current Age 0.90 (Average: 0.37) \n", "[3.13] 1 Hit: Current Age 1.84 (Average: 0.65) \n", "[3.26] 2 Hit: Current Age 1.42 (Average: 0.47) \n", "[3.31] 3 Hit: Current Age 0.61 (Average: 0.20) \n", "[3.36] 1 Hit: Current Age 2.06 (Average: 0.83) \n", "[3.40] 1 Hit: Current Age 2.11 (Average: 0.97) \n", "[3.50] 2 Hit: Current Age 1.66 (Average: 0.58) \n", "[3.57] 2 Hit: Current Age 1.72 (Average: 0.67) \n", "[3.59] 2 Hit: Current Age 1.74 (Average: 0.76) \n", "[3.63] Object TTL was extended.\n", "[3.63] Waiting for next eviction...\n", "[4.21] 3 Hit: Current Age 1.51 (Average: 0.53) \n", "[4.31] Object TTL was extended.\n", "[4.31] Waiting for next eviction...\n", "[4.34] 1 Hit: Current Age 3.05 (Average: 1.18) \n", "[4.40] Object TTL was extended.\n", "[4.40] Waiting for next eviction...\n", "[4.58] 2 Hit: Current Age 2.73 (Average: 0.90) \n", "[4.59] Object TTL was extended.\n", "[4.59] Waiting for next eviction...\n", "[4.70] 2 Hit: Current Age 2.86 (Average: 1.03) \n", "[4.73] 2 Hit: Current Age 2.89 (Average: 1.14) \n", "[5.12] 2 Hit: Current Age 3.27 (Average: 1.27) \n", "[5.21] Object 3 needs to be evicted (At time: 5.211951106151475)\n", "[5.21] Cache: Object 3 expired\n", "[5.21] Evicted 1 object(s).\n", "[5.21] Waiting for next eviction...\n", "[5.31] 2 Hit: Current Age 3.47 (Average: 1.39) \n", "[5.34] Object 1 needs to be evicted (At time: 5.33890464876055)\n", "[5.34] Cache: Object 1 expired\n", "[5.34] Evicted 1 object(s).\n", "[5.34] Waiting for next eviction...\n", "[5.36] 2 Hit: Current Age 3.51 (Average: 1.50) \n", "[5.58] 2 Hit: Current Age 3.74 (Average: 1.62) \n", "[5.60] 2 Hit: Current Age 3.75 (Average: 1.72) \n", "[5.99] 1 Miss: Average Age 1.07 \n", "[6.29] 1 Hit: Current Age 0.30 (Average: 1.01) \n", "[6.31] Object TTL was extended.\n", "[6.31] Waiting for next eviction...\n", "[6.40] 2 Hit: Current Age 4.55 (Average: 1.85) \n", "[6.52] 2 Hit: Current Age 4.67 (Average: 1.97) \n", "[6.60] Object TTL was extended.\n", "[6.60] Waiting for next eviction...\n", "[6.76] 2 Hit: Current Age 4.92 (Average: 2.09) \n", "[7.03] 2 Hit: Current Age 5.18 (Average: 2.22) \n", "[7.10] 2 Hit: Current Age 5.25 (Average: 2.33) \n", "[7.29] Object 1 needs to be evicted (At time: 7.290693998414112)\n", "[7.29] Cache: Object 1 expired\n", "[7.29] Evicted 1 object(s).\n", "[7.29] Waiting for next eviction...\n", "[7.38] 1 Miss: Average Age 0.93 \n", "[7.58] 3 Miss: Average Age 0.42 \n", "[8.10] Object 2 needs to be evicted (At time: 8.096464406708787)\n", "[8.10] Cache: Object 2 expired\n", "[8.10] Evicted 1 object(s).\n", "[8.10] Waiting for next eviction...\n", "[8.26] 2 Miss: Average Age 2.25 \n", "[8.38] Object 1 needs to be evicted (At time: 8.376949796979092)\n", "[8.38] Cache: Object 1 expired\n", "[8.38] Evicted 1 object(s).\n", "[8.38] Waiting for next eviction...\n", "[8.58] Object 3 needs to be evicted (At time: 8.582581448612647)\n", "[8.58] Cache: Object 3 expired\n", "[8.58] Evicted 1 object(s).\n", "[8.58] Waiting for next eviction...\n", "[8.87] 1 Miss: Average Age 0.86 \n", "[9.01] 2 Hit: Current Age 0.75 (Average: 2.19) \n", "[9.26] Object TTL was extended.\n", "[9.26] Waiting for next eviction...\n", "[9.78] 1 Hit: Current Age 0.91 (Average: 0.87) \n", "[9.86] 2 Hit: Current Age 1.60 (Average: 2.17) \n", "[9.87] Object TTL was extended.\n", "[9.87] Waiting for next eviction...\n", "[9.87] 1 Hit: Current Age 1.00 (Average: 0.87) \n", "[9.92] 1 Hit: Current Age 1.05 (Average: 0.88) \n", "[9.93] 2 Hit: Current Age 1.67 (Average: 2.16) \n", "[10.10] 2 Hit: Current Age 1.84 (Average: 2.14) \n", "[10.20] 2 Hit: Current Age 1.94 (Average: 2.14) \n", "[10.31] 1 Hit: Current Age 1.44 (Average: 0.92) \n", "[10.39] 3 Miss: Average Age 0.35 \n", "[10.72] 3 Hit: Current Age 0.33 (Average: 0.35) \n", "[10.75] 1 Hit: Current Age 1.88 (Average: 0.97) \n", "[10.78] Object TTL was extended.\n", "[10.78] Waiting for next eviction...\n", "[10.79] 2 Hit: Current Age 2.53 (Average: 2.15) \n", "[10.91] 1 Hit: Current Age 2.04 (Average: 1.02) \n", "[10.98] 1 Hit: Current Age 2.11 (Average: 1.07) \n", "[11.20] Object TTL was extended.\n", "[11.20] Waiting for next eviction...\n", "[11.33] 2 Hit: Current Age 3.07 (Average: 2.18) \n", "[11.50] 3 Hit: Current Age 1.11 (Average: 0.44) \n", "[11.72] Object TTL was extended.\n", "[11.72] Waiting for next eviction...\n", "[11.72] 3 Hit: Current Age 1.33 (Average: 0.54) \n", "[11.73] 3 Hit: Current Age 1.34 (Average: 0.62) \n", "Simulation ended after 11.726962652128046 seconds.\n", "CPU times: user 9.22 ms, sys: 2.17 ms, total: 11.4 ms\n", "Wall time: 9.47 ms\n" ] } ], "source": [ "%%time\n", "\n", "# Start processes\n", "env.process(age_cache_process(env, cache))\n", "env.process(client_request_process(env, cache, stop_event))\n", "\n", "# Run the simulation\n", "env.run(until=stop_event)\n", "simulation_end_time = env.now" ] }, { "cell_type": "code", "execution_count": 10, "id": "3b6f7c1f-ea54-4496-bb9a-370cee2d2751", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Object 1: Hit Rate = 0.76, Expected Hit Rate = 0.63, Average Time spend in Cache: 0.87, Average Age = 1.07, Expected Age = 1.82\n", "Object 2: Hit Rate = 0.91, Expected Hit Rate = 0.95, Average Time spend in Cache: 0.91, Average Age = 2.18, Expected Age = 1.80\n", "Object 3: Hit Rate = 0.60, Expected Hit Rate = 0.63, Average Time spend in Cache: 0.50, Average Age = 0.62, Expected Age = 0.94\n" ] } ], "source": [ "statistics = []\n", "# Calculate and print hit rate and average age for each object\n", "for obj_id in range(1, DATABASE_OBJECTS + 1):\n", " if cache.access_count[obj_id] != 0:\n", " hit_rate = cache.hits[obj_id] / max(1, cache.access_count[obj_id]) # Avoid division by zero\n", " expected_hit_rate = 1-math.exp(-db.lambda_values[obj_id]*CACHE_TTL)\n", " avg_cache_time = cache.cumulative_cache_time[obj_id] / max(1, simulation_end_time) # Only average over hits\n", " avg_age = sum(cache.cumulative_age[obj_id]) / len(cache.cumulative_age[obj_id])\n", " expected_age = hit_rate / (db.lambda_values[obj_id] * (1 - pow(hit_rate,2)))\n", " print(f\"Object {obj_id}: Hit Rate = {hit_rate:.2f}, Expected Hit Rate = {expected_hit_rate:.2f}, Average Time spend in Cache: {avg_cache_time:.2f}, Average Age = {avg_age:.2f}, Expected Age = {expected_age:.2f}\")\n", " statistics.append({\"obj_id\": obj_id,\"hit_rate\": hit_rate, \"expected_hit_rate\": expected_hit_rate, \"avg_cache_time\":avg_cache_time, \"avg_age\": avg_age, \"expected_age\": expected_age})" ] }, { "cell_type": "code", "execution_count": 11, "id": "b2d18372-cdba-4151-ae32-5bf45466bf94", "metadata": {}, "outputs": [], "source": [ "stats = pd.DataFrame(statistics)\n", "stats.to_csv(f\"{TEMP_BASE_DIR}/hit_age.csv\",index=False)\n", "stats.drop(\"obj_id\", axis=1).describe().to_csv(f\"{TEMP_BASE_DIR}/overall_hit_age.csv\")" ] }, { "cell_type": "code", "execution_count": 12, "id": "80971714-44f1-47db-9e89-85be7c885bde", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
| \n", " | access_count | \n", "hits | \n", "misses | \n", "mu | \n", "lambda | \n", "hit_rate | \n", "expected_hit_rate | \n", "expected_hit_rate_delta | \n", "avg_cache_time | \n", "cache_time_delta | \n", "avg_age | \n", "expected_age | \n", "age_delta | \n", "age_delta in % | \n", "
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | \n", "21 | \n", "16 | \n", "5 | \n", "0 | \n", "1 | \n", "0.761905 | \n", "0.632121 | \n", "0.129784 | \n", "0.870220 | \n", "-0.108316 | \n", "1.072219 | \n", "1.816216 | \n", "-0.743997 | \n", "-0.409641 | \n", "
| 2 | \n", "34 | \n", "31 | \n", "3 | \n", "0 | \n", "3 | \n", "0.911765 | \n", "0.950213 | \n", "-0.038448 | \n", "0.914511 | \n", "-0.002746 | \n", "2.177619 | \n", "1.801709 | \n", "0.375910 | \n", "0.208641 | \n", "
| 3 | \n", "10 | \n", "6 | \n", "4 | \n", "0 | \n", "1 | \n", "0.600000 | \n", "0.632121 | \n", "-0.032121 | \n", "0.498486 | \n", "0.101514 | \n", "0.623042 | \n", "0.937500 | \n", "-0.314458 | \n", "-0.335422 | \n", "