A database with no queries: key-value stores and Redis

After this lesson you will explain why a database that answers only "what is under this key?" exists, say what Redis adds to that idea and what it is not, explain why one thread makes every command atomic and still fast, and choose the right one of the seven data types for a need.

"Every 1/10 second delay resulted in 1% loss of sales." That is what the slide on p. 343 prints next to Amazon's name; on the same slide Google loses 20 % of its traffic for half a second of delay. Speed is money.

In 2009 Salvatore Sanfilippo was trying to make LLOOGG, the real-time web-log analyser of his Italian startup, scale, and traditional database systems could not keep up with some of its workloads. He wrote his own database and gave up almost everything a relational one offers: no SQL, no joins, no secondary indexes, everything kept in memory, and a single thread that runs one command at a time. He called it Redis.

Two of those choices sound backwards. How can a database that answers only one question — "what is stored under this key?" — be the fix when a relational database is too slow? And how can a server that uses one CPU core, one command at a time, need no locks at all and still keep up? By the end of the lesson you will answer both in the slides' own words.

One key, one value, one step

The lecture opens on the map of p. 342, "Overview – NoSQL Family": "Data stored in 4 types: Document, Graph, Key-value, Wide-column", with Redis, Amazon DynamoDB, Aerospike and Riak in the key-value cell (the four families are in lesson 5). Key-value is the plainest of the four, and p. 343, "Why is performance important?", says why anyone wants it: speed. Besides Amazon's 1 % of sales and Google's "20% drop in traffic", it reports that for a "1-second delay in page-load time" an "Industrial Group" counted "11% fewer page views", "16% decrease in customer satisfaction" and "7% loss in conversions".

Take the slide's own example, the blog database of p. 346 (Fig. 1). To show the page of blog post 101 you need its text from the blog table, its author's name from users and its category from category: three tables, tied by foreign keys. A key-value store keeps the whole page — text, author, category — as one value under one key, post:101, and hands it back in one step.

Key-value store — a database that stores every value under a unique key and reaches it only through that key: "(Business) Key -> Value" (p. 345). Its whole interface is: put a value under a key, get the value of a key, delete a key. The value is opaque: the store never looks inside it.

The examples of p. 345 are keys that businesses already have: "(twitter.com) tweet id -> information about tweet", "(kayak.com) Flight number -> information about flight", an account number at a bank, an item number at Amazon. Most of what a site is asked starts from one of them, hence the slide's last line: "Search by ID is usually built on top of a key-value store". In Python terms the store is a dict: d[key] = value, d[key], del d[key] — and no loop over the values.

"It all started in 2004 when Amazon was running Oracle's enterprise edition with clustering and replication," Werner Vogels, Amazon's CTO, recalled in 2017. The database could not "sustain the availability, scalability and performance needs" of the business, and "a deep dive on how we were using our existing databases revealed that they were frequently not used for their relational capabilities": "About 70 percent of operations were of the key-value kind, where only a primary key was used and a single row would be returned." That was "the genesis of the Amazon Dynamo database", published in 2007; Riak, in the key-value cell of p. 342, is one of its clones (p. 411), and Amazon's DynamoDB followed in 2012.

Page 346 asks the obvious question, "Isn't that just a database?", and lists what a relational database is: "Queried using SQL", "Key-based", "Foreign keys", "Indexes", "Joins". Read the list as two columns: a relational database has all five, a key-value store keeps only one — key-based. Keys exist in both, but they play different roles. In the relational blog the primary and foreign keys connect the tables, indexes let SQL search by any column — the slide's query, SELECT user_id from users WHERE username = "jbellis", finds a user by name, not by key — and joins put the page back together. The course notes say it in one line: in a key-value store "keys are just used to retrieve a specific value, unlike relational databases where primary and foreign keys are means to do searches and mapping between tables".

SELECT user_id FROM users WHERE username = 'jbellis' users user_id username state blog blog_id user_id blog_entry categoryid subscriber subscriber blogger row_id category category categoryid ask by any column

Before: the relational way (p. 346)

GET post:101 post:101 {entry, author, category} post:102 {entry, author, category} post:103 {entry, author, category} user:1 {username, state} posts by jbellis? only by key

After: the key-value way

Fig. 1. Left, the blog database of p. 346: four tables tied by foreign keys (the arrows). SQL may ask by any column — here the username — because the engine keeps indexes and joins the tables. Right, the same blog in a key-value store: each post page is one value under one key, fetched in one step by GET post:101. A question about what is inside the values, such as all the posts of jbellis, has no way in.

So the key-value store gives up a lot: no query on what is inside the values, no join, no secondary index, no schema to check. What it buys with that is cost. A lookup by key in a hash table takes one step whatever the number of keys, and nothing has to be parsed, planned or assembled. The race below puts numbers on it.

The lookup race: one key against a tree and a join

The same record fetched three ways while the tables grow from a thousand to a billion rows. Each curve counts the steps one lookup takes and multiplies them by the cost of one step. The costs are assumed orders of magnitude, not slide numbers: a random access to memory about 0.1 µs, a random page read about 0.1 ms on an SSD and 10 ms on a spinning disk; the B-tree holds 100 keys per page; nothing is cached unless you choose RAM. The shaded band at the top is everything slower than 100 ms, the delay p. 343 prices. Both axes are logarithmic: each grid line is ten times the one below it.

  1. Keep "a spinning disk" and N = 1 million. Before you touch anything: which curve will stay flat when N grows to a billion, and how many page reads will the B-tree need there? Then drag the slider to the right end.
    What you should see

    Only the key-value line stays flat: one step whatever N, because a hash table jumps straight to the slot of the key. The B-tree gains a level each time N grows a hundredfold: 4 page reads at a million, 6 at a billion (5 index levels and the data page), 60 ms; the join pays it three times, 18 reads, 180 ms. That is what the widget exists to show: the cost of a key lookup does not grow with the data, every other path does.

  2. Still on the spinning disk, predict: at 1,000 rows per table, is the join of three tables inside the shaded band, slower than the 100 ms of p. 343? Then find the smallest N where it enters the band.
    What you should see

    Not yet: 9 page reads, 90 ms. It enters the band just past 10,000 rows (12,589 on the slider): the B-tree grows a third level, each lookup needs 4 reads and the join 12, so 120 ms — in this model, with nothing cached.

  3. Switch to "RAM, everything cached". Predict: does the key-value store still win at a billion records, and by how much?
    What you should see

    Yes, by far less: 100 ns against 600 ns for the B-tree and 1.8 µs for the join, 6 and 18 times. In memory the gap is only the number of steps; on disk it is the medium, three to six orders of magnitude. A key-value store removes both — one step, in memory — and a real database also parses and plans every SQL statement and takes locks, which this model leaves out.

One step in memory is the whole point. A relational lookup, however well indexed, climbs a tree whose height grows with the data and, on disk, pays a seek at every level; a join pays it once per table.

A key-value store answers one question — what is under this key? — and gives up every other question to answer that one in a single step.

There is a second gift (background, not on these slides). Because every request names its key, the keys can be spread over many machines by hashing them, and any client can compute where a key lives. Dynamo placed keys and machines on a ring — consistent hashing, invented at MIT in 1997 for web caches. Redis's own ways of splitting its keys come in lesson 19; the ring itself returns with Cassandra in lesson 22.

"A key-value store is a relational table with two columns, key and value." No: a table can be searched by its value column, indexed and joined. A key-value store has none of that; the only efficient question is the exact key. Redis adds operations on the value (next section), never a query over the values.

Redis: a dictionary served over the network

The section slide of p. 347 spells the name: REmote DIctionary Server. A dictionary — a hash map from keys to values — living in a server and answering over the network. Page 348, "What is REDIS ?", gives the definition:

Redis — "Redis is an advanced key-value store, where keys can contain data structures such as strings, hashes, lists, sets, and sorted sets. Supporting a set of atomic operations on these data types." The slide highlights the next sentence: "Redis is a different evolution path in the key-value databases where values are complex data types that are closely related to fundamental data structures and are exposed to the programmer as such, without additional abstraction layers."

Three ideas to keep apart. It is a key-value store: access by key, nothing else. The value under a key can be a data structure the server understands — a string, a hash, a list, a set, a sorted set. And the operations on those structures are atomic. The middle idea is what "advanced" means. To a plain key-value store a list is a string of bytes like any other; to Redis it is a list, so you send the operation instead of the value (Fig. 2).

client jobs [bar,foo] GET jobs [bar,foo] append baz SET jobs [bar,foo,baz] OK

Before: a plain key-value store

client jobs bar ↔ foo RPUSH jobs "baz" (integer) 3 after jobs bar ↔ foo ↔ baz one command, on the server

After: Redis

Fig. 2. Adding one job to a list. Left: to a plain key-value store the list is an opaque value, so the client reads all of it, appends in its own code and writes all of it back — the whole value crosses the network twice, and another client's SET can fall between the two steps. Right: Redis knows the value is a list, "exposed to the programmer as such" (p. 348), so one atomic command appends on the server and returns the new length.

The exercise session says the same in plain words: "The stored values can be simple types (e.g., numbers) or complex structures (e.g., lists)" (p. 1581). Values — keep that word in mind when you reread the first sentence of p. 348.

"In Redis a key can be a list or a hash." No: a key is always a string (binary-safe); the value stored under it is the string, hash, list, set or sorted set. The slide's "keys can contain data structures" is Redis's own old tagline and means that a key holds such a value, as the next sentence says: "values are complex data types" (so do the table of p. 354 and the session, p. 1581). Check it on a command: in HSET user:1 name John the key user:1 is a plain string, the hash with its field name is its value, and TYPE user:1 answers hash, the type of the value. If a true/false question quotes the slide's sentence, it is the slide's own definition: treat it as true, and in your own answers write "the value of a key can be".

The last line of p. 348: "Can be used as Database, a Caching layer or a Message broker", and each role has its reason in a box under it. A database, because "Redis can persist data to the disk": it can write snapshots and logs and reload them after a restart (lesson 19). A caching layer, because "Redis is fast": copies of data that is expensive to fetch or to compute are served from memory. A message broker, because "Redis is not only a key-value store": clients publish messages on channels that other clients subscribe to, or hand work through a list that a worker waits on.

Page 349, "What is not REDIS", draws the limits:

Each limit follows from where the data lives. In memory, the dataset must fit in the RAM you can afford, so its size must be foreseeable. Persistence is a dial: the more often Redis writes to disk before it answers, the longer each write waits for the disk — the price you saw in the race. So the system of record stays elsewhere, and Redis holds what is small, hot and changing fast (Fig. 3).

application the truth hot data SQL / document store orders, payments, patient records the system of record Redis sessions, carts, counters, cached pages, queues, pub/sub beside it, not instead of it
Fig. 3. Where Redis sits, after p. 349: not "a replacement for Relational Databases nor Document Stores" but "complementary" to them. The application keeps what it must never lose in the system of record, and the small, hot, rapidly changing data in Redis.

The use cases of p. 350 are the roles at work; the table pairs each with what does the work in Redis. (If you ever serve a trained neural network in an application, the features it reads at prediction time live in the same kind of store: the feature-store lecture says they are "typically stored in low-latency databases like Redis", p. 735, lesson 29.)

The use cases of p. 350 and what serves each of them.
Use case (p. 350)RoleWhat does the work
Caching; MVC Output Cache providercaching layera value under a key, usually with a lifetime (lesson 19)
Counting thingsdatabasea string holding an integer, increased on the server
Blocking queuesmessage brokera list: push at one end, a pop that waits at the other
Pub/Sub (service bus); Backplane for SignalR; "Any real-time, cross-platform, cross-application communication"message brokerchannels: publishers send, every subscriber receives
ASP.NET Session State provider; Online user data (shopping cart, …)database or cachea hash per session, user or cart, often with a lifetime

Page 351, "When to consider Redis", condenses all of this into four conditions — two reasons to choose Redis, then two limits it must respect:

If you revise from the course notes: their p. 20 says that "being in-memory means that a single shutdown will cost you all your data". The slides say otherwise — "Redis can persist data to the disk" (p. 348). What a crash may cost is the most recent writes, depending on how persistence is set (lesson 19); that is the risk "Dataset is not critical" accepts.

Redis is a key-value store whose values are data structures the server understands: you send the operation, not the whole value.

One thread, one command at a time

Page 352, "Redis Architecture", is a list of facts. The plain ones first: Redis is "Written in ANSI C by Salvatore Sanfilippo (@antirez)", "Works in most POSIX systems like Linux, BSD and OS X", "Linux is the recommended", and there is "No official support for Windows" (the slide adds a Win-64 port maintained by Microsoft). Two more lines carry the exam.

Start from a case. Two nurses' tablets record a visit to the same ward at the same instant: both send INCR visits, and visits holds 10. Inside a Redis server there is no "same instant": the two commands wait in line and run one after the other, 10 → 11 → 12 (Fig. 4).

clients A B C queue one thread A INCR visits → 11 C INCR visits → 12 B LPUSH jobs x → 1 A GET visits → "12" time no two at the same time
Fig. 4. Three clients, one thread. Commands from every connection wait in one queue and run one at a time, each to its end before the next begins — "no two commands can run at the same time" (p. 352). The two increments of A and C cannot interleave: visits goes from 10 to 11 to 12, with no lock anywhere.

The slide: "Redis is a single-threaded server, not designed to benefit from multiple CPU cores. Several Redis instances can be launched to scale out on several cores." And the consequence, one bullet later: "All operations are atomic (no two commands can run at the same time)."

Atomic command — a command that runs from start to end with no other command in between: no client can see it half done, and it happens entirely or not at all.

Single-threaded, so atomic, with no lock anywhere: a lock exists to keep two threads off the same data, and here there is only one thread. Two clients that send INCR at once always count twice; an LPUSH never meets a half-built list.

"All operations are atomic, so anything I send is safe." Each command is atomic. A GET followed by a SET is two commands, and another client's command can run between them — the lost update that the exercise session draws, and the reason INCR exists (lesson 18 replays it). Several commands become one atomic block only inside a transaction (MULTI … EXEC) or a Lua script: the advantages slide names "Atomic operations and Transactions" and "LUA scripting support" (p. 365) without teaching them — background beyond the exam's commands.

One thread sounds slow. It is fast for four reasons:

Kleppmann adds a subtler reason in Designing Data-Intensive Applications (background): "the performance advantage of in-memory databases is not due to the fact that they don't need to read from disk … Rather, they can be faster because they can avoid the overheads of encoding in-memory data structures in a form that can be written to disk." A Redis list is a list in memory, not a page format to translate back and forth.

The design has a price. While one command runs, every other client waits, so a command over a huge value — all the members of a set with a million entries — holds up everybody; "most commands in O(1)" is what keeps the queue moving. And one thread uses one core: to use eight cores you run eight instances, each with part of the keys — the seed of partitioning (lesson 19).

Is Redis still single-threaded?

Mostly. Since Redis 6.0 (2020) reading and writing the network traffic can be spread over extra threads, and saving to disk runs in a separate child process. The commands themselves still execute one at a time on one thread, which is what the slide means, what makes them atomic, and what the exam expects you to say.

Page 353 draws the whole server on one slide, and it doubles as the map of this lesson and the next two (Fig. 5).

data structures · lessons 17–18 persistence · lesson 19 string hash list set sorted set HLL bitmap REDIS journal (text) snapshot (binary) persist / restore OS replication networking clients sentinel cluster lesson 19
Fig. 5. The architecture map of p. 353, redrawn without its colours, with the lessons that cover each part. Left, the data structures (HLL, the HyperLogLog, and bitmap are built on strings); right, persistence; below, replication, networking and clients, and the two add-ons for when one server is not enough, Sentinel and Cluster.

Read it as a table of contents. Left, the data structures: the next section, then the commands of lesson 18. Right, persistence: the "journal (text)" is the log of every write, the AOF, and the "snapshot (binary)" a picture of the whole dataset, the RDB file — the two mechanisms of p. 359. Below, the machinery for more than one server: replication keeps slaves as "exact copies of the master" (p. 359), Sentinel promotes a slave when the master dies, Cluster also spreads the keys over several masters (lesson 19).

One thread runs one command at a time: every command is atomic without a lock, and fast because each is a short step in memory.

Seven shapes for a value

Page 354, "Redis data types", lists what the value under a key can be: seven types, each with a picture, what it "Contains" and its "Read/write ability". Look at the pictures first (Fig. 6); the slide's text for each type is in the tabs below it.

String greeting I'm a string! 0 1 1 0 0 0 0 … Hash user:1 name John visits 1 List jobs A ↔ C ↔ B ↔ C Set users D B C A Sorted set scores B 0.1 D 0.3 A 250 C 250 Geospatial lat, lon places Value 20.63373 -103.55328 HyperLogLog visitors 0 1 1 0 0 0 0 1 … fixed 12 KB
Fig. 6. The seven types of p. 354, each drawn as the value under one key (violet). The pictures are the slide's, with concrete fields for the hash: the list keeps order and repeats (C twice), the set keeps neither; the sorted set is ordered by score, A and C tying at 250; the geospatial index holds a member with its latitude and longitude; the HyperLogLog holds only bits. A string can also be read and written bit by bit — the bitmap row.

Read the pictures in pairs. A list and a set both hold strings, but the list keeps the order of insertion and the repeats — C appears twice — while the set keeps neither: each string once, in no order. The exercise session gives the reason to pick a set: "SETS are useful as it is very quick to test the existence of a value, which is not as easy when it comes to lists" (p. 1600). A sorted set is a set whose members carry a number, the score, and stay in score order; A and C tie at 250 and are ordered by name (Redis breaks ties alphabetically; background). A hash is a small dictionary inside the value: its inner keys are called fields (HSET user:1 name John), so the Redis key names the object and the fields name its attributes.

The last two are built on the others. A geospatial index is "Sorted set implementation using geospatial information as the score": the position is packed into one number, so points near each other mostly get scores near each other and a radius search becomes a few ranges of scores (background) — TYPE on such a key answers zset. A HyperLogLog answers one question only, how many different items have been added, approximately and in at most 12 KB; it never gives an item back.

Contains: "Binary-safe strings (up to 512 MB), Integers or Floating point values, Bitmaps." Read/write ability: "Operate on the whole string, parts, increment/decrement the integers and floats, get/set bits by position."

Binary-safe means any bytes: a word, a JSON text, a whole JPEG. A string that holds a number can be increased on the server — the counter of "Counting things". In Python: str, bytes, or int when it holds a number. Use it for one value: a cached page, a counter, a flag.

# p. 355: bar held 223; returns (integer) 1223
INCRBY bar 1000

Contains: "Unordered hash table of keys to string values". Read/write ability: "Add, fetch, or remove individual items by key, fetch the whole hash."

The "keys" inside a hash are its fields. In Python: a dict of strings to strings. Use it for one object with named attributes — a user, a patient, a gas heater — read one field or all of them.

# p. 357: returns (integer) 1, the number of fields added to user:1
HSET user:1 name John

Contains: "Doubly linked list of strings". Read/write ability: "Push or pop items from both ends, trim based on offsets, read individual or multiple items, find or remove items by value."

Order of insertion, repeats allowed, cheap at both ends. In Python: a collections.deque. Use it for queues, stacks and "the latest N".

# p. 357: returns (integer) 1, the new length of the list jobs
RPUSH jobs "foo"

Contains: "Unordered collection of unique strings". Read/write ability: "Add, fetch, or remove individual items, check membership, intersect, union, difference, fetch random items."

No order, no repeats, a fast "is it in?". In Python: a set. Use it for tags, memberships and "what do these two have in common".

# p. 358: returns (integer) 2, the number of new members
SADD users "John" "Peter"

Contains: "Ordered mapping of string members to floating-point scores, ordered by score". Read/write ability: "Add, fetch, or remove individual items, fetch items based on score ranges or member value."

Unique members, each with a score, kept in score order. Python has no built-in twin: think of a dict from member to score that is always sorted by score. Use it for rankings, leaderboards and anything "sorted by year".

# p. 358: the score comes before the member; returns (integer) 3
ZADD scores 50 "Peter" 200 "Charles" 1000 "Mary"

Contains: "Sorted set implementation using geospatial information as the score". Read/write ability: "Add, fetch or remove individual items, search by coordinates and radius, calculate distance."

Use it for "what is near this point". The commands take the longitude first.

# the point of p. 354, longitude first; returns (integer) 1
GEOADD places -103.55328 20.63373 "Value"

Contains: "Probabilistic data structure to count unique things using 12Kb of memory". Read/write ability: "Add individual or multiple items, get the cardinality."

The cardinality is the number of different items. In Python terms: len(set(items)), approximately, without keeping the set. Use it for unique visitors or distinct searches when a small error is fine.

# returns (integer) 1: the structure changed
PFADD visitors "u17" "u03" "u17"
# returns (integer) 2: about two different visitors
PFCOUNT visitors
How can 12 KB count a billion different visitors?

Background, not on the slides: the idea behind HyperLogLog, with the two numbers the Redis documentation gives, 16,384 registers and 12 KB.

  1. Hash every item. A good hash looks like a row of fair coin flips, and a returning visitor produces exactly the same row, so repeats change nothing.

  2. Look at how each row starts. A run of \(k\) zeros at the front turns up about once in \(2^{k}\) different rows: if the longest run seen so far is 20, about a million different items have probably gone by. That is one small number, not the items.

  3. One longest run is a noisy guess, so 14 bits of the hash send each item to one of \(2^{14} = 16\,384\) groups; each group keeps its own longest run in a register, and the registers are combined into one estimate.

  4. A register stores only a run length, and 6 bits count up to 63, so the memory has a fixed ceiling whatever comes in:

    \[ \begin{aligned} 16\,384 \times 6 &= 98\,304 \text{ bits} \\ &= 12\,288 \text{ bytes} \end{aligned} \]

    — the 12 KB of p. 354, the most it ever takes, for a thousand items or a billion.

  5. The price is an error. With \(m = 16\,384\) registers the standard error is

    \[ \frac{1.04}{\sqrt{m}} = \frac{1.04}{128} \approx 0.81\,\% \]

    — and, as with the standard error of a mean in statistics, it shrinks like one over the square root of what you average.

The slide's seven are the classic types. Newer versions add Streams (since Redis 5.0, 2018), a log of messages for queues, and modules such as JSON — background, not on the slides. Choosing a type is choosing which commands you get for free: ask what the question needs — order, uniqueness, a score, named fields, a position, a count of different things — and the type follows. Try it on eight cases.

Pick the structure

Eight needs from the worlds of the past papers. Choose a scenario, then press the type you would store it in. The picture shows what the key would hold in that type, the verdict says why it fits or not, and the right type shows the commands that do the work on the server (their details are lesson 18). The insight to take away: the question you will ask decides the type.

  1. Open "Unique visitors". Predict what a List would report for the six visits, then press List, Set and HyperLogLog in turn.
    What you should see

    The list keeps every visit, so its length is 6 for 4 people. The set keeps each id once, an exact 4 — but it stores every id, millions of them on a busy day. The HyperLogLog says about 4 and never grows past 12 KB: the right answer when an error of about 1 % is fine.

  2. Open "Fastest laps", then "A waiting queue", and press Sorted set in both. Predict first: where is it right, and why only "works, but" in the other?
    What you should see

    Right for the laps: the lap time is a natural score, and Redis keeps the fastest first however the times arrive. For the queue you would have to invent the score (a check-in number) yourself, while a list keeps arrival order for free.

  3. Find the two scenarios where a List is the right type. What do their questions have in common?
    What you should see

    "A waiting queue" and "The last ten readings": both depend on the order of arrival, both keep repeats, and both work at the ends of the list — push at one end, pop or trim at the other.

  4. Open "Ambulances near a landslide" and press Sorted set. Why does the verdict say "close"?
    What you should see

    Because a geospatial index is a sorted set whose score encodes the position (p. 354). With plain sorted-set commands you would have to compute that score and the distances yourself; the GEO commands do both.

In an exam exercise, write the type you chose and its reason in one line before the commands — "a hash: one key for the object, one field per attribute". The paper of 30 Jan 2026 asked for "a single key-value pair to store all the data of one Gas Heater" and named no type: there the choice is the point of the question (lesson 18 solves that paper; the exercise below trains the same choices on another paper's ER model).

Pick the type by the question you will ask: order and repeats, a list; uniqueness, a set; order by a number, a sorted set; named fields, a hash; one value or a counter, a string; a position, a geospatial index; how many different, a HyperLogLog.

The puzzle, answered

Back to the two choices of 2009 that sounded backwards. A database that answers only "what is under this key?" is the fix when a relational one is too slow, because that question costs one step in memory whatever the size of the data, with nothing to parse, plan or join — the flat line of the race. And Redis gives back much of what a plain key-value store loses: the value is a data structure, so a push, an increment or a membership test is still one command on one key.

One thread needs no lock because it cannot race with itself: commands run one after another, each atomic. It keeps up because every step is short and in memory, and when one core is not enough you start another instance. The price is printed on p. 349: the data must fit in memory, and the safer you make it on disk, the slower it gets.

The course introduction says that each category of systems "is covered along 5 dimensions": data model, "(declarative vs. imperative) query languages", data distribution, non-functional aspects and architectural solutions. A theory question on the features of Redis fits on that grid:

Redis along the five dimensions of the course agenda.
DimensionRedis
Data modelkey → value: the key a string, the value one of seven data structures (pp. 348, 354)
Query languagenothing declarative: no SQL, no query over the values; imperative commands, each one operation on one key (lesson 18)
Data distributionseveral instances "to scale out on several cores" (p. 352); replication, and partitioning that breaks up the data "across different hosts in a cluster" (p. 359, lesson 19)
Non-functional aspectsfast because in memory and most commands O(1); every command atomic; the dataset must fit in memory; more persistency, more latency and less throughput (pp. 349, 352)
Architecturea single-threaded server in ANSI C; around the data structures, persistence, replication, networking, clients, Sentinel and Cluster (pp. 352–353)

When the exam's one-point table asks which DB technology fits which entities (lesson 5), motivate a key-value row like this: data accessed only by its ID, read and written very often, small enough for memory and not critical — sessions, carts, counters, cached results, live rankings — in Redis.

Exam-style questions

Theory questions in the forms the theory part uses — short open answers, true/false and multiple choice, where a wrong tick may cost points — and one practical exercise in the style of the papers, on the ER model of a past paper. The Redis exercises of the past papers themselves are solved in lesson 18.

What is a key-value store? Explain why search by ID is usually built on top of a key-value store, and what a key-value store gives up compared with a relational database.

Show model answer

A key-value store is a dictionary: every value is stored under a unique key, typically a business key such as a tweet id, a flight number, an account number or an item number, and the only way to reach a value is through its key. The store does not interpret the value; its operations are put, get and delete by key. Search by ID is built on top of a key-value store because a lookup by key is the cheapest possible access: with a hash table in memory it takes constant time whatever the size of the data, and it needs no query parsing, planning or join. Compared with a relational database, which is queried using SQL and is key-based with foreign keys, indexes and joins, a key-value store keeps only the key: no queries on the content of the values, no foreign keys, no secondary indexes, no joins. Keys are used only to retrieve a value, not to search or to map tables to each other. In exchange the store is very fast and easy to split across machines by key.

Three things make the answer complete: the definition (one key, one opaque value, access only by key), the reason (constant-time lookup with no parsing, planning or joins) and the trade-off measured against the five items of p. 346. One example from p. 345 shows you know the slide.

What is Redis? Explain its definition, the three roles it can play, and what Redis is not.

Show model answer

Redis (REmote DIctionary Server) is an advanced key-value store in which the value stored under a key can be a data structure — a string, a hash, a list, a set or a sorted set — with a set of atomic operations on these data types. Unlike a plain key-value store, the values are complex data types close to the fundamental data structures and are exposed to the programmer as such, without additional abstraction layers, so the client sends an operation (append this element, add this member) and the server applies it. Redis can be used as a database, because it can persist data to disk; as a caching layer, because it is fast, the data being in memory; and as a message broker, because it is not only a key-value store (publish/subscribe, blocking queues). It is not a replacement for relational databases or document stores: it is used next to them. Increasing persistency increases latency and decreases throughput, and the dataset should fit in memory, so Redis is best used for rapidly changing data with a foreseeable size.

The three parts of p. 348, the three roles each with its reason, and the limits of p. 349. Prefer "the value of a key can be a list" to the slide's "keys can contain data structures" (the pitfall in section 2), but recognise the printed wording if a question quotes it.

True or false: since all operations in Redis are atomic, a client that reads a counter with GET and then writes the incremented value back with SET performs one indivisible operation.

  • True Each of the two commands is atomic on its own, but p. 352 only guarantees that no two commands run at the same time: another client's command can run between the GET and the SET.
  • False Atomic means each single command. GET and SET are two commands; another client's SET can fall between them and an increment is lost. INCR does the read and the write in one command.
Show model answer

False. Redis is single-threaded, so no two commands run at the same time and every single command is atomic. A GET followed by a SET is two commands: another client's command can be executed between them, so an update can be lost. A single INCR (or INCRBY) is atomic.

The trap is the word "all". Lesson 18 plays this race step by step. Wrong answers may carry a penalty (the rule is announced just before the exam): tick only what you can justify.

Redis is a single-threaded server. Explain why this makes every command atomic, why Redis is nevertheless fast, and how it can use more than one CPU core.

Show model answer

Redis executes commands on a single thread: the commands of all clients are queued and executed one at a time, each from start to end, so no two commands can run at the same time and no client can observe or interleave with a half-executed command. Every command is therefore atomic, and no locks are needed. Redis is nevertheless fast because the data is in memory, so no disk access is on the path of a request; because most commands run in O(1) and take very little time; because a single thread spends nothing on locks or on switching between threads; and because one event loop serves many connections. The cost is that a slow command, such as an O(N) operation on a large structure, delays every other client. Redis is not designed to benefit from multiple CPU cores: to use several cores, several Redis instances are launched, each holding part of the data.

Three questions, three answers; the chain single thread, then atomic, then no locks is the core of the answer. You may add as background that since Redis 6.0 network I/O can use helper threads, while commands still execute on one thread.

Which statements about the Redis data types are correct? Select all that apply.

  • A list may contain the same string more than once, in insertion order. A list is a "Doubly linked list of strings"; the slide's picture holds A, C, B, C.
  • A set returns its members in the order in which they were inserted. A set is an "Unordered collection of unique strings".
  • The members of a sorted set are ordered by a floating-point score. "Ordered mapping of string members to floating-point scores, ordered by score" (p. 354).
  • A geospatial index is implemented as a sorted set. "Sorted set implementation using geospatial information as the score" (p. 354).
  • A HyperLogLog returns the list of the unique items it has seen. It only estimates their number, the cardinality, in at most 12 KB; the items are not kept.
  • A string can hold an integer that Redis increments on the server. Strings contain "Integers or Floating point values" and can "increment/decrement the integers and floats" (p. 354).
Show model answer

Correct: the statements on the list, the sorted set, the geospatial index and the string. A set is unordered; a HyperLogLog only estimates the number of distinct items and cannot return them.

A select-all question where wrong ticks may cost points: the two false statements are the classic confusions, list against set and counting against storing.

Exam-style exercise, written for this notebook on the ER model of the exam of 30 May 2025 (its header prints June 30, 2025), tracking puzzles' production and sales; that paper has no Redis exercise. The part used: Puzzle (Puzzle ID, Name, Collection Name (e.g., Museum Collection), Number of Pieces, Width, Height, Description), produced by one Company (Produce, with Production Date: Company (1, N) – Puzzle (1, 1)); User (Personal ID, Name, Surname, Age, Birth Date, City, Country), who buys puzzles (Buy, with Buy Date: Puzzle (0, N) – User (0, N)). N.B. All sizes are in cm.

Exercise – Redis. For each request, state the Redis data type you use and why, in one line, then write the commands.

(1) Create a single key-value pair to store all the data of one Puzzle, with all the attributes describing the object.

(2) Store the puzzles of the "Museum Collection" sorted by their number of pieces. Then, query them to get the puzzle with the most pieces.

(3) Store the countries of the users who bought the puzzle "P1", each country only once. Then, check whether "Italy" is one of them.

Show model answer

(1) A hash: one key for the puzzle and one field for each of its attributes, so a single attribute can be read or changed on its own. The values are examples, and the attribute names are written with underscores (a field name with spaces would need double quotes).

HSET puzzle:P1 Puzzle_ID "P1" Name "Starry Night" Collection_Name "Museum Collection" Number_of_Pieces 1000 Width 70 Height 50 Description "Van Gogh, 1889"

The reply is (integer) 7, the number of fields created. One string holding the whole puzzle as JSON would also be a single key-value pair, but reading one attribute would then mean reading the whole text.

(2) A sorted set: the number of pieces is the score, so Redis keeps the puzzles in that order whatever the order of insertion. The highest score is the last element, at index -1, so the second command returns "P3".

ZADD museum:by_pieces 1000 "P1" 500 "P2" 2000 "P3"
ZRANGE museum:by_pieces -1 -1

(3) A set: each country is kept once, however many users from it bought the puzzle, and the membership test is one command. SADD replies (integer) 3, because the second "Italy" is ignored; SISMEMBER replies (integer) 1.

SADD puzzle:P1:countries "Italy" "France" "Italy" "Spain"
SISMEMBER puzzle:P1:countries "Italy"

Written for this notebook in the wording of the papers; no official solution exists for it, nor for any past paper. It trains the choice that the exam of 30 Jan 2026, exercise 5, asks on its hospital ER model (solved in lesson 18). What makes each answer complete: (1) every attribute of Puzzle under one key — a hash, or JSON in a string; (2) a structure that keeps the order by itself, the score written before the member, and the right end read: index 0 is the lowest score, -1 the highest — a list would need the puzzles pushed already in order; (3) uniqueness and membership, the two things a set gives for free and a list does not ("very quick to test the existence of a value", p. 1600).

Check yourself

A key-value store keeps every blog post under keys like post:101, with the whole page as the value. Which request can it not answer by itself?

  • Return post 101 That is the one question a key-value store answers, in one step: get by key.
  • List the posts written by jbellis The author is inside the values, and the store never looks inside them; without a second key mapping jbellis to his post ids there is no way in.
  • Delete post 102 Delete by key is one of its three operations.
  • Replace the page of post 103 Put a new value under the same key.

In the lookup race, choose "RAM, everything cached" and grow N from a million to a billion. What happens?

  • All three curves stay flat: in memory every lookup takes constant time Memory makes each step cheap, not the steps fewer: the B-tree still descends one more level each time N grows a hundredfold.
  • The hash lookup stays at 1 step; the B-tree goes from 4 to 6 page visits and the join from 12 to 18 The cost of one step dropped from milliseconds to 0.1 µs; the number of steps did not change.
  • Only the join grows: a single B-tree lookup does not depend on N The join grows because each of its three lookups grows: 3 × (levels + 1).
  • The hash lookup grows too, because a larger table needs more probes A hash table jumps straight to the slot of the key; its expected cost does not depend on how many keys there are.

Why does RPUSH jobs "baz" on Redis beat GET, append and SET on a plain key-value store (Fig. 2)?

  • Redis knows the value is a list and appends one element on the server in one atomic command; with GET and SET the whole value crosses the network twice and another client can write in between The server understands the structure, so you send the operation instead of the value.
  • RPUSH writes the list to disk, while GET and SET only work in memory Both work on the value in memory; persistence is a separate matter.
  • Redis runs list commands on several threads in parallel Redis executes commands on one thread (p. 352).
  • There is no real difference: both are a single step for the server GET plus SET are two commands with work in the client between them, and the whole value travels twice.

Which of these follow from Redis executing commands on a single thread? Select all that apply.

  • Every single command is atomic No two commands run at the same time (p. 352).
  • No lock is needed to keep two commands off the same key There is no second thread to keep out.
  • One slow command over a huge value makes every other client wait The thread is busy until it ends; that is why "most commands in O(1)" matters.
  • Redis uses all the cores of the machine by itself "not designed to benefit from multiple CPU cores" (p. 352): you launch several instances.
  • A GET followed by a SET from one client runs without interruption Those are two commands: another client's command can run between them.

Atomic per command, not per conversation: that line separates this lesson's theory from the INCR of lesson 18.

In HSET user:1 name John, which part is the data structure that p. 348 means by "keys can contain data structures"?

  • The value stored under the key user:1, a hash with the field name The key is a plain string; the hash is its value — TYPE user:1 answers hash.
  • The key user:1 itself, which becomes a hash Keys are always (binary-safe) strings; only values have types.
  • The field name, which is a key inside the key name is a field of the hash: the hash's own inner key, part of the value.
  • The string John John is the value of one field inside the hash.

A screen lists a hospital's buildings from the newest to the oldest by year of construction, and new records are added over time in any order. Which type keeps them in that order without your code sorting them?

  • A list, pushing each building as it is recorded A list keeps the order of insertion: a building recorded later but built earlier lands in the wrong place.
  • A sorted set with the year as the score Redis keeps the members ordered by score; reading from the highest score down gives the newest first, whatever the order of insertion.
  • A set of building ids A set has no order at all.
  • A hash from building id to year A hash stores the years but returns its fields in no particular order.

A hospital plans to keep all its patients' medical records — 60 TB, growing every year, and by law never to be lost — only in Redis. Which conditions of p. 351 does the plan clearly violate? Select all that apply.

  • Dataset can fit in memory 60 TB, and growing, is far more than the memory of one server, and not a foreseeable size: Redis is "Best used for rapidly changing data with a foreseeable database size (should fit mostly in memory)" (p. 349).
  • Dataset is not critical These records are critical, and making every write durable costs latency and throughput (p. 349), the very speed Redis is chosen for.
  • Speed is critical Nothing in the plan contradicts it; speed is simply not the problem here.
  • More than just key-value pairs A record could well be a hash; the data types are not the problem.

Redis belongs next to the system of record (Fig. 3), not in its place.

Key take-aways

What to watch and read