Modeling This in Redis
Premier League Predictor: FastAPI & Redis
Chapter 2 · Modeling This in Redis
Chapter 1 connected to Redis and previewed one of its real data structures. Before any real feature
gets built, this chapter answers a more basic question: what does "teams," "seasons," and
"fixtures" even look like once there's no schema, no tables, and no CREATE TABLE
statement to write?
Redis Has No Tables — Only a Flat Keyspace
Every value Redis stores — a string, a hash, a set, a sorted set — lives under one single, flat key, in one single, shared namespace. There's no real concept of "the teams table" the way a relational database has one. The real, idiomatic substitute is entirely a matter of discipline: a consistent key-naming convention that a human reading the raw keyspace can still make sense of.
Nothing enforces this convention — it's a real, load-bearing team agreement, not a database feature. Every real finding in this chapter traces back to that one fact.
Real IDs: INCR, Not AUTO_INCREMENT
Redis has no native equivalent to SQL's AUTO_INCREMENT or SERIAL. The real,
idiomatic replacement is a dedicated counter key and the atomic INCR command:
INCR is genuinely atomic — even under real concurrent requests, two calls can never both
receive the same number, the same real guarantee a SQL auto-increment column provides.
A Team as a Hash
A hash is the closest thing Redis has to a single row — a set of named fields under one key. So far
this looks like a plausible, working substitute for a teams table.
id1 by mistake for what was meant to be a genuinely different team, calling
HSET again on the exact same key:
name and
founded were silently replaced, but stadium survived untouched from the
original write. There's no error, no warning, and no way to tell from the resulting hash alone that
two different logical records were ever involved — the surviving stadium field quietly
hides the fact that anything went wrong at all. A relational INSERT with a duplicate
primary key would have failed outright with a real, loud constraint violation; this fails silently,
by blending.
Season → Teams: A Set, Not a Hash
"Which teams are in this season" is genuinely different from "what are this team's own fields" — it's an unordered, deduplicated group of IDs, not a record. A Redis Set fits that shape directly:
This is deliberately not a sorted set — nothing about season membership needs an ordering at all, so the plain, unordered Set is the honest, minimal fit. Sorted sets are saved for Chapter 7 and Chapter 8, where a real ordering (points, ranking) genuinely exists.
A Fixture as a Hash — Referencing Teams With Plain Values
home_team_id and away_team_id are just plain field values — strings that
happen to look like team IDs. Redis has no concept of a foreign key, so nothing checks that either
one actually points at a real team.
team:999 genuinely exists:
Fixture.home_team_id and away_team_id as real foreign keys against
teams.id, so the identical insert there would raise a genuine
IntegrityError at the database layer, before the bad row could ever be written. Here,
the exact same mistake produces a real, permanently stored, genuinely broken fixture — and it stays
invisible until something later tries to actually look up team:999.
Gameweek → Fixtures: Another Set
Which Primitive Models Which Relationship
| Real relationship | Redis primitive | Why |
|---|---|---|
| A team's own fields | Hash | A named collection of fields under one key — the closest thing to a single row |
| Which teams are in a season | Set | Unordered, automatically deduplicated group membership — no ranking involved |
| Which fixtures are in a gameweek | Set | Same shape as season membership — a group, not an order |
| A fixture's own fields, including team references | Hash | Same as a team — but the "references" are just plain string values with no enforcement |
| (Chapters 7–8) The league table, the prediction leaderboard | Sorted Set | The one real relationship in this whole app that genuinely needs continuous ranking |
Revisiting Chapter 1's Own Scope Note, With Real Evidence Now
Chapter 1 asked to be taken on trust that this course is "deliberately narrower" than its own
PostgreSQL sibling. This chapter has now produced two real, concrete, reproduced costs of that
choice rather than a promise of one: HSET's own genuine partial-merge behavior can
silently blend two logical records together on an accidental ID collision, and a fixture can
reference a team that was never created, with the database itself offering no resistance at all.
Neither failure is hypothetical — both were reproduced with real code in this chapter. Chapter 9
is where this course works through the fuller, honest picture of what that costs at scale.
Hands-On Exercises
Run a real HGETALL against a team key that was never created at all -- not even accidentally, genuinely never written. Report the real return value and its real Python type, and explain why this makes the "reference to team:999" problem from this chapter even harder to catch than it already looks.
📄 View solutionRun two real SADD calls against the same season:1:teams set -- the first adding two genuinely new team IDs, the second adding one team ID already in the set alongside one genuinely new one. Report SADD's own real return value for each call, and explain what that return value is actually counting.
📄 View solutionExplain, in your own words, why the HSET-merge gotcha and the missing-foreign-key gotcha are both genuinely the same underlying limitation wearing two different costumes, rather than two unrelated bugs. What single sentence about Redis explains both of them at once?
📄 View solutionChapter 2 Quick Reference
- Key naming is the real schema — Redis enforces nothing; a consistent naming convention is the only thing standing in for one
- INCR replaces AUTO_INCREMENT — atomic, real, verified sequential IDs with no native counterpart otherwise
- Hash = record, Set = unordered membership — teams and fixtures are hashes; season/gameweek membership is a Set
- Verified: HSET merges, not replaces — an accidental ID reuse can silently blend two logical records into one, with no error
- Verified: zero referential integrity — a fixture can reference a team ID that was never created, and Redis accepts it without complaint