Not everything should be a table

Why NoSQL if you have PostgreSQL

28 min
What you'll learn
  • explain what a database is and why a table is not the only way to store data;
  • recognize five kinds of stores by the job they do alongside a regular database;
  • run your first Redis command and watch a record disappear on its own.

Chapter 1 — “Not everything should be a table”

You are a cadet archivist on a station that preserves the archives of old Earth. Cadets walk past this section every day: dark glass, with rows of dormant stores behind it. Today, the service lights are on inside. For the first time, engineers have restored power to the Dark Gallery.

This is where Kotomarket's stores are kept — an online shop whose main database the station preserved in full: customers, products, orders. But the shop did not rely on a single database. Alongside it were fast memory for carts, a store for product cards, delivery-service servers, and storefront search. They all went dark on the shop's final evening, February 24, 2025.

At the entrance, the Commandant of the gallery meets you: “Orders, products, and customers were already in the main database. Why did the shop need so many other stores? Figure it out — and tell me what we can save.”

QUERY is coming with you — the station's AI mentor, an old archival intelligence with the habits of a cat. He'll offer hints along the way.

A cadet seen from behind in front of a long wall of dark glass; beyond it is a gallery of dormant archives in different shapes, with a small robot cat with amber eyes at the cadet’s feet
The Dark Gallery: Kotomarket’s main database survived, but the stores around it went dark.

First — what a database is

The shop's program needs to remember what is happening: which products are left in stock, who ordered what, how much it cost. The place where a program stores this information and later retrieves it is called a database.

Data in a database is often stored in tables — almost like a spreadsheet: each row holds one record, such as a product or an order, and each column holds one property, such as a name or price. To get data from a table, the program sends the database a query in SQL. Kotomarket's main database runs on PostgreSQL — a program that stores tables and answers such queries.

Below is a real query to that database: it asks for the five most expensive products. A separate course covers SQL in detail, but you can read this query line by line:

  • SELECT name, price — show the name and price;
  • FROM products — take them from the products table;
  • ORDER BY price DESC — arrange them from highest price to lowest;
  • LIMIT 5 — keep the first five rows.

Click “Run” and see what the database returns.

After you run the query, the database returns a result that once again looks like a small table. Each row is a product it found, and the columns show its name and price.

The team asked you to put the more expensive products first, then keep only the first five rows. PostgreSQL does not permanently rearrange anything in the table itself: this order exists only in the result of this query.

This is an important foundation for the whole chapter. Tables and SQL work very well when it makes sense to ask for data in rows and columns. Next, you will meet tasks where a different way of storing or finding data is more convenient.

Where tables get awkward

A table works well when every record has the same properties: every order has a customer and an amount, every product has a name and a price. But an online store has plenty of tasks where this shape starts getting in the way.

  • Guest cart. While someone browses the site, the store remembers what is in their cart. If they leave and do not come back for half an hour, the cart can be forgotten. With a table, you would have to track the time separately and delete old rows.
  • View counter. Someone opens a product page, and the counter goes up by one. There may be thousands of events like this every minute while the main database is busy with orders. Sending every tiny update there is not always a good idea.
  • Product card. Headphones have battery life, jeans have sizes, and a book has a page count. If you create a separate column for every property, many products will be left with empty cells. That makes the table awkward to use, although the main database has other ways to store varying properties too — we will come back to them at the end of the chapter.

You end up with a strange workshop: orders are comfortable in neat rows and columns, a cart wants to disappear on its own after half an hour, product cards need different properties, and search receives a question in the customer's own words. You can force all these tasks into the same structure, but then you have to write extra logic and squeeze the data into a shape that does not suit it.

A useful picture here is a large warehouse with different zones. In one, things sit in strict compartments; in another, they are handed out by ticket number; in a third, cards may differ from one another; and another zone is designed to answer questions about connections quickly. Every zone has the same goal — storing and finding data — but each is organized differently inside.

The general name for these approaches is , meaning “not only SQL.” It is not one special database with a single design. The name covers different ways of storing data that are chosen for a particular task and often placed alongside PostgreSQL rather than instead of it.

Five approaches — each for a different job

There are many stores without familiar tables, but a few forms appear especially often. You do not need to memorize the names yet. First, look at the inconvenience you are trying to remove. And remember that these approaches can be combined: documents, for example, can also be spread across several servers.

When you need to fetch a record by a known name

For a cart or session, the program already knows which record it needs. It does not have to inspect the rest of the data: it can simply give the record's name and get whatever is stored under it.

Key → value (Redis) is like a coat check with numbered tickets. You hand over a ticket — this is called the key — and receive the item, which is the value. There is no need to go through the neighboring records.

This kind of store is often used as a cache — fast memory beside the main database. A cache keeps things that are requested often and can be fetched from the database again if they are lost.

A little further down, you will put a record like this into Redis through redis-cli.

When products have different properties

Trying to make one wide table for a book, jeans, and headphones quickly leaves you with lots of empty cells. You can create extra tables and rules, but for a simple product card that is already extra work.

In a document store, each record looks like its own card. MongoDB works this way. The card itself is called a document: for headphones it can store battery life, for jeans a size, and for a book a page count. The cards sit next to one another even though they contain different sets of properties.

It is like a folder of forms where everyone has the same first page, but after that each person fills in only the sections that apply to them. This kind of data is convenient when the records belong to the same subject but do not need to share exactly the same set of properties. MongoDB documents are often inspected by hand in MongoDB Compass.

When one server starts running out of room

A delivery service constantly receives new events about parcels. If the whole stream goes to one place, one server has to accept and save everything. As the stream grows, that single point gets more and more work.

Cassandra is designed for tasks where records are distributed across several servers. Each server stores its own share, so the whole stream does not have to pass through one place. A sorting center is a useful picture here: incoming boxes split across several lines, and each line handles its share of the work.

This approach is useful when a large stream of similar records keeps arriving for a long time: delivery events or technical events from a program, for example. You may encounter Cassandra as a standalone store or in the cloud service DataStax Astra DB.

When the customer types words, not a key

A key is simple as long as the program already knows the exact name of the record. A customer using a search box behaves differently: they do not know the product key and type ordinary words instead. And those words may not exactly match the name on the product card.

For this job, you use a search store. Elasticsearch receives a text query and looks for records that match its words. So a search for “warm blanket” can lead to a product named “Fleece blanket,” even though the customer did not type the exact name.

This kind of mechanism powers search across product catalogs, articles, documents, and program logs. With Elasticsearch, queries and results are often inspected through Kibana.

When the main question is “what is connected to what?”

An ordinary record answers “what do we know about this product?” very well. But recommendations ask a different question: which products are connected to the selected one, what is often bought together, and how does one record lead to another? When there are many such paths, the connections themselves become an important part of the data.

This is where a graph is useful. In Neo4j, you can picture records as circles and the connections between them as lines. It is like a map of acquaintances: the cards for each person matter, but the question “who knows whom?” is answered by the lines between the cards.

In a store, those lines can connect products that are bought together. Then you can move from the selected product to connected ones and use that for recommendations. You can inspect and query a Neo4j graph in Neo4j Browser.

QUERY: These stores have different habits. Redis quickly finds a record by key, MongoDB is perfectly happy with different cards, and a graph wants connections. We’ll meet them one at a time.

When there are many records, Cassandra distributes them across several servers.

Your first command

Closest to the gallery exit is Kotomarket's cache — a key → value store, Redis. The station has already restored power, so you can test it right away.

There are two ways to talk to Redis. A person types commands into a console: enter a command and immediately see the response. Redis's console is called redis-cli. The shop's program sends the same commands from its code — that is how this course will do it starting in Chapter 2.

In the cell below, cli('…') stands in for the console — a training function in the course that imitates redis-cli. Behind it is a training model of Redis running right in your browser. cli itself exists only here, but the command inside the single quotes is real: you can type it into any redis-cli. The product name inside the command is also wrapped in double quotes: that tells the console that «Пазл 1000 деталей» (1,000-piece puzzle) is one value, not three separate words.

In the output, the line that starts with redis and a greater-than sign repeats the command that was sent. That is how the course draws the console prompt; a real redis-cli shows the server address there instead. The store's response appears under the command. Click “Run” — we'll explain what each response means right after the cell. Every cell starts from the archive in its original state: no matter what one cell changes, the next one starts from the same point.

All the Python you need for this course

The code in the course cells is written in Python, but you do not need to learn the language beforehand. In the first chapters, you will only see a few things:

  • Python calls text in quotes a string: "Пазл 1000 деталей". It is just text, not a table row;
  • a name with parentheses means a function call — a ready-made action; the data for it goes inside the parentheses;
  • cli("…") — the course's training console: it sends a command to the store and prints the response, like redis-cli;
  • after the # sign comes a note for a person; Python ignores it;
  • clock.advance(60) moves the gallery's training clock forward by one minute: the dot chooses an action of the clock. Real Redis has no such clock.

Whenever something new appears, the course will first show it with an example.

Store a record, retrieve it, and ask how long it has left to live.
python · redis

Three things happened here.

  • SET stores a value under a key. The key here is session:42. The store itself came up with this name: the colon simply separates the word session from the customer number and has no special meaning to Redis. A session is one visit to the site: while customer 42 browses the pages, the store's program has recorded here that they are looking at a puzzle. Redis itself does not keep track of customers.
  • EX 1800 sets the record's time to live in seconds: half an hour. In English, this is called TTL.
  • GET fetches the value by key, while TTL shows how many seconds the record has left to live.

Now let’s match the commands with the responses in order. After saving the record, Redis replies “OK”: the record has been accepted. The next request returns a string in quotation marks — the very value stored under the key. The final response contains the number 1800 because immediately after saving, the full time to live is still remaining.

The “(integer)” note before the number only means that the response is a whole number. Redis is not showing a product, a customer, or a separate table row here: the program gave it a key and received the value connected to that key. That short path is exactly why you choose the “key → value” form.

A record that forgets itself

Now let’s test the feature that makes Redis so handy for records like this. In the next cell, the record is saved again first, so the half-hour countdown starts over. Then the line with clock.advance(1801) moves the gallery’s training clock forward by 1801 seconds: half an hour and one more second. After that, we ask about customer 42’s session again.

The time to live runs out — the record disappears without any delete command.
python · redis

The “(nil)” response means there is no longer a record under that key. Nobody ran a separate delete command — its time ran out, and Redis forgot the record by itself. With a table, you would have to write a program that, for example, looks for old rows once a minute and deletes them.

QUERY: The gallery clock had stopped at 18:00 — the minute the cache went dark. Now it is running again, and record lifetimes are expiring. Real Redis remembers the deadline as a moment on a normal clock: time keeps passing while the server is off, and after startup an expired record can no longer be read.

Nobody deleted the record: its time to live ran out, and Redis forgot it on its own.

🧪 Model: a real system works differently

Only the course’s training Redis lets us move time forward with one line. That way, you can immediately see what happens half an hour later instead of waiting. Real Redis uses the server’s normal clock.

Check yourself
A colleague suggests: “Let’s move the whole shop to NoSQL — it’s faster.” What would you say?
Practice: write the code
Customer 17 put a soft toy cat in their cart and went to make coffee. Remember the cart in Redis: key cart:17, value — Мягкая игрушка кот, time to live — 10 minutes.
python · redis

TermsWhat the new words mean

wordin plain language
databasea place where a program stores information and retrieves it later
tabledata in rows and columns: a row is a record, a column is a property
query, SQLa request to a database, and the language used to write such requests for tables
stores that are not limited to familiar tables and SQL
key and valuethe name of a record and what is stored under that name
cachefast memory next to the main database: things that are requested often and can be fetched from the database again
documenta card-like record with its own set of fields
grapha store of relationships between records
servera separate computer that runs a store
sessionone customer's visit to a website and the record of it while the customer is there
time to livehow many seconds a record lives before the store forgets it by itself
Key takeaways
  • A database stores information that a program needs. Data often lives in tables, and queries to it are written in SQL.
  • is a general name for different ways of storing data, not one replacement for PostgreSQL. They are placed alongside the main database when a particular task is better served by a different data structure.
  • We met five approaches: key → value for access by a known name, documents for records with different properties, distributing records across servers for a large stream, separate word-based search, and a graph for connections.
  • Redis stores “key → value” pairs and can delete a record on its own when its time to live runs out.

The Commandant asked why a store needs so many data stores. The answer is clear now: carts, search, delivery, and orders have different requirements for their data. In the next lesson, we will start looking at those requirements one by one.