NoSQL

NoSQL #

1. Что такое и для чего используется Redis? #

“Redis - это высокопроизводительная NoSQL база данных, которая работает в оперативной памяти (in-memory), обеспечивая минимальные задержки. Основное назначение Redis - хранение данных в виде ключ-значение "


2. Что представляют собой NoSQL-базы данных, какие задачи они решают и чем они принципиально отличаются от реляционных СУБД? #

Что такое NoSQL-базы данных #

NoSQL-базы данных — это класс баз данных, которые хранят и обрабатывают данные не только в классической табличной реляционной модели таблицы → строки → колонки.

NoSQL означает не обязательно «без SQL», а чаще — Not Only SQL, то есть «не только SQL». Это подход к хранению данных вне традиционной структуры реляционных БД.

Вместо строгой табличной модели NoSQL-БД могут использовать разные модели данных:

key-value       → ключ-значение
document        → документы JSON/BSON
wide-column     → широкие колонки / column families
graph           → графы: вершины и связи
time-series     → временные ряды
search/vector   → поиск, векторные данные

AWS в своей документации описывает NoSQL как класс БД с разными моделями данных: key-value, document, graph и column-oriented, оптимизированными под производительность и масштабирование.

Пример реляционной модели #

В реляционной БД данные обычно нормализованы по таблицам:

users
-----
id | username | email

orders
------
id | user_id | total_price

order_items
-----------
id | order_id | product_id | count

Связи строятся через:

primary key
foreign key
JOIN
constraints
transactions

Например:

SELECT users.username, orders.total_price
FROM users
JOIN orders ON orders.user_id = users.id;

Реляционная БД хорошо подходит, когда данные имеют чёткую структуру и между ними важны строгие связи.

Пример document NoSQL #

В document database данные могут храниться одним документом:

{
  "id": 1,
  "username": "alex",
  "email": "alex@example.com",
  "orders": [
    {
      "id": 100,
      "total_price": 2500,
      "items": [
        {"product_id": 10, "count": 2},
        {"product_id": 15, "count": 1}
      ]
    }
  ]
}

Здесь данные не разнесены по нескольким таблицам. Вся нужная структура может лежать внутри одного документа.

Это удобно, когда приложение часто читает объект целиком: например, профиль пользователя, настройки, корзину, карточку товара, контентную страницу.

Какие задачи решают NoSQL-БД #

NoSQL-БД появились как ответ на задачи, где классическая реляционная модель может быть неудобной или тяжёлой.

1. Гибкая структура данных #

В реляционной БД обычно заранее задаётся схема:

таблица users
колонка username VARCHAR
колонка email VARCHAR
колонка created_at TIMESTAMP

Чтобы добавить новое поле, часто нужна миграция.

В document NoSQL разные документы могут иметь разную структуру:

{
  "username": "alex",
  "email": "alex@example.com"
}
{
  "username": "john",
  "email": "john@example.com",
  "avatar_url": "https://example.com/avatar.png",
  "settings": {
    "theme": "dark"
  }
}

Это удобно, когда данные часто меняются или заранее неизвестна полная структура объекта.

2. Высокая горизонтальная масштабируемость #

Многие NoSQL-БД проектировались под распределённое хранение данных на нескольких узлах.

Например, Apache Cassandra официально описывается как распределённая NoSQL-БД с wide-column моделью и eventual consistency.

То есть данные можно распределять по нескольким серверам:

node 1 → часть данных
node 2 → часть данных
node 3 → часть данных
node 4 → часть данных

Это полезно для систем с большим объёмом данных и высокой нагрузкой:

логи
события
метрики
социальные сети
чат-системы
IoT
аналитика
high-load backend

3. Быстрый доступ по ключу #

Key-value базы хранят данные по принципу:

ключ → значение

Пример:

"user:100:session" → "{token: ..., expires_at: ...}"
"product:50:stock" → "142"

Redis, например, предоставляет разные структуры данных: strings, hashes, lists, sets, sorted sets, streams, time series и другие.

Такие БД часто используют для:

кэша
сессий
rate limiting
очередей
временных токенов
быстрых счётчиков
real-time данных

4. Хранение слабоструктурированных данных #

NoSQL хорошо подходит, когда данные не укладываются в строгую табличную модель.

Например:

JSON-документы
логи
события
пользовательские настройки
каталоги товаров с разными характеристиками
контентные страницы
анкеты с динамическими полями

В реляционной БД для таких данных иногда приходится делать много nullable-колонок или использовать отдельные таблицы свойств.

Например:

product_id | property_name | property_value

Это может усложнять запросы и поддержку.

5. Работа с графовыми связями #

Graph-базы удобны, когда важны не только объекты, но и связи между ними.

Пример:

Пользователь → подписан на → Пользователь
Пользователь → лайкнул → Пост
Пользователь → купил → Товар
Товар → похож на → Товар

Такая модель подходит для:

социальных графов
рекомендательных систем
антифрод-систем
графов зависимостей
маршрутов
сетевых связей

Основные типы NoSQL-БД #

1. Key-value #

Данные хранятся как ключ и значение.

session:abc123 → user_id=10

Примеры задач:

кэш
сессии
токены
rate limiting
быстрые настройки

Пример БД:

Redis
DynamoDB в key-value сценариях

2. Document database #

Данные хранятся как документы, обычно JSON/BSON.

{
  "id": 1,
  "title": "Phone",
  "price": 900,
  "attributes": {
    "color": "black",
    "memory": "256GB"
  }
}

Примеры задач:

каталоги товаров
профили пользователей
CMS
настройки
анкеты
контент

Примеры БД:

MongoDB
CouchDB
Amazon DocumentDB

3. Wide-column #

Данные хранятся в строках и column families, но структура более гибкая, чем в классической реляционной таблице.

AWS описывает wide-column store как NoSQL-БД, где данные организованы в column families, строки имеют уникальный row key, а колонки могут различаться.

Примеры задач:

большие логи
события
метрики
аналитика
данные с высокой записью

Примеры БД:

Apache Cassandra
ScyllaDB
HBase

4. Graph database #

Данные представлены как граф:

вершины → объекты
рёбра   → связи между объектами

Примеры задач:

социальные сети
рекомендации
антифрод
поиск связей
графы зависимостей

Примеры БД:

Neo4j
Amazon Neptune
ArangoDB

Чем NoSQL принципиально отличается от реляционных СУБД #

1. Модель данных #

Реляционная БД:

таблицы
строки
колонки
связи
JOIN
строгая схема

NoSQL:

документы
ключ-значение
графы
wide-column
гибкая схема
часто денормализация

Главное отличие: реляционная БД строится вокруг таблиц и связей, а NoSQL-БД обычно выбирает модель хранения под конкретный тип доступа к данным.

2. Схема данных #

Реляционная БД обычно требует заранее определённую схему:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE
);

NoSQL часто допускает более гибкую структуру:

{
  "email": "test@example.com"
}
{
  "email": "test@example.com",
  "phone": "+994...",
  "settings": {
    "theme": "dark"
  }
}

Но это не значит, что в NoSQL схема вообще не нужна. Просто часто она контролируется не самой БД, а приложением.

3. Нормализация против денормализации #

В реляционных БД данные часто нормализуют:

users отдельно
orders отдельно
products отдельно
order_items отдельно

Плюс:

меньше дублирования
строгие связи
удобные JOIN

В NoSQL часто данные денормализуют:

{
  "user_id": 1,
  "username": "alex",
  "orders": [
    {
      "id": 100,
      "items": [
        {"name": "Keyboard", "price": 100}
      ]
    }
  ]
}

Плюс:

быстро читать объект целиком
меньше JOIN
удобно масштабировать чтение

Минус:

дублирование данных
сложнее обновлять связанные данные
больше ответственности на уровне приложения

4. JOIN и связи #

Реляционные БД хорошо работают со связями:

SELECT *
FROM users
JOIN orders ON users.id = orders.user_id
JOIN order_items ON orders.id = order_items.order_id;

Во многих NoSQL-БД JOIN либо отсутствует, либо ограничен, либо не является основной моделью работы.

Поэтому в NoSQL часто заранее проектируют данные под конкретные запросы:

как приложение будет читать данные?
какие запросы самые частые?
какие данные выгоднее хранить вместе?

5. Транзакции и консистентность #

Реляционные СУБД традиционно сильны в ACID-транзакциях:

Atomicity    → всё или ничего
Consistency → данные остаются валидными
Isolation   → параллельные транзакции не ломают друг друга
Durability  → данные сохраняются после commit

NoSQL-БД часто делают другой компромисс:

больше масштабируемость
больше доступность
выше скорость записи
но иногда слабее строгая консистентность

Например, Cassandra официально указывает eventual consistency semantics.

Eventual consistency означает:

данные на разных узлах могут стать одинаковыми не мгновенно,
а через некоторое время

Это не плохо само по себе. Это компромисс для распределённых систем.

6. Масштабирование #

Реляционные БД часто масштабируют вертикально:

больше CPU
больше RAM
быстрее диск
сильнее сервер

Хотя современные реляционные БД тоже умеют репликацию, шардинг и кластеры, исторически их сложнее горизонтально масштабировать для некоторых типов нагрузки.

NoSQL-БД часто изначально проектируются под горизонтальное масштабирование:

добавили node 4
добавили node 5
данные распределились по кластеру

Это одна из причин популярности NoSQL в high-load системах.

Сравнение кратко #

Критерий              SQL / RDBMS                 NoSQL
---------------------------------------------------------------------
Модель данных          таблицы                     документы, ключи, графы, колонки
Схема                  строгая                     часто гибкая
Связи                  JOIN, FK                    часто денормализация
Транзакции             сильная сторона             зависит от конкретной БД
Масштабирование        чаще вертикальное           часто горизонтальное
Подходит для           строгих структур            гибких/больших/распределённых данных
Запросы                SQL                         API/язык конкретной БД
Консистентность        обычно строгая              может быть eventual consistency

Когда лучше использовать реляционную БД #

Реляционная БД обычно лучше, если:

данные хорошо структурированы
важны транзакции
важны связи между сущностями
нужны JOIN
нужна строгая целостность данных
есть финансовые операции
есть сложная отчётность

Примеры:

банковские операции
заказы и платежи
складской учёт
CRM
ERP
админ-панели
типичный backend с users/orders/payments

Для большинства стандартных backend-проектов PostgreSQL часто будет более правильным базовым выбором, чем NoSQL.

Когда лучше использовать NoSQL #

NoSQL подходит, если:

данные плохо укладываются в таблицы
схема часто меняется
нужно хранить большие объёмы событий
нужно быстро читать/писать по ключу
нужно горизонтальное масштабирование
нужно хранить JSON-подобные документы
нужно работать с графовыми связями

Примеры:

кэш и сессии → Redis
каталог товаров → MongoDB / DocumentDB
логи и события → Cassandra / ClickHouse-like подходы
социальные связи → Graph DB
метрики и временные ряды → Time-series DB

Важный момент #

NoSQL не является «заменой SQL во всём».

Правильнее так:

SQL    → когда важны структура, связи, транзакции, целостность
NoSQL  → когда важны гибкость, масштабирование, быстрый доступ под конкретный сценарий

В реальных проектах их часто используют вместе:

PostgreSQL → основные бизнес-данные
Redis      → кэш, сессии, rate limiting
MongoDB    → гибкие документы
Elasticsearch/OpenSearch → поиск
Cassandra  → большие потоки событий

Итог #

NoSQL-БД — это нереляционные базы данных, которые используют не только табличную модель, а разные способы хранения данных: ключ-значение, документы, графы, wide-column и другие.

Они решают задачи:

гибкой структуры данных
горизонтального масштабирования
быстрого доступа по ключу
хранения больших объёмов событий
работы со слабоструктурированными данными
оптимизации под конкретные запросы приложения

Принципиальное отличие от реляционных СУБД:

реляционная БД проектируется вокруг таблиц, связей и строгой схемы;
NoSQL-БД проектируется вокруг конкретной модели доступа к данным и масштабирования.


3. Какими свойствами классических реляционных БД жертвуют NoSQL-системы и ради каких преимуществ? #

NoSQL-системы часто жертвуют частью свойств классических реляционных БД:

строгой схемой
нормализацией
JOIN
foreign key
сильными ACID-транзакциями
строгой консистентностью
универсальностью SQL-запросов

Ради чего:

горизонтального масштабирования
высокой доступности
быстрой записи/чтения
гибкой структуры данных
удобной работы с большими объёмами данных
лучшей работы в распределённых системах

Главный компромисс:

меньше строгих гарантий и универсальности
→ больше масштабируемости, гибкости и скорости под конкретный сценарий

1. Жертвуют строгой схемой #

В реляционной БД схема обычно заранее задана:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    age INTEGER
);

БД сама контролирует:

какие поля есть
какие типы у полей
какие поля обязательны
какие ограничения действуют

В NoSQL, особенно в document databases, структура часто гибче:

{
  "username": "alex",
  "email": "alex@example.com"
}
{
  "username": "john",
  "email": "john@example.com",
  "settings": {
    "theme": "dark",
    "language": "ru"
  }
}

То есть разные документы могут иметь разный набор полей.

Ради чего жертвуют:

легче менять структуру данных
удобнее хранить JSON-подобные объекты
меньше миграций схемы
удобнее работать с динамическими полями

Минус:

валидация часто переезжает из БД в приложение
легче получить грязные или несовместимые данные
сложнее поддерживать единый формат данных

AWS описывает NoSQL-БД как системы с разными моделями данных — key-value, document, graph, column — оптимизированными под производительность и масштабирование, в отличие от реляционной модели со строгой схемой таблиц и связей.

2. Жертвуют нормализацией #

В реляционной БД данные обычно нормализуют:

users
orders
order_items
products

Данные не дублируются без необходимости, а связи строятся через ключи.

В NoSQL часто используют денормализацию:

{
  "user_id": 1,
  "username": "alex",
  "orders": [
    {
      "id": 100,
      "items": [
        {
          "product_id": 10,
          "name": "Keyboard",
          "price": 100
        }
      ]
    }
  ]
}

Здесь часть данных может дублироваться, зато объект удобно читать целиком.

Ради чего жертвуют:

быстрое чтение без JOIN
меньше запросов к БД
данные сразу лежат в форме, удобной приложению
легче масштабировать чтение

Минус:

дублирование данных
сложнее обновлять одни и те же данные в разных местах
может появиться рассинхронизация

3. Жертвуют JOIN и сложными связями #

Реляционные БД хорошо подходят для запросов со связями:

SELECT users.email, orders.total_price
FROM users
JOIN orders ON orders.user_id = users.id
JOIN order_items ON order_items.order_id = orders.id;

Во многих NoSQL-БД JOIN либо отсутствует, либо не является основной моделью работы.

Вместо этого данные проектируют под конкретные запросы:

как будем читать данные?
какой объект нужен приложению целиком?
какие данные выгодно хранить рядом?

Ради чего жертвуют:

быстрее чтение по заранее известному паттерну
проще распределять данные по узлам
меньше дорогих межтабличных операций

Минус:

сложнее делать ad-hoc аналитику
сложнее менять паттерны запросов
часть логики связывания данных переходит в приложение

4. Жертвуют foreign key и строгой ссылочной целостностью #

В реляционной БД можно сказать:

user_id INTEGER REFERENCES users(id)

БД не даст создать заказ для несуществующего пользователя.

Во многих NoSQL-БД таких строгих foreign key-ограничений нет. Приложение само должно следить, чтобы ссылки были валидными.

Ради чего жертвуют:

проще распределять данные
быстрее запись
меньше координации между узлами
меньше глобальных блокировок и проверок

Минус:

можно получить ссылку на несуществующий объект
сложнее гарантировать целостность данных
нужны дополнительные проверки в коде

5. Жертвуют сильными ACID-транзакциями #

Классическая реляционная БД сильна в транзакциях:

Atomicity    → всё или ничего
Consistency → данные остаются валидными
Isolation   → транзакции не мешают друг другу
Durability  → commit сохраняется

PostgreSQL, например, описывает транзакцию как набор операций, который выполняется как единая операция «всё или ничего»: промежуточные состояния не видны другим транзакциям, а при сбое незавершённые изменения не применяются.

Многие NoSQL-БД исторически ослабляли транзакционные гарантии, особенно для операций между несколькими документами, партициями или узлами.

Ради чего жертвуют:

выше скорость записи
меньше задержки
лучше масштабирование
лучше доступность при распределённой архитектуре

Минус:

сложнее делать операции "всё или ничего"
сложнее поддерживать строгую бизнес-целостность
часть компенсационной логики приходится писать вручную

Важная оговорка: современные NoSQL-БД не всегда полностью отказываются от транзакций. Например, MongoDB поддерживает распределённые транзакции между несколькими коллекциями, базами и документами, когда нужна атомарность read/write-операций. Но в распределённых NoSQL-системах такие гарантии обычно имеют цену в виде задержек, сложности и потери части преимуществ масштабирования.

6. Жертвуют строгой консистентностью #

В классической реляционной БД после COMMIT обычно ожидается, что следующие чтения увидят актуальное состояние данных.

В распределённых NoSQL-БД часто возможна eventual consistency:

запись уже принята одним узлом
но другие реплики могут увидеть её не мгновенно
через некоторое время данные синхронизируются

Например, Cassandra использует настраиваемые уровни консистентности: для каждой операции можно выбирать компромисс между консистентностью и доступностью.

Ради чего жертвуют:

высокая доступность
работа при сетевых сбоях
низкая задержка
масштабирование на много узлов
геораспределённость

Минус:

можно временно прочитать устаревшие данные
разные пользователи могут увидеть разные версии
нужно учитывать eventual consistency в бизнес-логике

7. Жертвуют универсальностью запросов #

SQL даёт мощный универсальный язык:

JOIN
GROUP BY
HAVING
подзапросы
агрегации
оконные функции
сложные фильтры

В NoSQL запросы чаще зависят от конкретной БД и модели данных.

Например:

Redis       → доступ по ключу и структурам данных
MongoDB     → запросы по документам
Cassandra   → запросы вокруг partition key
Neo4j       → графовые запросы

Ради чего жертвуют:

лучше оптимизация под конкретный паттерн доступа
проще горизонтальное масштабирование
быстрее типовые операции

Минус:

сложнее делать произвольные запросы
нужно заранее проектировать модель под чтение
миграция между NoSQL-БД сложнее

8. Жертвуют частью изоляции и предсказуемости конкурентного доступа #

В реляционных БД есть развитые уровни изоляции транзакций:

READ COMMITTED
REPEATABLE READ
SERIALIZABLE

В NoSQL всё зависит от конкретной системы. Где-то есть сильные гарантии на уровне одного документа, но слабее гарантии между несколькими объектами. Где-то консистентность настраивается на уровне операции.

Ради чего жертвуют:

меньше блокировок
выше throughput
быстрее параллельные записи
лучше работа в кластере

Минус:

сложнее рассуждать о гонках данных
нужны idempotency, versioning, compare-and-set, optimistic locking

Связь с CAP-теоремой #

Для распределённых систем есть фундаментальный компромисс:

Consistency  → все узлы видят одинаковые актуальные данные
Availability → система отвечает на запросы
Partition tolerance → система переживает сетевые разделения

При сетевом разделении нельзя одновременно идеально сохранить и строгую консистентность, и доступность. Поэтому разные NoSQL-системы выбирают разные компромиссы:

Cassandra-like подход:
больше доступность и масштабирование
консистентность может быть eventual/tunable

MongoDB-like подход:
больше документная модель и удобство разработки
часть транзакционных гарантий доступна, но с ценой

Redis-like подход:
очень быстрый доступ по ключу
но это не замена полноценной реляционной модели

Сравнение #

Чем жертвуют                 Ради чего
---------------------------------------------------------------------
Строгая схема                Гибкость структуры данных
Нормализация                 Быстрое чтение готовых объектов
JOIN                          Масштабируемость и предсказуемое чтение
Foreign key                  Быстрая запись и слабая связанность данных
Сильные ACID-транзакции      Производительность и распределённость
Строгая консистентность      Доступность и низкая задержка
Универсальный SQL            Оптимизация под конкретный тип нагрузки
Глобальная целостность       Горизонтальное масштабирование

Пример на практике #

Для банковского перевода лучше реляционная БД:

списать деньги со счёта A
зачислить деньги на счёт B
записать транзакцию
всё должно быть атомарно

Здесь важнее:

ACID
строгая консистентность
изоляция транзакций
целостность данных

Для хранения событий кликов или логов может быть лучше NoSQL/wide-column/event storage:

миллионы событий
быстрая запись
горизонтальное масштабирование
допустима небольшая задержка синхронизации

Здесь важнее:

throughput
availability
scalability
низкая задержка записи

Итог #

NoSQL-системы обычно жертвуют не «надёжностью вообще», а частью классических реляционных гарантий:

строгой схемой
нормализацией
JOIN
ссылочной целостностью
сильными транзакциями
строгой консистентностью
универсальностью SQL-запросов

Ради преимуществ:

гибкая модель данных
высокая скорость
горизонтальное масштабирование
работа на большом объёме данных
высокая доступность
удобство хранения документов, событий, кэша, графов

Главная мысль:

SQL-БД чаще выбирают, когда важны связи, транзакции и строгая целостность.

NoSQL чаще выбирают, когда важны масштабирование, гибкость модели данных и скорость под конкретный сценарий доступа.


4. Как ограничить (блокировать) доступ к базе данных с помощью Redis? #

Главное уточнение #

Redis сам по себе не «закрывает» доступ к PostgreSQL/MySQL напрямую. Обычно Redis ставят перед обращением к БД на уровне приложения:

request → приложение → проверка Redis → разрешить/запретить запрос к БД

То есть Redis используется как быстрый внешний механизм контроля:

rate limit
temporary block
distributed lock
circuit breaker
cache

Но настоящую защиту БД всё равно нужно делать отдельно:

права пользователя БД
сетевые ACL/firewall
закрытый порт БД
минимальные privileges
connection pool limit

Redis — это не security boundary для самой БД, а прикладной ограничитель перед обращением к ней.

1. Временная блокировка пользователя #

Самый простой вариант — хранить в Redis ключ блокировки:

db:block:user:123 → 1
TTL: 300 секунд

Пока ключ существует — пользователю запрещён доступ к операциям, которые ходят в БД.

Redis SET поддерживает NX, чтобы установить ключ только если его ещё нет, и EX/PX, чтобы сразу задать время жизни ключа.

Пример:

import redis

redis_client = redis.Redis(
    host="localhost",
    port=6379,
    decode_responses=True,
)


def block_user_db_access(user_id: int, ttl_seconds: int = 300) -> None:
    redis_client.set(
        name=f"db:block:user:{user_id}",
        value="1",
        ex=ttl_seconds,
    )


def is_user_blocked(user_id: int) -> bool:
    return redis_client.exists(f"db:block:user:{user_id}") == 1


def get_user_data(user_id: int):
    if is_user_blocked(user_id):
        raise PermissionError("DB access temporarily blocked")

    # Здесь уже обращение к реальной БД
    return user_repository.get_by_id(user_id)

Схема:

1. Проверяем Redis
2. Если ключ блокировки есть → не идём в БД
3. Если ключа нет → выполняем запрос к БД

2. Ограничение частоты запросов к БД #

Если задача — не заблокировать полностью, а ограничить частоту обращений, используют rate limiting.

Например:

не больше 100 запросов к БД в минуту на пользователя

В Redis это часто делают через счётчик:

db:rate:user:123:2026-06-26T14:30 → 42
TTL: 60 секунд

Redis официально описывает pattern rate limiter через INCR и EXPIRE: счётчик увеличивается при каждом запросе, а ключ автоматически удаляется после истечения окна времени.

Пример:

import time


def allow_db_request(user_id: int, limit: int = 100, window: int = 60) -> bool:
    current_window = int(time.time()) // window
    key = f"db:rate:user:{user_id}:{current_window}"

    pipe = redis_client.pipeline()
    pipe.incr(key)
    pipe.expire(key, window)
    current_count, _ = pipe.execute()

    return current_count <= limit


def get_user_notifications(user_id: int):
    if not allow_db_request(user_id, limit=100, window=60):
        raise PermissionError("Too many DB requests")

    return notification_repository.list_by_user(user_id)

Логика:

1-й запрос  → counter = 1  → разрешить
...
100-й запрос → counter = 100 → разрешить
101-й запрос → counter = 101 → заблокировать

Для атомарности INCR и EXPIRE обычно выполняют вместе через transaction/pipeline или Lua-скрипт. В документации Redis для rate limiting отдельно указывается использование INCR вместе с EXPIRE внутри MULTI/EXEC, чтобы операции были атомарными.

3. Блокировка конкретной операции через distributed lock #

Иногда нужно не запретить пользователю доступ к БД вообще, а не дать нескольким процессам одновременно выполнить одну критичную операцию.

Например:

не запускать два пересчёта баланса одновременно
не обрабатывать один заказ двумя воркерами
не делать параллельную миграцию/синхронизацию
не создавать один и тот же ресурс дважды

Тогда используют Redis lock:

lock:order:1001 → random_token
TTL: 30 секунд

Redis описывает distributed locks как паттерн для взаимного исключения, когда разные процессы работают с общим ресурсом.

Пример:

import uuid


def acquire_lock(lock_key: str, ttl_seconds: int = 30) -> str | None:
    token = str(uuid.uuid4())

    acquired = redis_client.set(
        name=lock_key,
        value=token,
        nx=True,
        ex=ttl_seconds,
    )

    if acquired:
        return token

    return None

Использование:

def recalculate_order(order_id: int):
    lock_key = f"lock:order:{order_id}"
    token = acquire_lock(lock_key, ttl_seconds=30)

    if token is None:
        raise RuntimeError("Operation is already running")

    try:
        return order_service.recalculate(order_id)
    finally:
        release_lock(lock_key, token)

Но удалять lock простым DEL lock_key опасно. За время выполнения TTL мог истечь, другой процесс мог взять новый lock, а старый процесс случайно удалит чужую блокировку.

Правильнее удалять lock только если значение совпадает с твоим token:

RELEASE_LOCK_SCRIPT = """
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
"""


def release_lock(lock_key: str, token: str) -> None:
    redis_client.eval(RELEASE_LOCK_SCRIPT, 1, lock_key, token)

4. Блокировка после нескольких неудачных попыток #

Типовой сценарий:

5 неудачных попыток → заблокировать доступ на 15 минут

Например, для защиты endpoint’а, который активно ходит в БД:

def register_failed_attempt(user_id: int) -> None:
    key = f"db:fail:user:{user_id}"
    attempts = redis_client.incr(key)

    if attempts == 1:
        redis_client.expire(key, 900)

    if attempts >= 5:
        redis_client.set(
            name=f"db:block:user:{user_id}",
            value="1",
            ex=900,
        )


def check_db_access(user_id: int) -> None:
    if redis_client.exists(f"db:block:user:{user_id}"):
        ttl = redis_client.ttl(f"db:block:user:{user_id}")
        raise PermissionError(f"Blocked for {ttl} seconds")

Команда TTL позволяет узнать, сколько времени осталось до истечения ключа; Redis возвращает время жизни ключа в секундах.

5. Circuit breaker перед БД #

Redis можно использовать как флаг: «БД перегружена, временно не пускаем тяжёлые операции».

Например:

db:circuit:heavy_queries → open
TTL: 60 секунд

Код:

def check_heavy_queries_allowed() -> None:
    if redis_client.exists("db:circuit:heavy_queries"):
        raise RuntimeError("Heavy DB queries are temporarily disabled")


def run_heavy_report():
    check_heavy_queries_allowed()

    return report_repository.build_heavy_report()

Так можно временно отключить:

тяжёлые отчёты
массовые выгрузки
дорогие фильтры
частые пересчёты

Но обычные лёгкие запросы оставить доступными.

6. Кэширование, чтобы реже ходить в БД #

Это не блокировка, но тоже способ ограничить нагрузку на БД.

import json


def get_user_profile(user_id: int):
    cache_key = f"user:profile:{user_id}"

    cached = redis_client.get(cache_key)
    if cached is not None:
        return json.loads(cached)

    user = user_repository.get_by_id(user_id)

    redis_client.set(
        cache_key,
        json.dumps(user),
        ex=300,
    )

    return user

Схема:

1. Проверяем Redis cache
2. Если данные есть → БД не трогаем
3. Если данных нет → идём в БД
4. Результат кладём в Redis

Это особенно полезно для:

профилей
настроек пользователя
справочников
часто читаемых данных
результатов тяжёлых запросов

Где лучше ставить проверку #

Проверку Redis лучше ставить не внутри repository, а выше:

API endpoint / middleware / dependency
service layer
use-case layer

Плохой вариант:

repository сам решает, можно ли ходить в БД

Лучше:

service проверяет Redis
service решает, можно ли выполнять операцию
repository только работает с БД

Пример:

class UserService:
    def __init__(self, user_repo, redis_client):
        self.user_repo = user_repo
        self.redis = redis_client

    def get_user(self, user_id: int):
        if self.redis.exists(f"db:block:user:{user_id}"):
            raise PermissionError("DB access blocked")

        return self.user_repo.get_by_id(user_id)

Так архитектура чище:

Redis-политика доступа → service
SQL-запросы            → repository

Что важно не забыть #

1. Всегда ставить TTL #

Плохой вариант:

redis_client.set("db:block:user:123", "1")

Если забыть удалить ключ, пользователь может остаться заблокированным навсегда.

Лучше:

redis_client.set("db:block:user:123", "1", ex=300)

2. Не использовать Redis как единственную защиту БД #

Redis-проверка может быть обойдена, если кто-то имеет прямой доступ к БД.

Поэтому для реальной безопасности нужны:

закрытый доступ к БД из интернета
отдельный DB-user для приложения
минимальные права
сетевые ограничения
connection pool limits
аудит запросов

3. Учитывать отказ Redis #

Нужно заранее решить поведение:

Redis недоступен → запрещать запросы к БД?
Redis недоступен → разрешать запросы к БД?
Redis недоступен → разрешать только критичные операции?

Для security-sensitive операций обычно выбирают fail-closed:

Redis не отвечает → доступ запрещён

Для обычного кэша чаще выбирают fail-open:

Redis не отвечает → идём напрямую в БД

Итог #

Redis можно использовать для ограничения доступа к БД как быстрый управляющий слой перед БД:

temporary block → ключ db:block:user:{id} с TTL
rate limit      → INCR + EXPIRE
distributed lock → SET key value NX EX
circuit breaker → флаг db:circuit:* с TTL
cache           → уменьшить число обращений к БД

Главная схема:

перед запросом в БД проверяем Redis
если Redis говорит "нельзя" → запрос к БД не выполняем
если Redis говорит "можно"  → выполняем запрос к БД

Но Redis не заменяет права доступа самой БД. Он ограничивает обращения на уровне приложения, а не защищает базу от прямого подключения.