ADR-0059: Поиск во всех поверхностях PIN — Elasticsearch-проекции через Talent.Common.Domain.Search
Статус
Accepted
Дата
2026-07-14
Контекст
Поиск в PIN нужен в разных контекстах: каталог недвижимости, шахматка (поиск проектов), пользователи
Identity, контакты мессенджера, клиенты/фиксации/сделки CRM, тикеты/база знаний/арбитраж поддержки,
посты ленты. Первые детальные ТЗ реализовывали его на PostgreSQL-FTS (tsvector + generated-колонка
- GIN-индекс,
websearch_to_tsquery), трактуя это как следствие simplicity-first (ADR-0056): «не добавляем отдельное поисковое хранилище».
PostgreSQL-FTS оказался недостаточным для реальных требований поиска платформы:
- нет typo-tolerance (fuzzy) и транслитерации (кириллица ↔ латиница) — критично для имён, адресов, названий проектов и застройщиков;
- слабая работа с алиасами/синонимами и морфологией нескольких языков одновременно;
- relevance-ранжирование (
ts_rank) плохо совмещается с keyset-курсорами и не масштабируется на подсветку; - разъезжающиеся реализации FTS в каждом сервисе (свои
SearchVector, свои GIN-индексы, свои правила scope-фильтрации) — дублирование без единого канона.
Elasticsearch как search projection уже принят платформой (ADR-0008) и поддержан реальным SDK:
провайдер-нейтральные контракты Talent.Common.Domain.Search (ISearchIndex/SearchIndexResult/
SearchPage) над живой инфраструктурой Talent.Common.Infrastructure.ElasticSearch (клиент
Elastic.Clients.Elasticsearch v9). Целевая нагрузка (ADR-0056: 1–10k одновременно активных) не требует
шардирования — это единый небольшой кластер как read/search projection, а не операционное хранилище.
Историческая заметка: ранние черновики называли движок «OpenSearch» и пакет
Talent.Common.Search. Фактический SDK работает на Elasticsearch (Talent.Common.Domain.Search+ инфраTalent.Common.Infrastructure.ElasticSearch); OpenSearch-пакет в SDK — заглушка. Терминология приведена в соответствие с кодом.
Решение
Поиск во всех поисковых поверхностях PIN переводится с PostgreSQL-FTS на Elasticsearch-проекции через
единый SDK-слой Talent.Common.Domain.Search (инфра — Talent.Common.Infrastructure.ElasticSearch).
Собственные поисковые движки, прямой клиент Elasticsearch в бизнес-коде и PostgreSQL-FTS (tsvector/GIN)
как основной механизм поиска — не используются.
1. Единый строительный слой — Talent.Common.Domain.Search (+ инфра ElasticSearch)
ElasticsearchDocument— базовый typed-документ проекции (DocumentId,TenantId,BrandId?,SchemaVersion,UpdatedAt,IndexedAt); конкретные документы каждого сервиса — вsrc/services/<service>/Infrastructure/Search/Documents/.ISearchIndex/IElasticsearchIndexRepository<TDocument>— индексация/upsert/delete и чтение (SearchIndexResult/SearchPage); никакого прямого клиента Elasticsearch в handlers/domain.SearchQuery— типизированный запрос (фильтры, пагинация, сортировка); внешний ввод (searchTerm) идёт только параметром запроса, не в raw-DSL.ISearchRebuildJob— полная переиндексация из source-of-truth PostgreSQL (bootstrap нового индекса, восстановление после расхождения/потери проекции).IElasticsearchIndexManager/IElasticsearchAliasManager— версионирование индексов и zero-downtime reindex через переключение alias между версиями (ADR-0029: alias strategy).SearchScopeFilter— tenant/brand/actor-фильтр, применяемый до выполнения запроса (единообразно с repository-pipeline scope-rules, ADR-0014); security-trimming выполняется до query execution, не после получения результата.
2. Модель данных: source of truth остаётся в PostgreSQL
- Source of truth каждой поверхности — PostgreSQL owning-сервиса (ADR-0004). Elasticsearch — rebuildable read/search projection и не является owner-ом данных (ADR-0035, data-ownership-and-lineage).
- Проекция наполняется инкрементально по доменным/интеграционным событиям сервиса (upsert/delete
документа при изменении агрегата) плюс периодический/ручной rebuild-джоб (
ISearchRebuildJob) для bootstrap и восстановления консистентности. - Каждый search-документ имеет documented owner, source events, transform,
documentId, routing key, tenant/brand trimming, PII-поля, freshness SLA и rebuild-джоб (data-ownership-and-lineage → Elasticsearch Projections).
3. PII-trimming и scope-фильтр
- В документ проекции кладутся только поля, разрешённые для соответствующей поисковой поверхности; чувствительные данные (ФИО/телефон/паспорт/реквизиты) в индекс не попадают либо помечаются PII-полями и вырезаются security-trimming-ом до выдачи.
SearchScopeFilterнавешивает tenant/brand/record-level ограничения (ADR-0052 tenancy, ADR-0014 scope-filters) — пользователь видит только то, что ему разрешено; фильтрация применяется до query execution.
4. Поисковые поверхности (scope перевода)
| Поверхность | Сервис (owner) | Ищется | Примечание |
|---|---|---|---|
| Проекты и объекты недвижимости | catalog (публичный каталог); поиск проектов — chessboard (search-projects) | проекты, дома/здания, объекты недвижимости/планировки, застройщики, адреса | fuzzy/транслитерация/алиасы — основной драйвер |
| Пользователи | identity | пользователи (агенты/менеджеры/сотрудники) по имени/телефону/e-mail | PII-trimming обязателен; scope по правам |
| Контакты | messenger | контакты/пользователи для старта чата (поиск по телефону/имени) | заменяет прямой lookup/FTS |
| Клиенты / фиксации / сделки | crm | клиенты, фиксации, сделки | record-level (владелец + группа/руководитель) в SearchScopeFilter |
| Тикеты / база знаний / арбитраж | support | тикеты, KB-статьи, арбитражные кейсы | публичный KB-поиск + внутренний по тикетам/арбитражу с разными scope |
| Посты | news | опубликованные посты ленты (заголовок/подзаголовок/текст блоков) | заменяет SearchVector/GIN partial (UC-NWS-005) |
Каждая поверхность фиксирует свой search-документ и rebuild-джоб в детальном ТЗ сервиса
(docs/06-services/<context>/search/<index>.md). PostgreSQL-FTS-артефакты (SearchVector, GIN-индексы,
websearch_to_tsquery) из уже написанных ТЗ заменяются Elasticsearch-проекцией при доработке контекстов.
5. Отношение к ранее принятым решениям
- Подтверждает ADR-0008 (Elasticsearch как search projection): настоящий ADR распространяет уже принятый канон на все поисковые поверхности PIN и фиксирует единый SDK-слой реализации.
- Отменяет (supersedes) FTS-пункт ADR-0056: трактовка simplicity-first как «поиск на PostgreSQL-FTS,
отдельное поисковое хранилище не добавляем» больше не действует — поиск идёт через Elasticsearch-проекции.
Остальные положения ADR-0056 остаются в силе: PostgreSQL — основная СУБД и source of truth; Redis
не используется в
sessions/favorites/selections; закрытие сессий/просмотров — polling-delta.
Последствия
- Единый поисковый слой (
Talent.Common.Domain.Search) во всех сервисах; никаких разъезжающихся FTS-реализаций, прямых клиентов Elasticsearch и сторонних движков. - Появляется operational-компонент Elasticsearch как search projection: нужны index/alias-версионирование (ADR-0029), rebuild/backfill-джобы (ADR-0035), freshness-мониторинг (ADR-0019, data-freshness-standard) и наблюдаемость каждого search-запроса (ADR-0047).
- Проекция eventually-consistent относительно PostgreSQL: результаты поиска сопровождаются
freshness-семантикой; для admin/decision/support-запросов возвращается
FreshnessView(data-ownership-and-lineage). - Детальные ТЗ поверхностей (
catalog,chessboardproject-search,identity,messenger,crm,support,news) дорабатываются: search-документ + консюмеры наполнения +ISearchRebuildJob+ PII/scope-правила; ранее описанные PostgreSQL-FTS-механизмы (например, UC-NWS-005,search-projects,search-users-by-phone,search-kb-articles) переводятся на проекцию. - Security-trimming и tenant/record-level scope выполняются до query execution через
SearchScopeFilter— единообразно с repository-pipeline (ADR-0014).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (5): Entity (Replica): MessengerUserEntity, Entity / Aggregate: ArbitrageCaseEntity (+ ArbitrageNoteEntity), Entity / Aggregate: KnowledgeBaseArticleEntity, Entity / Aggregate: Post, Entity / Aggregate: TicketEntity
- API (9): GET /api/agent/crm/clients — Клиентская база агента (список + поиск), GET /api/agent/crm/fixations — Список фиксаций агентства (+ поиск), GET /api/messenger/contacts — Список подтверждённых контактов, GET /api/public/support/kb/articles — Поиск базы знаний/FAQ, GET /api/support/arbitrage-cases — Очередь арбитражных дел (фильтры), GET /api/support/tickets — Список обращений (фильтры), POST /api/support/kb/articles — Создать статью базы знаний, POST /api/v1/posts/{id}/unpublish — снять пост с публикации, PUT /api/v1/posts/{id}/blocks — Редактирование блоков поста (embed через selections)
- Use Cases (3): UC-NWS-001. Создать черновик и опубликовать пост, UC-NWS-002. Встроить подборку в пост (иммутабельный snapshot), UC-NWS-005. Поиск постов (Elasticsearch)
- Консюмеры (1): Consumer: Messenger User Replica Sync (MessengerUserUpsertedConsumer)
- Поиск (5): ClientSearchDocument, DealSearchDocument, FixationRequestSearchDocument, KbArticleSearchDocument, TicketSearchDocument
- adr (3): ADR-0008: Elasticsearch как search projection, ADR-0056: Simplicity-first — целевая нагрузка, PostgreSQL как основная СУБД, закрытие сессий/просмотров polling-ом, ADR-0063 — справочники pin.handbook (репликация из Talent Handbook)
- index.md (5): CRM Service, Messenger Service, News Service (Pin.News), Notifications Service, Support Service
Связанные документы
- ADR-0008 (Elasticsearch как search projection — подтверждается), ADR-0056 (simplicity-first —
FTS-пункт superseded), ADR-0004 (PostgreSQL source of truth), ADR-0014 (repository pipeline и
scope-filters), ADR-0019 (data freshness standard), ADR-0029 (migrations/seeding — alias strategy),
ADR-0035 (event governance и data lineage), ADR-0052 (tenancy), ADR-0038 (Shared SDK —
Talent.Common.Domain.Search). - Архитектура: data-ownership-and-lineage (Elasticsearch Projections), data-architecture, data-freshness-standard, runtime-context-diagram.
- SDK:
docs/11-backend-sdk/02-base-entities-events.md(Elasticsearch Document),docs/11-backend-sdk/05-repository-pipeline.md(SearchScopeFilter),docs/11-backend-sdk/03-nuget-package-structure.md(Talent.Common.Domain.Search+Talent.Common.Infrastructure.ElasticSearch). - Поверхности (детальные ТЗ):
docs/06-services/catalog/,docs/06-services/chessboard/,docs/06-services/identity/,docs/06-services/messenger/,docs/06-services/crm/,docs/06-services/support/,docs/06-services/news/.