Module 2: Napkin Estimation (Back-of-the-Envelope)
6. Memory: the working set and why it fits in RAM
Description
The fourth and last capacity number is the memory: how much RAM the hot part of Enlace's data asks for so it's worth keeping in a cache. It's a different number from the previous three, and it's worth saying up front so as not to get confused: we are not asking "how much memory to store all the data?" —that would be the 6 TB from lesson 4, and putting all of it in RAM would be absurdly expensive and unnecessary—. We're asking "how much memory to store the fraction of data that's actually used at any given moment?". That fraction has a name —the working set— and for Enlace it weighs about ~333 MB: hundreds of megabytes that fit easily in the RAM of a single server, even though the total disk is 6 TB.
In this lesson you learn what the working set is, apply the 80/20 rule (the 20% of URLs receives 80% of the traffic) to Enlace's scale, and run the calculation that produces the ~333 MB. And —this is the important part— you learn to read that number: since the working set (hundreds of MB) is tiny against the total disk (6 TB), keeping the hot part in memory is cheap and removes almost all reads from disk. That contrast —333 MB against 6 TB, a factor of 18,000— is the arithmetic justification for why Enlace carries a cache, and it's the number that closes the capacity table.
Connection to the module: this lesson builds the fourth and last row of the capacity table. It reuses a number you already computed in lesson 3 —the new URLs per day (~3.3 million), which are the proxy for the set of distinct URLs in play— and the URL size from lesson 4 (~500 bytes). With it you'll have the four numbers complete and ready for the table in lesson 8. Here there's an important boundary that's respected to the letter: in this lesson we compute only how much memory the working set asks for, to know whether it fits in RAM. The cache design —cache-aside, the LRU eviction policy, the average-latency calculation L = h·L_cache + (1-h)·L_db, the TTL and invalidation— is module 4. Here we produce the number that justifies that cache; module 4 designs it.
The library and the work table
Imagine a university library with two million books on its shelves. Those two million are the total storage: everything the library has, kept on shelves that take up whole floors. It's the equivalent of Enlace's 6 TB on disk: a lot, permanent, and most of it untouched at any given moment.
Now look at your work table inside that library. On it you don't have two million books: you have the five or six you're using right now for your essay. That table is your working set: the small subset of the data you work with actively in a time window. The table is very fast —you reach out and there's the book, without walking to the shelf—, but it's small: two million books don't fit, nor do you need them. That table is the equivalent of RAM: fast, expensive, limited, and that's why you only put on it what you're actually using.
And here's the observation that makes the whole cache possible, and that the library knows well: a few books are requested much more than the rest. The textbook for the most popular course is checked out a hundred times a day; an obscure 1974 thesis isn't checked out in years. If the library put a special shelf by the door with only the 20% most-requested books, that small shelf would satisfy 80% or 90% of all checkouts, and almost no one would have to walk to the back. That's exactly a cache: a small, fast shelf (RAM) with the most-requested part of the data, catching most of the requests without going to the big, slow warehouse (disk).
The question of this lesson is the size of that special shelf: how many books —how many URLs— do you have to put on the fast table to catch most of the traffic, and how much do they weigh? For Enlace, the answer is ~333 MB, and the good news is that "table" fits comfortably in the RAM of a single machine.
What exactly the working set is
The working set is the subset of the data a system uses actively in a given time window. It's not everything the system has stored (that's the total on disk), nor a copy of everything in memory: it's only the "hot" part, the part being requested now.
The key distinction, and the one that's hardest at first, is between total data and active data:
- Total data: everything that exists on disk. For Enlace, the 6 billion records accumulated over 5 years (~6 TB). The vast majority of those URLs aren't touched on any given day: they're old links no one visits anymore.
- Active data (working set): the URLs that are actually being resolved in the window you look at (an hour, a day). They're far fewer, because —just as in the library— most of the traffic concentrates on a handful of recent and popular links.
Caching consists of keeping the working set (small) in RAM to serve most of the reads without hitting the disk. That's why this lesson's memory question is not "how much RAM for 6 TB?" (answer: too much, and it's not needed), but "how much RAM for the working set?" (answer: hundreds of MB, perfectly affordable). Confusing the two is the error that ruins this estimate, and we cover it at the end.
A temporal nuance that helps: the working set changes over time. Today's hot URLs aren't the ones from a year ago —a viral link burns for a week and then cools down—. That's why the working set is estimated over a recent window (a day is a reasonable napkin choice) and not over the complete history. The cache, in practice, keeps replacing the cold with the hot on its own, with its eviction policy; but how it does it is module 4. Here we only size that hot set.
The 80/20 rule applied to the scale
The tool that turns "the hot data" into a number is the 80/20 rule, also called the Pareto principle: in many systems, a minority of the elements concentrates the majority of the events. Said for URLs: the ~20% of URLs receives the ~80% of the visits. A few popular links (the article that went viral, the link pinned on a profile with millions of followers) accumulate most of the traffic, while the long tail of forgotten links is hardly touched.
The numbers "20" and "80" aren't sacred —it could be 10/90 or 30/70 depending on the system—, but the shape of the distribution is real and very common: the traffic is skewed, not evenly spread. And that skew is exactly what makes caching work. If all URLs were visited equally, a small cache would almost never hit (the next request would be as likely for a cold URL as for a hot one). Since the traffic is skewed, a cache that keeps only the hot 20% catches 80–90% of the reads: it's a small shelf that resolves most of the work.
To estimate the memory, we apply the rule like this: the working set to cache is the hot 20% of the distinct URLs in play. We're missing one number: how many distinct URLs are "in play" in the window we choose? Here we reuse a result from lesson 3.
In lesson 3 you computed that Enlace creates ~3.3 million new URLs per day (100M/month ÷ 30 days). That number is a reasonable proxy for the set of distinct URLs active in a day: since links are shared and consumed mostly shortly after being created (someone shortens a link and people click it that same week), the recent URLs are the ones that dominate the reads, and the new ones of the day are a good measure of that changing set. It's not exact —there are old links that are still hot and new ones no one opens—, but for napkin math it's a clean and defensible proxy. Watch a distinction lesson 3 already made: we use the new URLs per day (which count distinct things), not the reads per day (~345M, which count visits). A viral URL is read thousands of times but takes up a single entry in the cache; what sizes the memory is how many distinct URLs have to be stored, not how many times they're read.
Worked example: Enlace's ~333 MB
With the 80/20 rule and the proxy of distinct URLs per day, the calculation has three steps: how many distinct URLs are in play (per day), what fraction to cache (the hot 20%), and how much each cache entry weighs.
The third step calls for a decision: how much does a cache entry weigh? Enlace's cache serves the read path: given a short_code, it returns the long_url to redirect. So an entry is the short_code → long_url pair. The short_code (the key) weighs ~7 bytes; the long_url (the value) weighs ~500 bytes. The value dominates completely, so we estimate each entry at ~500 bytes (the URL). We don't put the complete 1 KB record because the read cache doesn't need created_at, expires_at, or clicks: just the code → URL mapping that resolves the redirect.
Let's run the calculation:
# Enlace's working set: the hot 20% of a day's distinct URLs.
writes_per_day = 100_000_000 / 30 # new URLs/day (lesson 3), proxy of the distinct set
hot_fraction = 0.20 # 80/20 rule: we cache the hot 20%
entry_bytes = 500 # each entry ~ short_code(7B) -> long_url(~500B)
hot_urls = writes_per_day * hot_fraction
working_set = hot_urls * entry_bytes
print(f"new URLs/day (distinct set) = {writes_per_day:,.0f}")
print(f"hot 20% to cache = {hot_urls:,.0f} URLs")
print(f"working set = {hot_urls:,.0f} x {entry_bytes} B = {working_set:,.0f} B")
print(f"working set = {working_set/1e6:.0f} MB")
What to expect.
new URLs/day (distinct set) = 3,333,333
hot 20% to cache = 666,667 URLs
working set = 666,667 x 500 B = 333,333,333 B
working set = 333 MB
~333 MB. That's Enlace's fourth number: the hot part of the data, the set worth keeping in RAM, fits in about 333 megabytes. Notice the exponent arithmetic, which you already master from lesson 2: 3.3 × 10⁶ URLs × 0.2 × 5 × 10² B = 3.3 × 10⁶ × 10² B = 3.3 × 10⁸ B ≈ 333 MB. You counted zeros: millions (10⁶) times hundreds of bytes (10²) gives 10⁸, which is hundreds of MB.
And here, as with every number in the module, comes the design conclusion, which in this case is resounding: 333 MB fit easily in the RAM of any modern server. A typical cache machine has 16, 32, or 64 GB of RAM; 333 MB is a tiny fraction of that. Enlace's cache isn't just possible: it's comfortable, cheap, and has plenty of room to grow. Without this calculation, "let's add a cache" would be an act of faith; with it, it's a decision backed by a number you can defend.
Working set vs. total: why caching is cheap
The real value of the ~333 MB isn't in the number alone, but in the contrast with the total storage. Let's put the two memory and disk numbers side by side:
working_set = 333_333_333 # ~333 MB, the hot part in RAM
total_disk = 6e12 # 6 TB, all the data on disk (lesson 4)
print(f"working set (RAM): {working_set/1e6:>7.0f} MB")
print(f"total (disk): {total_disk/1e12:>7.0f} TB")
print(f"ratio total/hot = {total_disk/working_set:,.0f}x")
print(f"the working set is {working_set/total_disk*100:.4f}% of the total")
What to expect.
working set (RAM): 333 MB
total (disk): 6 TB
ratio total/hot = 18,000x
the working set is 0.0056% of the total
Read it slowly, because it's the heart of why Enlace's cache works: the working set is 18,000 times smaller than the total. By keeping in RAM barely the 0.0056% of the data —five thousandths of one percent— you catch the vast majority of the reads. This is the magic of skewed traffic (the 80/20 rule): you don't need to put the 6 TB in memory (impossible and very expensive); you put in 333 MB (trivial and cheap) and with that you resolve 80–90% of the requests without touching the disk.
This contrast is the economic reason the cache exists. RAM is expensive per byte (much more than disk), so putting 6 TB in RAM would be an absurd expense. But putting 333 MB in RAM costs practically nothing, and the return is enormous: most of the read traffic —Enlace's main challenge— is served from memory. Caching is worth it when the working set is a small fraction of the total and the traffic is skewed toward that fraction; Enlace meets both conditions with margin to spare, and that's why module 4 exists. What the estimate contributes is the numerical proof: not "we think the cache helps", but "the hot part is 18,000 times smaller than the total and fits in 333 MB of RAM".
Let's also see it as a fraction of a server's RAM, to calibrate how much room there is to spare:
working_set = 333e6
for ram_gb in (16, 32, 64):
pct = working_set / (ram_gb * 1e9) * 100
print(f"333 MB on a {ram_gb:>2} GB RAM server = {pct:.2f}% of the memory")
What to expect.
333 MB on a 16 GB RAM server = 2.08% of the memory
333 MB on a 32 GB RAM server = 1.04% of the memory
333 MB on a 64 GB RAM server = 0.52% of the memory
Enlace's working set uses between 0.5% and 2% of a common server's RAM. There's so much room to spare that you could cache several days of URLs, or the complete record instead of just the URL, and it would still be comfortable —we check it in the sensitivity section—. The conclusion: the memory, like the bandwidth, isn't a bottleneck for Enlace; on the contrary, it's the resource that makes it easy to resolve the one that is (the read-heavy load).
The entry size: why 500 bytes and not 1 KB
It's worth pausing on the "~500 bytes per entry" decision, because it's where people most go wrong when estimating memory, and because it shows that a cache entry's size depends on what you store, not on how much the record weighs on disk.
On disk, a Link record weighs ~1 KB (lesson 4): the URL, plus created_at, expires_at, clicks, and the database overhead. But the read cache doesn't need all that. Its only job is to answer "given this short_code, which long_url do I redirect to?". For that the short_code → long_url pair is enough. The metadata (when it was created, how many clicks it has) isn't needed to redirect, so it isn't cached. That's why the entry weighs ~500 bytes (the URL) and not 1 KB (the complete record):
short_code = 7 # the key: 7 chars base62
long_url = 500 # the value: the average long URL
entry = short_code + long_url
print(f"cache entry = {short_code} B (key) + {long_url} B (value) = {entry} B")
print(f"rounded ~ {round(entry, -2)} B (the value dominates; the key is negligible)")
What to expect.
cache entry = 7 B (key) + 500 B (value) = 507 B
rounded ~ 500 B (the value dominates; the key is negligible)
The 7 bytes of the key are negligible beside the 500 of the value, so rounding the entry to ~500 B is clean. And even if you went overboard being conservative and cached the complete 1 KB record, the working set would only rise to ~666 MB —double—, which still fits comfortably in any server. That's another way to see the robustness of the conclusion: the cache design can choose to store more or less per entry, and in no reasonable case does the memory stop being plentiful. The lesson: when estimating cache memory, estimate the size of what you'll actually store in it, not the record on disk, because they're usually different (the cache stores less, just what's needed for its job).
The hit ratio: the number that justifies caching (and what belongs to module 4)
The 80/20 rule doesn't just say how much memory; it also anticipates how well the cache will work. If you keep the hot 20% and that 20% receives 80–90% of the reads, then ~80–90% of the requests will find their URL in the cache (a hit) and only 10–20% will have to go to disk (a miss). That percentage of hits is called the hit ratio, and it's the number that translates "the working set fits in RAM" into "the cache really offloads the disk".
For Enlace, with a 333 MB working set that captures the hot part, a hit ratio of ~90% is a reasonable napkin expectation. That means that of the 4,000 reads/s, only ~400/s would reach the disk:
qps_read = 4000
hit_ratio = 0.90
reads_to_disk = qps_read * (1 - hit_ratio)
print(f"total reads/s = {qps_read:,}")
print(f"served by cache (90%) = {qps_read * hit_ratio:,.0f}/s")
print(f"reach the disk (10%) = {reads_to_disk:,.0f}/s")
What to expect.
total reads/s = 4,000
served by cache (90%) = 3,600/s
reach the disk (10%) = 400/s
With a cache, the database goes from handling 4,000 reads/s to only ~400/s: a tenth of the load. That's the effect the 333 MB of RAM buy, and it's the numerical reason Enlace carries a cache.
Here's the boundary with module 4, and it's respected: in this lesson we only size the memory (333 MB) and anticipate the hit ratio as the number that justifies caching. How that cache is built —the cache-aside pattern (look at the cache first, go to disk on a miss, and store the result), the eviction policy that decides what to remove when it fills up (LRU, least recently used), the average-latency calculation L = h·L_cache + (1-h)·L_db that translates the hit ratio into milliseconds, the TTL and invalidation when a URL changes— is all module 4. We don't design it here. Our deliverable is the number: ~333 MB working set, fits in RAM, expected hit ratio ~90%. With that, module 4 has what it needs to design the cache with grounds.
Sensitivity: what if I'm wrong about an assumption?
As we did with the storage in lesson 4, it's worth asking: how fragile is the conclusion "fits in RAM"? The estimate rests on two loose assumptions —the hot fraction (20%) and the entry size (500 B)—, so let's test the reasonable extremes and see whether the conclusion holds:
writes_per_day = 100_000_000 / 30
print(f"{'fraction':>10} {'entry':>9} {'working set':>14}")
for frac in (0.20, 0.50, 1.00): # hot 20% ... up to caching ALL of the day
for entry in (500, 1000): # just the URL ... complete record
ws = writes_per_day * frac * entry
unit = f"{ws/1e6:.0f} MB" if ws < 1e9 else f"{ws/1e9:.2f} GB"
print(f"{frac*100:>9.0f}% {entry:>7} B {unit:>14}")
What to expect.
fraction entry working set
20% 500 B 333 MB
20% 1000 B 667 MB
50% 500 B 833 MB
50% 1000 B 1.67 GB
100% 500 B 1.67 GB
100% 1000 B 3.33 GB
Look at the heaviest corner of the table: even if you cached 100% of a day's distinct URLs (not just the hot 20%) with the complete 1 KB record (not just the URL), the working set would be 3.33 GB —and that still fits in a 16 GB server, using ~20% of its RAM—. The estimate is robust: being wrong about your two loosest assumptions, in the most expensive direction, by factors of 5 and 2 at once, the design conclusion ("fits in a machine's RAM, caching is cheap") doesn't move. That robustness is what lets you report "~333 MB, fits in RAM" with confidence: it doesn't depend on having gotten the exact 20% or the exact 500 B; it holds for any reasonable value of those assumptions. A good estimator does this test —"and if my loose assumptions were off by a factor of several?"— and confirms the answer doesn't flip. If it flipped, you'd have to measure better before deciding; here it doesn't flip, not even close.
A bit of intuition: the working set of other systems
As with the previous numbers, Enlace's 333 MB make sense when compared. What governs the working set's size is the combination of how many distinct elements are hot and how much each one weighs. These orders of magnitude calibrate the ear:
| System | Working set (order of magnitude) | Why |
|---|---|---|
| Enlace (URLs) | ~hundreds of MB | few distinct hot URLs, ~500 B each |
| A website's sessions | ~a few GB | millions of active sessions, ~1 KB each |
| Social network feed (timelines) | ~tens–hundreds of GB | large pre-computed timelines, many active users |
| A photo service's cache | ~many TB | few distinct photos but enormous (MB each) |
Note the pattern: when the hot part is small text (Enlace: 500 B URLs), the working set fits in a machine's RAM, even though the total on disk is large. When the hot part is multimedia (hot photos of MB each), the working set can be terabytes and no longer fits on a single server —a distributed cache is needed, spread across many machines—. The same question ("how much RAM for the hot part?") gives answers of completely different scales according to the element size, just like bandwidth and storage. For Enlace, being short text, the memory is a luxury: the hot part fits with plenty of margin on a single machine, and that makes the cache one of the easiest and most profitable decisions of the whole design.
Common mistakes
Confusing the working set with the total data (sizing the RAM for the disk). What happens: someone reads "6 TB of data" and concludes "I need 6 TB of RAM to cache it", a very expensive and unnecessary nonsense. Why it happens: they skip the distinction between total data (everything that exists) and active data (what's used now), and apply the big number where the small one goes. How to detect it: if your memory estimate is on the same order as the storage one, you cached everything instead of just the hot part. How to fix it: the cache stores the working set (the hot fraction), not the total. Apply the 80/20 rule: cache the ~20% hot, not the 100%. For Enlace that goes from 6 TB (impossible in RAM) to ~333 MB (trivial). The total goes to disk; only the hot part goes to RAM.
Caching the complete record when the cache only needs part (oversizing the entry). What happens: the person estimates each cache entry at 1 KB (the complete record on disk) when the read cache only needs the short_code → long_url pair (~500 B). Why it happens: the record size from lesson 4 is taken without thinking about which of that record is needed in the cache. How to detect it: if your cache entry includes fields the cached operation doesn't use (the clicks, the dates), you're storing too much. How to fix it: the cache entry weighs what what you actually store in it weighs, not the whole record. For Enlace's read path, that's the URL (~500 B). Storing too much doesn't break the conclusion (Enlace has plenty of RAM), but it's the kind of imprecision that, in a system at the memory limit, would matter.
Counting visits instead of distinct things (confusing events with elements). What happens: someone sizes the working set with the reads per day (~345 million) instead of the distinct URLs per day (~3.3 million), inflating the memory by a factor of 100. Why it happens: "reads per day" is a number you already have at hand, and it sneaks in where "distinct URLs" goes. How to detect it: if your working set came from a number of accesses and not of unique elements, you overestimated: a viral URL is read a thousand times but takes up one cache entry. How to fix it: the memory is sized by how many distinct things have to be stored, not how many times they're accessed. Use the count of unique elements (distinct URLs), not of events (reads). This is the same "events vs. distinct things" distinction lesson 3 already pointed out, and it's the key to all memory estimation.
Exercises
Exercise 1 — The working set of a new system. A news service has 10 million articles in total on disk, but publishes 5,000 new articles a day, and the 20% of the recent articles concentrates 80% of the reads. Each cached article (title + summary + trimmed body) weighs ~10 KB. (a) How many hot articles to cache (the 20% of the day's recent ones)? (b) How much working set in MB? (c) Does it fit on a 16 GB RAM server?
See solution
- (a) The 20% of the day's 5,000 recent ones:
5,000 × 0.20 = 1,000 hot articles. - (b)
1,000 × 10 KB = 1,000 × 10,000 B = 10,000,000 B = 10 MB. A tiny working set: ~10 MB. - (c) Yes, with plenty to spare:
10 MB / 16 GB = 0.06%of the RAM. It fits with lots of margin.
Note that the 10 million total articles are a distractor for this calculation: the working set is sized by the active set (the recent hot ones), not the total on disk —just as in Enlace the 6 TB total don't enter the 333 MB calculation—. The method is identical: distinct active URLs (or articles) × hot fraction × entry size. Here the element weighs more (10 KB vs 500 B) but there are far fewer hot ones (1,000 vs 666,667), and the result is still trivial for the RAM.
Exercise 2 — Why is caching worth it? In one sentence each, explain: (a) why storing Enlace's complete 6 TB in RAM would be absurd; (b) why storing only 333 MB in RAM is not; and (c) what property of the traffic makes 333 MB (the 0.0056% of the data) catch most of the reads.
See solution
- (a) RAM is expensive per byte (much more than disk) and volatile; putting 6 TB in RAM would cost a fortune to store mostly cold data almost no one requests —you'd pay premium memory for links that aren't visited—.
- (b) 333 MB is a tiny fraction of any server's RAM (0.5–2% of 16–64 GB), so it costs practically nothing, and in exchange it serves 80–90% of the reads without touching the disk: enormous return for a minimal cost.
- (c) The traffic skew (the 80/20 rule): the visits aren't spread evenly among the URLs, but concentrate on a hot ~20%. That skew is what makes caching a small fraction catch a large majority; without skew (uniform traffic), a small cache would almost never hit.
The underlying idea: caching is profitable when the working set is a small fraction of the total and the traffic is skewed toward that fraction. Enlace meets both with margin, and that's why the cache is one of its clearest design decisions.
Exercise 3 — The correct proxy. To estimate the working set, we used the new URLs per day (~3.3 million) and not the reads per day (~345 million), even though both numbers were available. (a) Explain why the reads per day is the wrong number to size memory. (b) In what case would the new URLs per day fall short as a proxy for the hot set, and how would you adjust it?
See solution
- (a) The reads per day count visits (events), not distinct URLs (elements). The memory is sized by how many distinct things have to be stored, not how many times they're accessed: a viral URL read 10,000 times a day takes up a single entry in the cache, not 10,000. Using the reads (345M) instead of the distinct URLs (3.3M) would inflate the working set by a factor of ~100, sizing RAM for events that share the same entry.
- (b) The day's new URLs would fall short if a significant part of the traffic went to old links that are still hot (a link from a year ago that goes viral again). The proxy assumes the hot part is mostly the recent part; if it weren't, the real active set would include URLs from previous days. You'd adjust it by widening the window —for example, taking the distinct URLs of the last 7 days as the active set— and applying the 20% over that larger set. For Enlace, even multiplying the working set by 7 (a week) would give ~2.3 GB, which still fits in RAM: another sample of how robust the conclusion is.
The lesson: choosing the correct proxy (distinct active elements, not events) is half of estimating memory well; the other half is stating the time window that defines "active" and checking that the conclusion holds if that window grows.
Summary and next step
In this lesson you produced the fourth and last number of the capacity table: the working-set memory. You learned to distinguish the total data (6 TB on disk) from the active data (the working set, the hot part used now), and why the cache stores the latter, not the former. You applied the 80/20 rule —the ~20% of URLs receives the ~80% of the visits— using the distinct URLs per day (~3.3M, the proxy from lesson 3) as the set in play, and ran the calculation: 3.3M × 0.20 × 500 B ≈ 333 MB. You saw that this working set is 18,000 times smaller than the total on disk (the 0.0056%), and that it fits using only 0.5–2% of a server's RAM: the numerical proof of why caching is cheap and why module 4 exists. And you checked that the conclusion "fits in RAM" is robust: it holds even caching 100% of the day's URLs with the complete record (3.33 GB, still fits).
Before moving on you should be able to: distinguish total data from working set; apply the 80/20 rule to size the hot part; choose the correct proxy (distinct active elements, not events) and the correct entry size (what the cache stores, not the complete record); and use the working-set-vs-total contrast to argue whether caching is worth it.
With this you have Enlace's four numbers complete —QPS, storage, bandwidth, memory— each computed and with its design conclusion. But a computed number isn't yet a defensible number: it's missing the discipline that makes it reliable. Lesson 7 teaches that discipline: smart rounding (one significant figure, powers of 10) and sanity checks —crossing two paths to the same number, comparing it against known references, and detecting when a number "can't be"—. It's what separates an estimate you can trust from a list of unverified calculations, and what you need before assembling the complete table in lesson 8.
Resources
- Martin Kleppmann, Designing Data-Intensive Applications, Chapter 1 ("Describing Load", the Twitter fan-out example and skewed loads) — dataintensive.net. It shows how the skewed distribution of traffic (a few users or elements concentrate the load) decides what's pre-computed and cached, which is exactly the 80/20 rule we apply here. In English.
- Redis documentation, "Key eviction" and "Using Redis as an LRU cache" — redis.io/docs/latest/develop/reference/eviction/. The most widely used in-memory cache system; its documentation explains how a cache's RAM is sized and how it decides what to keep hot. Context for the working-set number (the cache design itself is module 4). Free, in English.
- The System Design Primer, "Cache" and "When to update the cache" sections — github.com/donnemartin/system-design-primer#cache. It presents the working-set idea and the 80/20 skew as the cache's motivation, with the same "store the hot, not everything" reasoning. Free, in English.