How Long Should Botie Trust the Saved Reading?
A cache can answer quickly while showing an old reading. Follow its expiry boundary and the load a cold cache sends back to the source.
14 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
A Helpful Copy Can Become a Dependency
BOTIE
Redis makes expiry an explicit property of a key. A timeout gives a copy a limited lifetime; the application still chooses a lifetime appropriate to its data. Our workshop isolates the decision at that boundary. It cannot establish whether a source can absorb refresh traffic.
Sources for this history
The Screen Still Shows Yesterday’s Count
The lookup returns the right pump but still shows eighteen open inspections after the source has changed to twenty-three. We saved the answer to avoid asking the source on every refresh. That saved answer has outlived its usefulness.
A cache is a copy kept for reuse. We need a policy for when it may answer, what happens when it cannot, and how the service behaves if many copies expire together.
Times are invented seconds on one shared clock. Source values are fixtures, not network responses.
Step 1 of 2
The source changes before the deadline
This policy permits the copy until time 120.
| Time | Cached count | Source count | Chosen branch |
|---|---|---|---|
| 100 | 18 | 18 | cache |
| 110 | 18 | 23 | cache |
| 120 | 18 | 23 | source |
100
- Cached count
- 18
- Source count
- 18
- Chosen branch
- cache
110
- Cached count
- 18
- Source count
- 23
- Chosen branch
- cache
120
- Cached count
- 18
- Source count
- 23
- Chosen branch
- source
Step 2 of 2
A miss needs another route
Use existence and time before choosing the saved answer.
- 1
Lookup
Does an entry exist? - 2
Deadline
Is now earlier than expiry? - 3
Usable entry
Return its saved value - 4
Otherwise
Fetch the source
A correctly named record can still be stale
The deadline decides eligibility; the saved value cannot tell us whether it is old.
Cross the expiry time
Read the saved value and deadline on separate branches. Compare the clock with the deadline before choosing a copy.
BOTIE
Value returned under the expiry policy
18
Follow the operations
1. Current time in seconds
119
2. Cached count and deadline
{ "value": 18, "expires_at": 120 }3. Read a document path
120
4. Is the clock before expiry?
true
5. Read a document path
18
6. Current source count
23
7. Choose using a Boolean
18
8. Result
18
The browser runs an explicit model of the mechanism. It does not connect to MongoDB or Redis, simulate their query planners, or establish their concurrency guarantees.
1. Separate the source from the copy
The source owns the inspection count. The cache stores a previous answer. A hit saves a request; it does not prove the source still agrees.
An approximate activity display might tolerate delay. A current safety state may need a different policy. Decide the acceptable stale interval with the people who depend on the answer. A timeout selected for convenience quietly makes that decision for them.
A cache lifetime expresses tolerance for old information.
2. Follow a cache-aside read
In cache-aside, the application asks the cache first. A usable hit returns the copy. A miss asks the source and may store that answer for reuse. A failed source call is a separate outcome.
The application can invalidate a copy when it updates the source, but writes and invalidations can race or fail independently. Expiry limits lingering copies; it does not make separate stores update atomically.
Describe the outage behaviour rather than quietly treating an unavailable count as zero.
3. Make the deadline comparison explicit
The fixture has value and expires_at. Both now and expires_at use the same time unit. Our rule uses the saved value only while now is strictly earlier than expiry. At equality, the entry has expired.
Real caches own their clocks and expiry mechanics. Here a shared clock is an explicit assumption, allowing repeatable tests without sleeping or relying on a workstation clock.
Test the boundary itself, not only times far from it.
Pause on either side of expiry
The contents stay unchanged while their eligibility changes.
entry = {"value":18, "expires_at":120}
for now in [119,120,121]:
print(now, now < entry["expires_at"])119 True
120 False
121 FalseThe exact deadline takes the source branch under this policy.
Check yourself
Questions on Parts 1 to 3
Question 1 of 2
Which value is returned?
Use the information below.
Entry value 18 expires at 120. Now is 120 and the source value is 23. The cache is eligible only while now < expiry.4. Store the value and lifetime together
Redis SET accepts an EX lifetime in seconds. Setting the value and expiry in one command avoids a gap where SET succeeds and a separate EXPIRE fails. Ordinary SET without an applicable expiry option clears an existing timeout.
Eviction is different: a cache may remove an entry under its memory policy before the nominal deadline. The application must tolerate that miss too.
A TTL is not a promise that memory will retain the item until that time.
Run in your own environment
Store a count with a lifetime
SET supplies the value and expiry together. TTL reports remaining lifetime or a special absence/no-expiry result.
Use redis-cli with a disposable local Redis server. Time passes between commands; this is not a browser exercise.
redis-cli SET 'asset:north:P1:open-count' '18' EX 60
redis-cli TTL 'asset:north:P1:open-count'The Python challenge replaces elapsed real time with an explicit clock.
Store a count with a lifetime. Documentation checked .
5. Prepare for many misses together
A cold cache can send every request to the source. Popular keys expiring together can also create a burst. Coalescing simultaneous fetches for one key reduces duplicated work; spreading expiry times can reduce synchronised refreshes.
Bound the source request rate and decide what happens when that budget is exhausted. A controlled stale-response policy can suit some displays, but it needs a final deadline and an honest description of the old information being served.
Measure source load during misses, not only response speed during hits.
6. Recognise an unexpired but outdated answer
At time 110, our copy is eighteen and the source is twenty-three. The deadline still permits the old answer until 120. That is the allowance we chose.
If the user needs the newest count immediately after a write, a simple TTL is insufficient. Reading the source for that operation or using a version-aware update process changes the design and its costs. A passing deadline test cannot decide whether the allowance is appropriate.
Correct code can faithfully implement the wrong freshness policy.
7. Observe the branch without exposing the payload
Track hits, misses and refresh failures separately. Avoid logging private cached values as a substitute for useful metrics. Include tenant and representation version in naming where required, and enforce access at the service boundary.
The challenge returns a route label with its value. That makes a falsey hit and an absent entry distinguishable even when their eventual displayed counts look similar.
The workshop evaluates both supplied branches for inspection. It does not issue a source request on every real cache hit.
Check yourself
Questions on Parts 1 to 7
Question 1 of 2
Which mechanism reduces duplicated source fetches for that key?
Use the information below.
Many requests miss the same popular key at once.Try it with Botie
Build with Botie
Write and run Python