Exercise: Key-Value Store with Valkey

Belongs to: Key_Value_Stores · Duration: ~35 min · Work in pairs

Valkey is the open-source fork of Redis — all commands are identical. Today you build the “fast parts” of a webshop: sessions, a like counter, a shopping cart and a top-products list. And you find out what a key-value store cannot do.


0. Setup (3 min)

In the nosql-lab folder:

docker compose --profile valkey up -d
docker exec -it valkey valkey-cli
PING
INFO server

PING should answer PONG. In the INFO output, find the lines that contain version. What do you notice?


1. Strings and keys (4 min)

SET product:1:name "Keyboard"
GET product:1:name
EXISTS product:1:name
DEL product:1:name
GET product:1:name                → ?
KEYS *                            → which keys exist?

2. Sessions with a time to live (5 min)

Ben logs in. His session should expire after 20 seconds (in reality: 30 min).

SET session:8f3a "user:2" EX 20
GET session:8f3a
TTL session:8f3a                  → run it several times

Wait until the TTL is over, then GET session:8f3a → ?

Ben clicks something, so his session should be extended:

SET session:8f3a "user:2" EX 20
EXPIRE session:8f3a 60
TTL session:8f3a                  → ?

3. A like counter without lost updates (8 min)

Open a second terminal with docker exec -it valkey valkey-cli. We call them A and B.

Wrong way — read, calculate in the application, write back:

StepAB
1SET likes:post:7 100
2GET likes:post:7 → ?
3GET likes:post:7 → ?
4SET likes:post:7 101
5SET likes:post:7 101
6GET likes:post:7 → ?

Two likes were given. Is the counter right? You know this problem from the MySQL isolation exercise — what was it called?

Right way:

StepAB
1SET likes:post:7 100
2INCR likes:post:7 → ?
3INCR likes:post:7 → ?

Why can’t INCR lose an update?


4. A shopping cart as a hash (6 min)

Anna’s cart maps product id → quantity.

HSET cart:anna 1 1 2 2            # 1 × Keyboard, 2 × Mouse
HGETALL cart:anna
HINCRBY cart:anna 2 1             # one more mouse
HGET cart:anna 2                  → ?
HDEL cart:anna 1                  # remove the keyboard
HGETALL cart:anna
EXPIRE cart:anna 86400            # forget the cart after one day
TYPE cart:anna                    → ?

Now create a cart for Ben with 1 × Headphones (product 3) and 1 × Mouse (product 2).


5. Top products as a sorted set (4 min)

Every time a product is sold, its score goes up:

ZINCRBY top:products 1 "Keyboard"
ZINCRBY top:products 3 "Mouse"
ZINCRBY top:products 2 "Headphones"
ZINCRBY top:products 1 "Keyboard"
ZREVRANGE top:products 0 2 WITHSCORES      → top 3?

Sell 3 more keyboards. Is the order still the same?


6. Checkout in one step: MULTI / EXEC (3 min)

SET stock:2 10
MULTI
DECRBY stock:2 2
DEL cart:anna
EXEC                               → ?
GET stock:2
EXISTS cart:anna

What would be different if the server crashed between DECRBY and DEL without MULTI?


7. What a key-value store cannot do (2 min)

The marketing department asks: “Which customers have a Mouse (product 2) in their cart?”

Try to answer it with Valkey commands. Which commands did you need? How would this work with 2 million carts?

KEYS * scans every key and blocks the server meanwhile. In production it is forbidden — never use it on a real system.


8. Restart test & clean up

HOST: docker restart valkey, then connect again and run KEYS *. Is your data still there? Look up why in the scriptum (persistence).

HOST: docker compose --profile valkey down


Hand-in

  1. Your results (→ ?) for tasks 0–8.
  2. Short answers:
    • a) What does TTL return for a key that has no expiry? And for a key that doesn’t exist?
    • b) Why is INCR safe, but GET + SET is not?
    • c) Which Valkey data type would you use for “the last 5 products a user looked at”? Write the two commands.
    • d) Why can’t Valkey answer the question from task 7 efficiently?
  3. Fill in the key-value column of the classification grid.