Какие данные стоит хранить в базе данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие данные хранить в базе данных
Это стратегический вопрос архитектуры: что и где хранить. Рассмотрю критерии и best practices.
Основной принцип
База данных — это не свалка, а источник истины (source of truth)
Хранить в БД нужно:
- Данные, которые являются источником истины
- Данные, которые используются повторно
- Данные, которые должны пережить перезагрузку приложения
- Данные, которые нужны для аудита и истории
Категория 1: Бизнес-критичные данные (ДА в БД)
Что хранить:
✅ Пользователи и аккаунты
✅ Заказы и покупки
✅ Финансовые транзакции
✅ Контрактные данные
✅ Производство и инвентарь
✅ История изменений (audit log)
✅ Персональные данные (ПДн)
Почему:
- Данные должны пережить сбой
- Нужна атомарность операций
- Требуется консистентность
- Нужна история для аудита
- GDPR и другие требования
Пример:
class User(Base):
__tablename__ = 'users'
id = Column(UUID, primary_key=True)
email = Column(String(255), unique=True) # ДА
password_hash = Column(String(255)) # ДА
full_name = Column(String(255)) # ДА
phone = Column(String(20)) # ДА
created_at = Column(DateTime) # ДА
updated_at = Column(DateTime) # ДА
last_login_at = Column(DateTime) # ДА (для истории)
class Order(Base):
__tablename__ = 'orders'
id = Column(UUID, primary_key=True)
user_id = Column(UUID, ForeignKey('users.id'))
total_amount = Column(Decimal(10, 2)) # ДА (финансовые данные)
status = Column(String(50)) # ДА
items = relationship("OrderItem") # ДА
created_at = Column(DateTime) # ДА
completed_at = Column(DateTime) # ДА
Категория 2: Рабочие данные (ЗАВИСИТ от случая)
Session / Authentication токены:
❌ В БД (для каждого запроса нужно делать query)
✅ В памяти/Redis (быстрый доступ, можно потерять)
✅ JWT токены (self-contained, не нужен доступ в БД)
# Если все же в БД, то с TTL
class AuthToken(Base):
__tablename__ = 'auth_tokens'
token = Column(String(500), primary_key=True)
user_id = Column(UUID)
created_at = Column(DateTime)
expires_at = Column(DateTime) # Удалять старые записи
Кэш данных:
❌ В БД (медленно)
✅ В Redis/Memcached (быстро)
✅ В БД с индексом если часто читается
❌ В Redis если это source of truth
# Пример: кэш популярных товаров
# В Redis: быстрый доступ (10ms)
# В БД: source of truth
Временные данные (OTP, Email verification):
❌ В БД без TTL (груз растет)
✅ В Redis с TTL 15 минут
✅ В БД с автоматической очисткой старых
class EmailVerification(Base):
__tablename__ = 'email_verifications'
code = Column(String(6), primary_key=True)
user_id = Column(UUID)
created_at = Column(DateTime)
expires_at = Column(DateTime) # 10 минут
is_used = Column(Boolean, default=False)
Категория 3: Метаданные и логи (МОЖЕТ быть отдельная таблица)
Логи приложения:
❌ В реляционной БД (она замедляется)
✅ В Elasticsearch/Log aggregation (специализированная система)
✅ В отдельной колонке с JSONB (PostgreSQL)
✅ В отдельной таблице с TTL
# Пример: система логирования
class ApplicationLog(Base):
__tablename__ = 'logs'
id = Column(UUID, primary_key=True)
timestamp = Column(DateTime, index=True)
level = Column(String(10)) # INFO, WARNING, ERROR
message = Column(Text)
context = Column(JSON) # Метаданные
__table_args__ = (
Index('idx_timestamp_level', 'timestamp', 'level'),
)
# Или лучше использовать Elasticsearch
Аудит (audit trail):
✅ В БД с отдельной таблицей (должен быть immutable)
class AuditLog(Base):
__tablename__ = 'audit_logs'
id = Column(UUID, primary_key=True)
entity_type = Column(String(50)) # 'User', 'Order'
entity_id = Column(UUID)
action = Column(String(50)) # 'CREATE', 'UPDATE', 'DELETE'
old_values = Column(JSON)
new_values = Column(JSON)
user_id = Column(UUID) # Кто сделал
timestamp = Column(DateTime, default=datetime.utcnow)
ip_address = Column(String(45))
# Никогда не удаляем эти записи!
Категория 4: Счетчики и аналитика (ОТДЕЛЬНО)
Счетчики просмотров, рейтинги:
❌ UPDATE counter = counter + 1 на каждый просмотр (тяжело)
✅ В Redis счетчик, периодически синхронизировать в БД
# Redis
REDIS: incr("views:product:123")
# Каждый час асинхронно синхронизируем
def sync_counters():
for product_id in redis.keys("views:product:*"):
views = redis.get(product_id)
db.update(Product).set(view_count=views).where(...)
Аналитика и статистика:
❌ В основной БД (замедляет)
✅ В separate analytics DB (Data Warehouse)
✅ В BigQuery, Redshift, Snowflake
✅ В отдельных таблицах с материализованными представлениями
# Отдельное хранилище для аналитики
CREATE TABLE analytics.daily_stats AS
SELECT
DATE(created_at) as date,
COUNT(*) as orders_count,
SUM(total_amount) as revenue
FROM orders
GROUP BY DATE(created_at);
Категория 5: Файлы и большие объекты (НЕ в БД)
Файлы и медиа:
❌ BLOB/CLOB в реляционной БД
- Замедляет backup
- Затрудняет масштабирование
- Плохо сжимается
✅ Object Storage (S3, GCS, Azure Blob)
- Быстро
- Масштабируемо
- Дешево
# В БД только ссылка
class UserAvatar(Base):
__tablename__ = 'user_avatars'
user_id = Column(UUID, primary_key=True)
s3_url = Column(String(500)) # ✅ только ссылка
s3_key = Column(String(255))
uploaded_at = Column(DateTime)
file_size = Column(Integer)
mime_type = Column(String(50))
Документы (PDF, Word):
❌ Хранить файл в DB
✅ Хранить в S3, ссылку в DB
# Структура
S3: /invoices/2026/03/invoice-12345.pdf
DB: {
"invoice_id": "inv_12345",
"s3_key": "invoices/2026/03/invoice-12345.pdf",
"file_size": 256000,
"created_at": "2026-03-28"
}
Категория 6: Настройки и конфигурация (ЗАВИСИТ)
Статические настройки:
❌ В БД (если они не меняются в runtime)
✅ В конфиг файлах
✅ В env переменных
# config.yaml
app:
name: "Taxi App"
version: "2.5.0"
max_connections: 1000
Динамические настройки (Feature Flags):
✅ В БД с кэшированием
class FeatureFlag(Base):
__tablename__ = 'feature_flags'
name = Column(String(100), primary_key=True)
enabled = Column(Boolean, default=False)
description = Column(String(500))
rollout_percentage = Column(Integer) # 0-100%
updated_at = Column(DateTime)
Категория 7: Чувствительные данные (ДОЛЖНЫ быть в БД с защитой)
Пароли:
✅ Хэшированные в БД (НИКОГДА не в открытом виде)
user.password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
db.add(user)
db.commit()
❌ НИКОГДА не хранить открытый пароль
❌ НИКОГДА не логировать пароли
Payment данные (CCN):
❌ В БД (PCI compliance требует)
✅ В PCI-compliant сервисе (Stripe, PayPal)
# В БД только
class PaymentMethod(Base):
__tablename__ = 'payment_methods'
id = Column(UUID, primary_key=True)
user_id = Column(UUID)
stripe_payment_method_id = Column(String(100)) # ✅ только ID
last_four = Column(String(4)) # ✅ последние 4 цифры
card_brand = Column(String(20)) # Visa, Mastercard
expires_at = Column(DateTime)
API ключи и токены:
❌ В открытом виде
✅ Зашифрованные в БД
✅ В vault-e (AWS Secrets Manager, HashiCorp Vault)
from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher = Fernet(key)
encrypted_token = cipher.encrypt(api_token.encode())
db.add(APIToken(encrypted_value=encrypted_token))
db.commit()
Таблица: Где хранить какие данные
| Тип данных | БД | Кэш (Redis) | Файловое хранилище | Логирование | Комменты |
|---|---|---|---|---|---|
| Пользователи | ✅ | ❌ | ❌ | ✅ audit | Source of truth |
| Сессии | ❌ | ✅ | ❌ | ❌ | TTL в Redis |
| Заказы | ✅ | ❌ | ❌ | ✅ audit | Финансовые |
| Логи приложения | ❌ | ❌ | ❌ | ✅ | ELK, Datadog |
| Изображения | ❌ | ❌ | ✅ S3 | ❌ | Ссылка в БД |
| OTP коды | ❌ | ✅ | ❌ | ❌ | TTL 10 min |
| Счетчики | ❌ | ✅ | ❌ | ❌ | Синхронизировать |
| Аналитика | ❌ | ❌ | ❌ | ✅ DW | Separate DB |
| API ключи | ✅ шифр | ❌ | ❌ | ❌ | Vault или шифр |
Design patterns
Pattern 1: Cache-Aside
def get_user(user_id):
# Сначала кэш
user = redis.get(f"user:{user_id}")
if user:
return json.loads(user)
# Потом БД
user = db.query(User).filter(User.id == user_id).first()
if user:
redis.set(f"user:{user_id}", user.to_json(), ex=3600)
return user
Pattern 2: Write-Through
def update_user(user_id, data):
# Обновляем БД
user = db.query(User).filter(User.id == user_id).first()
for key, value in data.items():
setattr(user, key, value)
db.commit()
# Обновляем кэш
redis.set(f"user:{user_id}", user.to_json(), ex=3600)
return user
Размер данных — когда нужно переносить
< 100 GB → В одной БД
100 GB - 1 TB → Рассмотрите шардирование
1-10 TB → Нужно шардировать
10+ TB → Data Warehouse (BigQuery, Redshift)
# Пример роста
Ecommerce site:
Узеры: 1 GB
Производства: 2 GB
Заказы/месяц: 10 GB
После 3 лет: 360 GB → Нужно масштабировать
Best Practices
1. Определяй Source of Truth
✅ БД = SOURCE OF TRUTH
✅ Кэш = копия SOURCE OF TRUTH
❌ Кэш = SOURCE OF TRUTH (когда потеряется кэш, потеряются данные)
2. Не храни redundant данные
❌
user: {id: 1, name: "John", orders_count: 5}
orders: [...5 orders...]
✅
user: {id: 1, name: "John"}
orders: [...5 orders...]
count = len(orders) // вычислить, не хранить
3. Используй правильные типы
❌ email как TEXT
✅ email как VARCHAR(255) с уникальным индексом
❌ деньги как FLOAT
✅ деньги как DECIMAL(10, 2)
❌ даты как STRING
✅ даты как TIMESTAMP
4. Индексируй правильно
✅ Индексы на поля в WHERE
✅ Индексы на foreign keys
✅ Составные индексы для частых комбинаций
❌ Индексы на BLOB/TEXT без необходимости
❌ Слишком много индексов (замедляют запись)
5. Очищай старые данные
✅ Настроить TTL для временных данных
✅ Архивировать старые records
✅ Удалять согласно retention policy
Вывод
В БД хранить:
- Источники истины (users, orders, products)
- Финансовые данные (transactions, invoices)
- Аудит (audit logs, history)
- Персональные данные (с защитой, для GDPR)
НЕ хранить в БД:
- Сессии (в Redis)
- Файлы (в S3)
- Логи (в ELK, Datadog)
- Статистика (в DW)
- Кэш (в Redis, Memcached)
Используй правильные инструменты для каждого типа данных — это ключ к масштабируемой архитектуре.