← Назад к курсу

System Design - Система учета покупок (Purchase Tracking System)

Цель: Научиться проектировать систему для обработки финансовых транзакций с акцентом на данные, консистентность и аналитику, используя Python-стек.

Длительность: 1.5 - 2 часа


Часть 1: Введение и бизнес-контекст (10 минут)

Проблема: Компания (ритейл, маркетплейс, SaaS) нуждается в системе для отслеживания всех покупок пользователей с возможностями аналитики, отчетности и интеграции.

Бизнес-требования:

  1. Учет всех транзакций с гарантией сохранности.
  2. Отслеживание статусов заказов (создан, оплачен, доставлен, отменен).
  3. Аналитика: топ товаров, LTV пользователя, сезонные тренды.
  4. Интеграция с платежными системами и ERP.
  5. Персонализация (рекомендации, кэшбэк).

Ключевые отличия от URL shortener:

  • Акцент на данные и их консистентность, а не на низкую задержку.
  • Сложные запросы (агрегации, JOIN) вместо простых key-value lookups.
  • Строгие требования к надежности (финансовые данные).
  • Необходимость обработки как онлайн-трафика, так и офлайн-аналитики.

Часть 2: Разбор кейса: Маркетплейс с 10 млн пользователей (70 минут)

Шаг 1: Требования и оценки (15 мин)

  • Функциональные требования:

    1. Регистрация покупки (пользователь, товары, сумма, время).
    2. Обновление статуса заказа.
    3. Получение истории покупок пользователя.
    4. Расчет и начисление кэшбэка/баллов.
    5. Формирование отчетов (выручка за период, топ товаров).
    6. Интеграционные вебхуки для оплаты.
  • Нефункциональные требования:

    • Консистентность данных (главное!): Сумма списанных средств = сумме заказа. Недопустимо потерять транзакцию.
    • Доступность: Система приема платежей должна быть высокодоступной.
    • Масштабируемость: Пиковые нагрузки (Черная пятница, распродажи).
    • Аудит и откат: Возможность отследить цепочку событий и отменить ошибочную операцию.
  • Оценки масштаба:

    • Активных пользователей: 10 млн.
    • Средний чек: 5000 руб.
    • Среднее количество покупок на пользователя в месяц: 2.
    • Пиковый RPS на запись (оформление заказа): (10M * 2 / 30 дней / 86400 сек) * 10 (пиковый коэффициент) ≈ 80 RPS.
    • Пиковый RPS на чтение (история заказов): 80 RPS * 50 ≈ 4000 RPS.
    • Объем данных за год (только заголовки заказов): 10M * 2 * 12 мес * 2 КБ ≈ 480 ГБ/год.
    • Объем данных о товарах в заказах (детализация): в 5-10 раз больше.

Шаг 2: Высокоуровневая архитектура и выбор технологий (20 мин)

Пользователь -> [API Gateway / Load Balancer] -> [Микросервисы] -> [Базы данных и очереди]

Core-сервисы (Python):

  1. Order Service: Ядро системы. Создание заказа, управление статусом.
    • Технологии: FastAPI/Django (REST), SQLAlchemy/asyncpg.
    • База данных: PostgreSQL. Причины: транзакции (ACID), сложные запросы по истории, джойны с пользователями/товарами. Используем схемы и партиционирование по дате.
  2. Payment Service: Взаимодействие с эквайрингом (ЮKassa, Stripe, Tinkoff).
    • Технологии: aiohttp/httpx для асинхронных вызовов к API банков.
    • База данных: PostgreSQL. Требует строгой согласованности.
  3. Analytics Service: Сбор и предрасчет метрик.
    • Технологии: Celery для периодических задач, Pandas/Polars для обработки, ClickHouse/PostgreSQL + TimescaleDB для хранения агрегатов.
  4. Notification Service: Отправка email (через SendGrid) и push о смене статуса.
    • Технологии: FastAPI, Celery/RQ для фоновых отправок.

Коммуникация между сервисами:

  • Синхронная (HTTP/REST/gRPC): Когда нужен immediate response (создание заказа -> проверка наличия товара).
  • Асинхронная (очереди сообщений): Для декoupling и фоновых задач. Используем RabbitMQ/Kafka.
    # Пример: Отправка события в RabbitMQ (pika)
    import pika
    connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
    channel = connection.channel()
    channel.queue_declare(queue='order_created')
    channel.basic_publish(exchange='',
                          routing_key='order_created',
                          body='{"order_id": 123, "user_id": 456}')
    

Шаг 3: Детальное проектирование. Обработка потока заказа (15 мин)

# order_service/api.py (упрощенно)
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from datetime import datetime

app = FastAPI()

class OrderCreate(BaseModel):
    user_id: int
    items: list[dict]
    total: float

@app.post("/orders")
async def create_order(order_data: OrderCreate, background_tasks: BackgroundTasks):
    # 1. НАЧАЛО ТРАНЗАКЦИИ в PostgreSQL
    # 2. Проверка данных (наличие товара, баланс пользователя - запрос к другим сервисам)
    # 3. Создание записи Order в статусе "PENDING"
    new_order_id = save_order_to_db(order_data)
    
    # 4. ПОЛУЧЕНИЕ ТРАНЗАКЦИИ. Фиксация факта создания заказа.
    
    # 5. АСИНХРОННО: Отправка события в очередь для Payment Service
    background_tasks.add_task(publish_to_message_queue, 'order.created', {'order_id': new_order_id})
    
    # 6. АСИНХРОННО: Запуск задачи на предрасчет рекомендаций
    background_tasks.add_task(celery_app.send_task, 'update_user_profile', args=[order_data.user_id])
    
    return {"order_id": new_order_id, "status": "processing"}

# payment_service/consumer.py
# Слушает очередь 'order.created', вызывает API банка, обновляет статус заказа.
# Важно: идемпотентность. Повторное сообщение не должно списывать деньги дважды.

Ключевые моменты:

  • Транзакционность: Используем with session.begin(): в SQLAlchemy для atomic операций.
  • Идемпотентность API: Генерация idempotency_key на клиенте для повторной безопасной отправки запроса.
  • SAGA-паттерн: Для распределенных транзакций между Order и Payment сервисами. Компенсирующие действия при ошибках (например, отмена заказа, если платеж не прошел).

Шаг 4: Масштабирование и работа с данными (20 мин)

  1. Шардирование БД заказов:

    • По user_id: Все заказы пользователя в одном шарде. Упрощает получение истории. Проблема: горячие шарды для VIP-клиентов.
    • По дате (партиционирование): ORDER BY created_at работает быстро. Упрощает архивацию.
    -- PostgreSQL пример партиционирования
    CREATE TABLE orders (
        id BIGSERIAL,
        user_id INT,
        created_at TIMESTAMP DEFAULT now(),
        total DECIMAL
    ) PARTITION BY RANGE (created_at);
    CREATE TABLE orders_2024_05 PARTITION OF orders FOR VALUES FROM ('2024-05-01') TO ('2024-06-01');
    
  2. Кэширование:

    • Redis для горячих данных: История последних 10 заказов пользователя, часто запрашиваемые товары. Инвалидация при обновлении статуса.
    # Кэширование с помощью aiocache (асинхронный)
    from aiocache import Cache
    cache = Cache(Cache.REDIS, endpoint="localhost", port=6379, namespace="orders")
    
    async def get_user_orders(user_id: int):
        cached = await cache.get(f"last_orders:{user_id}")
        if cached:
            return cached
        orders = await db.fetch_user_orders(user_id)  # Долгий запрос
        await cache.set(f"last_orders:{user_id}", orders, ttl=300)
        return orders
    
  3. Аналитика и отчетность (отдельный контур данных):

    • Проблема: Сложные аналитические запросы убивают OLTP-базу.
    • Решение (CDC - Change Data Capture): Делаем снимок данных из PostgreSQL и загружаем в ClickHouse (для быстрых агрегаций) или в S3 + AWS Athena (для ad-hoc-аналитики).
    • Инструменты: Debezium (Kafka Connect) для захвата изменений или собственный механизм на Python, публикующий события в Kafka.
  4. Резервное копирование и аудит:

    • Все финансовые события (создание заказа, списание, отмена) пишутся в неизменяемый лог (Kafka с long retention) или в append-only таблицу в БД. Это источник истины для сверок.

Часть 3: Паттерны и антипаттерны для финансовых систем на Python (15 минут)

Паттерны:

  1. Repository/Unit of Work (SQLAlchemy Session): Для управления транзакциями.
  2. SAGA/Outbox Pattern: Для координации обновлений между микросервисами. Реализация через таблицу outbox_messages в той же транзакции, что и основное изменение.
  3. CQRS (Command Query Responsibility Segregation): Разделение модели записи (PostgreSQL) и модели чтения (кэш, предрассчитанные материализованные представления).
  4. Retry with Exponential Backoff: Для вызовов внешних платежных API (tenacity библиотека).
  5. Circuit Breaker: Защита от сбоев внешних сервисов.

Антипаттерны:

  1. Прямые JOIN между микросервисами через БД: Нарушает инкапсуляцию. Общайтесь только через API.
  2. Игнорирование идемпотентности: Приводит к дублированию списаний.
  3. Блокирующие вызовы в цикле событий: Вызов requests.get() в асинхронном платежном вебхуке заблокирует все другие запросы.
  4. Хранение денежных сумм в float: Использовать Decimal с фиксированной точностью.
  5. Отсутствие метрик и алертов: Не знать о росте времени ответа БД или количестве неудачных платежей.

Часть 4: Практическое задание (5 минут)

Задание: Спроектировать систему лояльности с кэшбэком в рамках маркетплейса.

  • Требования:
    1. Начисление баллов (1% от суммы покупки).
    2. Списание баллов при следующей покупке.
    3. История начислений/списаний.
    4. TTL для баллов (сгорание через 1 год).
    5. Real-time отображение баланса в личном кабинете.
  • Вопросы для проектирования:
    1. Как гарантировать консистентность между списанием денег и начислением баллов? (Транзакция? SAGA?).
    2. Как рассчитать баланс пользователя быстро? (Кэш, отдельная таблица с текущим балансом).
    3. Как реализовать TTL для баллов? (Фоновая задача Celery, которая пересчитывает балансы).
    4. Какая БД лучше подходит для хранения событий лояльности? (PostgreSQL для транзакций, Redis для горячего баланса).

Резюме и ключевые выводы:

  1. Консистентность важнее latency в финансовых системах.
  2. Python + PostgreSQL — отличная база для бизнес-логики и данных, но нужны правильные архитектурные решения (партиционирование, индексы).
  3. Микросервисы + очереди помогают масштабировать и изолировать отказы.
  4. Разделяй online (OLTP) и offline (OLAP) нагрузку. Используй разные хранилища.
  5. Пиши идемпотентный код, логируй все финансовые события, имей план отката.

Дополнительные материалы:

  • Книга: "Designing Data-Intensive Applications" Martin Kleppmann.
  • Статьи: Реальные кейсы от Uber (Schemaless), Airbnb (Airflow), Spotify (Python микросервисы).
  • Библиотеки Python для production: FastAPI, SQLAlchemy, Celery, pydantic, structlog, prometheus-client.