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

KbArticleSearchDocument

Elasticsearch document KbArticleSearchDocument — rebuildable search projection публичного полнотекстового поиска базы знаний/FAQ (../api/public/search-kb-articles.md, поиск по заголовку/анонсу/тексту/тегам с фильтром по категории/тегу и аудитории актора). Переводит FTS с PostgreSQL-tsvector/GIN на Elasticsearch-проекцию (ADR-0059).

Назначение

Source of truth — KnowledgeBaseArticleEntity в PostgreSQL; проекция хранит только индексируемые/фильтруемые/сортируемые/возвращаемые в карточку поля опубликованных статей. Поиск идёт через Talent.Common.Domain.Search (ElasticsearchDocument, IElasticsearchIndexRepository<KbArticleSearchDocument>, SearchQuery) — прямого клиента Elasticsearch в handlers/domain нет (ADR-0059). Драйвер перехода — typo-tolerance (fuzzy), транслитерация кириллица↔латиница и морфология русского языка для заголовков/тела. Индекс держит только Status = Published-срез: back-office-список статей (Draft/Archived, любые аудитории — ../api/support/list-kb-articles.md) читает PostgreSQL напрямую и в этот индекс не ходит. Cross-cutting — по Общим Требованиям К Документам.

Index

ПолеЗначение
Index aliassupport-kb-articles
Versioned indexsupport-kb-articles-v1 (переключение alias — IElasticsearchAliasManager, ADR-0029)
Owner servicesupport (Pin.Support)
Source of truthKnowledgeBaseArticleEntity (../domain/knowledge-base-article.md) в PostgreSQL
Rebuild jobKbArticleSearchRebuildJob : ISearchRebuildJob

Документы — в src/services/support/Infrastructure/Search/Documents/; наполнение — инкрементально in-service проектором по доменным событиям статьи + периодический/ручной rebuild (ADR-0059 п.2).

Document Id И Routing

  • Document id: <TenantId>:<ArticleId> (TenantId = PIN CompanyId, единый tenant платформы, ADR-0052) — детерминирован, upsert идемпотентен.
  • Routing key: TenantId (единый tenant платформы; кластер малый, ADR-0056).
  • Delete/tombstone: документ индексируется только при Status = Published; переход в Draft/Archived или soft-delete (DeletedAt) удаляет документ из индекса (fail-closed — неопубликованная статья поиском не находится).

Поля

ПолеТипIndexedStoredAnalyzerSourceSecurity
DocumentIdkeywordдадаTenantId:ArticleIdinclude
ArticleIdkeywordдадаKnowledgeBaseArticleEntity.Idinclude
TenantIdkeywordданетKnowledgeBaseArticleEntity.TenantIdscope-only (не выдаётся)
SlugkeywordдадаSluginclude (переход /help/{slug})
Titletextдадаru_translit_fuzzyTitleinclude
Summarytextдадаru_translit_fuzzySummaryinclude
Bodytextданетru_translit_fuzzyBodyполнотекст (в список не выдаётся — только матч)
Tagskeyword + textдадаru_translit_fuzzy (text-подполе)Tags.Items (jsonb)фильтр «тег» + матч
SearchTexttextданетru_translit_fuzzyTitle ⊕ Summary ⊕ Body ⊕ Tags (copy_to)основной матч Q
CategorykeywordдадаCategory (KbCategory)фильтр
AudiencekeywordдадаAudience (KbAudience)security-trimming (аудитория)
StatuskeywordданетStatus (только Published индексируется)security-trimming
PublishedAtdateдадаPublishedAtсортировка (default при пустом Q)
ViewsCountlongдадаViewsCountранжирование «по популярности» (задел, отложено)
SchemaVersionintegerдадаElasticsearchDocumentверсия схемы проекции
UpdatedAtdateдадаUpdatedAtfreshness watermark
IndexedAtdateдадаиндексаторfreshness

Внешний ввод (Q) идёт только параметром SearchQuery (multi-match по SearchText с fuzziness), не в raw-DSL. Фильтры (Category/Tags) и релевантность/PublishedAt-сортировка — типизированными полями SearchQuery. Анализатор ru_translit_fuzzy — русская морфология + ICU-транслитерация (кириллица↔латиница) + edge-ngram для typo-tolerance (ADR-0059 п.1).

Source Events

EventVersionTransform
ArticlePublished (IDomainEvent)1upsert документа из состояния статьи (Status = Published, маппинг полей выше)
контент-Update при Status = Published1re-upsert изменённых полей (Title/Summary/Body/Tags/Category/Audience)
ArticleArchived / soft-delete (DeletedAt)1delete документа по DocumentId

База знаний интеграционных Kafka-событий не публикует (внутренний справочник); проекция наполняется in-service проектором KbArticleSearchProjector по доменным событиям агрегата + периодический/ручной KbArticleSearchRebuildJob для bootstrap и восстановления из PostgreSQL.

Отказоустойчивость индексации

  • Транзиентный сбой записи/удаления документа — ретраи проектора; исчерпание попыток → карантин события с алертом, без блокировки проекции. Пропущенный документ дорисовывается ближайшим KbArticleSearchRebuildJob.
  • Публикация → находимость и смена Audience (сужение) применяются как upsert/переиндексация документа; до синхронизации поиск может краткий промежуток отдавать прежнее состояние, но фактический доступ на детальном чтении всегда проверяется по актуальной записи (INV-KB-3), поэтому неопубликованный или суженный по аудитории контент через поиск не утекает (fail-closed).
  • Out-of-order. Watermark UpdatedAt документа не даёт запоздавшему обновлению перетереть более свежее состояние; расхождение с PostgreSQL всегда сходится rebuild-ом (индекс — ускоритель, не источник истины).

Freshness

  • SLA: search (near-real-time, < 1 мин от публикации до появления в поиске; eventual относительно PostgreSQL, FreshnessView — ADR-0019).
  • Watermark: IndexedAt + UpdatedAt статьи в документе.
  • Stale behavior: показать результаты с freshness-маркером; тело статьи и точный ViewsCount отдаёт ../api/public/get-kb-article.md, читающая source-of-truth PostgreSQL.

Security

  • Audience/scope trimming выполняется через SearchScopeFilter до query execution (ADR-0059 п.3): AnonymousAudience = Public; UserAudience ∈ {Public, AuthenticatedUsers}; SupportInternal на публичном поиске никогда не отдаётся (INV-KB-3). В индексе — только Status = Published.
  • PII fields: none (контент справочный; ФИО/телефон/реквизиты в базу знаний не попадают).
  • Полнотекстовый индекс не хранит secrets, tokens, платёжные/KYC-данные; TenantId — только scope-фильтр, наружу не выдаётся.

Rebuild

  1. Создать версионный индекс support-kb-articles-vN (маппинг + ru_translit_fuzzy).
  2. Backfill KbArticleSearchRebuildJob из PostgreSQL: Filter(Status == Published && DeletedAt == null) по KnowledgeBaseArticleEntity стримом (SearchAsStream), map в KbArticleSearchDocument.
  3. Валидация counts/checksums против источника.
  4. Атомарно переключить alias support-kb-articles на новую версию (IElasticsearchAliasManager).
  5. Держать предыдущий индекс до конца rollback-окна.

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

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

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