🎓 A1Learner
🔍
0 of 47 lessons completed

Cache-Aside and Read-Through

Read strategies decide who fills the cache when a request misses. Cache-aside lets the app fill the cache, and read-through lets the cache fill itself.

Cache-aside, step by step

The app does the filling here. A repeated key shows the source switch from database to cache.

db = {"page-1": "Home", "page-2": "Help"}
cache = {}

def cache_aside(key):
    if key in cache:
        print(key, "from cache")
        return cache[key]
    print(key, "from database")
    cache[key] = db[key]
    return db[key]

for key in ["page-1", "page-1", "page-2", "page-1"]:
    cache_aside(key)

Read-through fills itself

The app never touches the database here. On a miss, the cache loads the value on its own.

db = {"page-1": "Home", "page-2": "Help"}
cache = {}

def read_through(key):
    if key in cache:
        print(key, "from cache")
        return cache[key]
    print(key, "cache filled itself from database")
    cache[key] = db[key]
    return db[key]

for key in ["page-1", "page-1", "page-2", "page-1"]:
    read_through(key)

Same requests, same reads

Both strategies face the same 5 requests. The database read counts match, because only the filler differs.

db = {"a": 1, "b": 2, "c": 3}
requests = ["a", "b", "a", "c", "a"]

def run(fill):
    cache = {}
    reads = 0
    for key in requests:
        if key not in cache:
            reads += 1
            cache[key] = fill(key)
    return reads

print("cache-aside database reads:", run(lambda k: db[k]))
print("read-through database reads:", run(lambda k: db[k]))

Example

db = {"song-1": "Blue", "song-2": "Red", "song-3": "Gold"}
requests = ["song-1", "song-2", "song-1", "song-3", "song-2", "song-1"]

cache_a = {}
for key in requests:
    if key in cache_a:
        print("cache-aside", key, "from cache")
    else:
        print("cache-aside", key, "from database")
        cache_a[key] = db[key]

cache_b = {}
for key in requests:
    if key in cache_b:
        print("read-through", key, "from cache")
    else:
        print("read-through", key, "cache filled itself")
        cache_b[key] = db[key]

Key points

  • Cache-aside: the app checks the cache, reads the database on a miss, then stores the answer in the cache.
  • Read-through: the app asks the cache only, and the cache reads the database itself on a miss.
  • Use cache-aside to control exactly which answers the app stores.
  • Use read-through to keep the database reading code in one place, inside the cache.

In this example, both strategies serve the same 6 requests with the same pattern of sources. Each one reads the database 3 times and the cache 3 times, so the real choice is who writes the filling code.

Note

both strategies end the same way, with the next request for that key scoring a hit.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

In which strategy does the app itself read the database on a miss and then store the answer in the cache?