Перейти к основному содержимому

ADR-0056: Simplicity-first — целевая нагрузка, PostgreSQL как основная СУБД, закрытие сессий/просмотров polling-ом

Статус

Accepted (пункт про поиск/PostgreSQL-FTS — superseded ADR-0059; остальное в силе)

Дата

2026-07-13

Контекст

Целевая нагрузка PIN не является высокой:

МетрикаЗначение
Всего пользователейдо 1 000 000 (регистраций)
Агенты / менеджеры / менеджеры застройщиковне более 20 000
Остальныеинвесторы
Одновременно активных1 000 – 10 000

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

Решение

1. Принцип simplicity-first (обязателен для всех детальных ТЗ)

  • Не вводить сложные паттерны/архитектуру и микрооптимизации «на вырост» без измеренной необходимости.
  • Простые CRUD-контексты (user-activity/sessions, favorites, selections и подобные) проектируются максимально просто: PostgreSQL — основная и единственная СУБД, обычные таблицы + индексы, IDbRepository из SDK. Redis для этих сервисов не используется (ни кэш, ни счётчики, ни сессии).

    Superseded (ADR-0059) — только про поиск. Ранее этот принцип трактовался как «поиск делаем на PostgreSQL-FTS (tsvector/GIN), отдельное поисковое хранилище не добавляем». Это положение отменено: поиск во всех поверхностях PIN переведён на Elasticsearch-проекции через Talent.Common.Domain.Search (ADR-0008 подтверждён). Не затрагивает остальное: PostgreSQL остаётся основной СУБД и source of truth; Redis по-прежнему не используется в sessions/favorites/selections.

  • Кэширование добавляется только точечно и по результатам измерений; на целевой нагрузке достаточно PostgreSQL + разумных индексов.
  • Redis/backplane и прочие внешние компоненты — только там, где без них нельзя (см. п.4).

2. Отслеживание сессий и просмотров — требования

Несмотря на упрощение, обязательно отслеживаем:

  • длительность сессии пользователя/гостя;
  • длительность каждого просмотра объекта недвижимости;
  • сколько раз и как долго просматривался каждый объект (агрегаты по лоту).

3. Механизм: LastActivityAt + polling-delta (без Redis/TTL-магии)

Записи Session и LotView (просмотр лота) ведутся в PostgreSQL:

  • при старте: CreatedAt = начало сессии / открытия лота; Status = Active;
  • активность (heartbeat/действия фронта) обновляет LastActivityAt той же записи (IDbRepository.Update(SetPropertyCalls) — дёшево, без загрузки агрегата);
  • фоновый worker (polling, background job SDK) периодически (например, раз в 1–5 минут) выбирает записи Status = Active, у которых now() - LastActivityAt > 10–15 минут (порог — конфигурация);
  • для каждой такой записи в одной транзакции: Status → Inactive (завершена), вычисляется длительность Duration = LastActivityAt - CreatedAt, публикуются Kafka-события через outbox:
    • pin.user-activity.session-ended.v1 (sessionId, userId/anonymousId, CreatedAt, LastActivityAt, Duration);
    • pin.user-activity.lot-view-ended.v1 (lotId, sessionId, userId/anonymousId, Duration, контекст фильтров);
  • worker идемпотентен (повторный прогон не трогает Inactive), выборка — Filter c SelectLockMode.ForUpdateSkipLocked;
  • агрегаты по лоту (сколько раз, суммарно/в среднем как долго) — консюмер lot-view-ended инкрементально обновляет счётчики (LotViewStats: ViewsCount, TotalDuration, LastViewedAt) в PostgreSQL.

4. Границы применения других хранилищ (без изменения ранее принятого)

  • ClickHouse — только там, где обосновано аналитикой: история цен в invest-analytics (append-only, требование ТЗ) и view-статистика застройщиков в dev-stats (3 года facts + rollup-ы, ADR-0057). Операционные события user-activity — PostgreSQL (уточнение ADR-0054); новые применения CH — только через ADR.
  • Redis не используется в sessions/favorites/selections. Для SignalR-hub мессенджера на целевой нагрузке (1–10k активных) достаточно одного инстанса без Redis backplane; backplane — опция при реальном горизонтальном масштабировании (уточнение ADR-0055).
  • Распределённые блокировки, если нужны, — Postgres-провайдер SDK (DistributedLock.Postgres), не Redis.
  • Elasticsearch (полнотекстовый/fuzzy-поиск): simplicity-first не отменяет search-projection — поиск во всех поверхностях PIN использует Elasticsearch-проекции через Talent.Common.Domain.Search (ADR-0059, подтверждает ADR-0008). Source of truth остаётся в PostgreSQL сервиса; проекция строится по событиям + rebuild-джоб.

Последствия

  • Детальные ТЗ контекстов user-activity, favorites, selections проектируются как простые PostgreSQL-CRUD с фоновым polling-worker-ом; никаких Redis/TTL/стриминговых пайплайнов.
  • Все ТЗ обязаны проходить проверку «не переусложнено ли» (verify-пас конвейера).
  • При будущем росте нагрузки узкие места решаются точечно (индексы → реплики чтения → кэш) с измерениями.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы

  • ADR-0054 (user-activity — уточнён: PostgreSQL-only), ADR-0055 (messenger — backplane опционален), ADR-0059 (поиск на Elasticsearch-проекциях — supersedes FTS-пункт), docs/06-services/user-activity/, docs/06-services/favorites/, docs/06-services/selections/.