← Назад к вопросам

Какие данные стоит хранить в базе данных?

2.2 Middle🔥 151 комментариев
#Архитектура систем#Требования и их анализ

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какие данные хранить в базе данных

Это стратегический вопрос архитектуры: что и где хранить. Рассмотрю критерии и 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)Файловое хранилищеЛогированиеКомменты
Пользователи✅ auditSource of truth
СессииTTL в Redis
Заказы✅ auditФинансовые
Логи приложенияELK, Datadog
Изображения✅ S3Ссылка в БД
OTP кодыTTL 10 min
СчетчикиСинхронизировать
Аналитика✅ DWSeparate 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

Вывод

В БД хранить:

  1. Источники истины (users, orders, products)
  2. Финансовые данные (transactions, invoices)
  3. Аудит (audit logs, history)
  4. Персональные данные (с защитой, для GDPR)

НЕ хранить в БД:

  1. Сессии (в Redis)
  2. Файлы (в S3)
  3. Логи (в ELK, Datadog)
  4. Статистика (в DW)
  5. Кэш (в Redis, Memcached)

Используй правильные инструменты для каждого типа данных — это ключ к масштабируемой архитектуре.

Какие данные стоит хранить в базе данных? | PrepBro