Skip to content

PRD: Search indexing (CDC / Typesense)

ModuleNền tảng (CORE-16)PRD IDPRD-IDX-001
StatusShippedOwnerPlatform / Search squad
Date2026-06-15Versionv1.0
Packages@nx/search · @nx/coreURDIDX · SCH

TL;DR

Cho cả nền tảng một bề mặt tìm kiếm denormalize, luôn-tươi-mới mà không service nào phải ghi vào search engine. Mọi thay đổi cơ sở dữ liệu đã commit chảy ra thành một change-data event, và một consumer duy nhất phản chiếu nó vào đúng search collection - chín collection (organizers, merchants, categories, devices, sale-channels, products, product-variants, inventories, users) được nuôi từ một danh mục các bảng nguồn CDC. Mỗi document được enrich với dữ liệu liên quan để một kết quả mang đủ những gì UI cần (tên merchant, giá variant, ảnh, mã quét, tồn theo vị trí, option facet), và một thay đổi của một cha dùng chung (đổi tên merchant, đổi tên location, một mã dùng chung) fan ra mọi document phụ thuộc bằng patch có đích - không bao giờ re-index toàn bộ. Document được version để sự kiện replay hoặc sai-thứ-tự không bao giờ làm sống lại trạng thái cũ, luồng suy giảm an toàn khi engine sập, và caller truy vấn bất kỳ collection nào qua một API tìm kiếm keyword + semantic hợp nhất.

1. Bối cảnh & Vấn đề

Dữ liệu của KICKO nằm rải khắp nhiều service và schema Postgres - commerce, pricing, inventory, identity. Một màn hình storefront hay back-office cần "tìm một sản phẩm theo tên, barcode, option, hoặc giá, giới hạn theo một merchant" không thể fan một query khắp tất cả các bảng đó lúc đọc, và bắt mọi service tạo sự kiện cũng phải ghi vào một search engine sẽ làm logic index phân tán, nhân đôi mọi đường ghi, và lệch đồng bộ ngay lần đầu một service quên cập nhật.

Cái còn thiếu là một seam duy nhất biến luồng change-data sẵn có của nền tảng thành một bề mặt sẵn-sàng-truy-vấn. Phần khó không phải lưu trữ: đó là giữ mỗi search document denormalize nhưng tươi mới (kết quả của một sản phẩm vẫn phải hiện đúng giá sau một sửa fare, đúng tên sau một đổi tên), sống sót qua replay và sự cố mà không hỏng trạng thái, và phơi bày một hợp đồng truy vấn nhất quán mọi app đều dùng được. Increment này cung cấp backbone đó.

2. Mục tiêu & Không-mục-tiêu

Mục tiêu

  • Phản chiếu mọi ghi đã commit của một bảng nguồn được index vào search collection của nó, chỉ điều khiển bởi luồng change-data - không service tạo sự kiện nào ghi vào search (IDX).
  • Ánh xạ bảng nguồn tới chín collection, mỗi cái một bảng-nguồn-document cộng một tập input liên quan / dẫn xuất (IDX).
  • Enrich mỗi document với dữ liệu liên quan đã join trước khi index, để một kết quả tự-đầy-đủ (IDX).
  • Fan ra một thay đổi bản ghi dùng chung tới mọi document phụ thuộc bằng patch có đích, không re-index toàn bộ (IDX).
  • Version mỗi document chống sự kiện replay / sai-thứ-tự; tombstone delete và soft-delete (IDX).
  • Suy giảm an toàn - circuit-break khi engine/dependency sự cố, dead-letter message poison, cô lập lỗi theo-từng-document (IDX).
  • Một API truy vấn tìm kiếm keyword + semantic hợp nhất trên bất kỳ collection đã đăng ký nào, kèm count, scoping, và hợp đồng list chuẩn của nền tảng (SCH).

Không-mục-tiêu

  • Sở hữu việc capture change-data - phát sự kiện topic là hạ tầng CDC (Debezium) của nền tảng; search tiêu thụ các sự kiện đúng định dạng (URD-CON-008).
  • Index mọi bảng - SaleOrder là một nguồn CDC đã định nghĩa nhưng chưa được index (URD-CON-009).
  • Một màn hình re-index / backfill cho người dùng cuối - backfill là snapshot replay vận hành (URD-CON-010).
  • Logic ghi, giá, hay toán tồn của chính các service tạo sự kiện - chúng nằm ở Commerce, pricing, và Inventory.

3. Chỉ số thành công

Chỉ sốMục tiêu / tín hiệu
Độ tươi mớiMột ghi đã commit phản ánh vào collection của nó trong độ trễ bình thường của luồng, không bước thủ công
Đầy đủ denormalizeMột kết quả mang các trường liên quan (tên, giá, ảnh, mã, tồn, facet) không cần lookup thứ hai
Đúng fan-outMột đổi tên cha / thay đổi mã dùng chung cập nhật mọi document phụ thuộc; không giá trị denormalize cũ nào còn lại
An toàn replayMột sự kiện replay hoặc sai-thứ-tự không bao giờ ghi đè trạng thái document mới hơn
Bền với sự cốMột sự cố engine tạm dừng và hồi phục luồng không mất dữ liệu; message poison vào dead-letter topic, không vào đường live
Nhất quán truy vấnMọi app search bất kỳ collection nào qua một hợp đồng (bao-hoặc-mảng + range header), keyword hoặc semantic

4. Persona & Use Case

PersonaMục tiêu trong tính năng này
Cashier / StorefrontTìm một sản phẩm, variant, hay khách hàng tức thì theo tên, barcode, hay option facet
Owner / ManagerSearch merchants, categories, sale-channels, inventory, và users giới hạn theo cái họ quản lý
Tích hợp kênh / back-officeTruy vấn một collection theo filter + count theo một hợp đồng ổn định
Người vận hành nền tảngTin luồng luôn tươi mới, sống sót sự cố, và cô lập message xấu

Kịch bản lõi: một chủ đổi tên một merchant. Thay đổi được bắt từ luồng change-data và index lên collection merchants; cùng đổi tên đó fan ra products, categories, và sale-channels của merchant đó để mọi kết quả hiện tên mới - bằng patch có đích, không re-index. Vài khoảnh khắc sau một cashier search products cho một thức uống theo tên và nhận một kết quả đã mang giá, ảnh, option facet, và tồn theo vị trí của nó. Nếu search engine sập giữa luồng, consumer tạm dừng, probe để hồi phục, và tiếp tục từ chỗ dừng - không mất sự kiện.

5. User Story

  • Là một storefront, tôi search một collection theo tên / barcode / option và nhận lại kết quả đã mang đủ thứ để render, để tôi không bao giờ gọi lần hai cho mỗi kết quả.
  • Là một chủ, tôi đổi tên một merchant một lần và mọi product, category, channel hiện tên đó đều cập nhật - tôi không bao giờ phải re-publish catalogue.
  • Là một tích hợp, tôi truy vấn và đếm một collection với một filter chuẩn, để search hành xử như mọi list endpoint khác.
  • Là một người vận hành, tôi tin một sự kiện replay sẽ không làm sống lại dữ liệu đã xóa và một sự cố engine tạm dừng luồng thay vì rớt ghi.
  • Là một user back-office, search của tôi tự động giới hạn theo tenant, để tôi không bao giờ thấy bản ghi mình không được phép.

6. Yêu cầu chức năng

#Yêu cầuURD ref
FR-1Mọi thay đổi đã commit của một bảng nguồn được index đều bắt từ luồng change-data và phản chiếu vào collection của nó - không service tạo sự kiện nào ghi vào searchURD-IDX-001
FR-2Bảng nguồn ánh xạ tới chín collection; mỗi collection một bảng-nguồn-document, phần còn lại là liên quan / dẫn xuấtURD-IDX-002
FR-3Sự kiện create / update / snapshot upsert document; một delete hoặc soft-delete ghi một tombstone để nó rời kết quảURD-IDX-003
FR-4Mỗi document được enrich với dữ liệu liên quan đã join (tên sở hữu, tập category, giá, ảnh, mã quét, tồn-theo-vị-trí, option facet, định danh / vai trò / organizer của user) trước khi indexURD-IDX-004
FR-5Một thay đổi dùng chung / cha fan ra mọi document phụ thuộc bằng patch có đích, không re-index toàn bộURD-IDX-005
FR-6Mỗi document mang một dấu phiên bản; sự kiện replay / sai-thứ-tự không bao giờ ghi đè trạng thái mới hơn; patch con→cha chỉ động vào trường của riêng nóURD-IDX-006
FR-7Sự kiện xử lý theo lô từng-topic; một lô hỏng parse hoàn toàn hoặc ghi index lỗi được báo lỗi để retry, không bao giờ âm thầm bỏ quaURD-IDX-007
FR-8Sự cố engine / dependency trip một circuit breaker tạm dừng và probe để hồi phục; message poison chuyển sang dead-letter topicURD-IDX-008
FR-9Một enrich lỗi vẫn index document với dữ liệu trên nó; một cascade lỗi không bao giờ chặn các fan-out khác trong lôURD-IDX-009
FR-10Id bản ghi nhúng trong filter engine được kiểm tra để một id sai định dạng không bao giờ làm thay đổi tập đích của một fan-outURD-IDX-010
FR-11Full-text search bất kỳ collection đã đăng ký nào theo tên với một filter kiểu Ignis (where / limit / skip / order / include / fields), trả về theo hình bao-hoặc-mảng của nền tảng kèm range header, cộng một countURD-SCH-001..002
FR-12Search hỗ trợ khớp hybrid keyword + semantic (vector) - endpoint chung hybrid mặc định, search gắn-theo-tài-nguyên keyword-only kèm opt-inURD-SCH-003 · URD-SCH-005
FR-13Một resource controller có thể gắn /search + /search/count có-giới-hạn merge một caller-scope (tenant) vào query; search và count được xác thực và gated bằng permissionURD-SCH-004 · URD-SCH-006..007

Toàn văn yêu cầu và tiêu chí chấp nhận nằm ở Platform URD - IDXSCH. PRD này tham chiếu chúng thay vì lặp lại.

7. Yêu cầu phi chức năng

Lĩnh vựcYêu cầu
Độ tươi mớiIndex theo luồng change-data; không service tạo sự kiện nào ghi vào search, nên có một đường và không double-write drift
IdempotencyDấu phiên bản (vị trí log nguồn) của document làm việc re-delivery và replay an toàn - trạng thái mới hơn luôn thắng
Bền vữngSự cố engine / dependency tạm dừng luồng qua một circuit breaker và probe để hồi phục; message poison được dead-letter; lô retry khi lỗi
Cô lập lỗiEnrich lỗi → index un-enrich; một cascade lỗi → các cái khác vẫn áp; lỗi của một document không bao giờ làm lỗi lô toàn bộ
Hiệu năngFan-out dùng patch có-filter có-đích với một giới hạn concurrency; một thay đổi cha động tới hàng nghìn con không bao giờ kích re-index toàn bộ
An toànId nhúng trong filter engine được kiểm tra; lỗi query phân loại rõ (collection thiếu → rỗng, query xấu → 400)
Tenancy & authzSearch gắn-theo-tài-nguyên merge một caller-scope vào query; search / count được xác thực JWT / Basic và gated bằng permission
i18nTên denormalize lưu dạng object song ngữ ({ en, vi }) và search được ở cả hai

8. UX & Luồng

Một bảng-nguồn-document (ví dụ Product, ProductVariant, InventoryStock, User) tạo ra document collection của riêng nó; phần còn lại trong input của một collection - giá, option, ảnh, mã quét, profile, grant, bảng join - là cascade-only: thay đổi của chúng fan vào document đã tồn tại thay vì tạo một cái của riêng mình.

9. Dữ liệu & Miền

Khái niệmVai trò
Search collectionMột trong chín index denormalize: organizers, merchants, categories, devices, sale-channels, products, product-variants, inventories, users
Bảng-nguồn-documentBảng CDC duy nhất mà hàng của nó thành document của một collection (ví dụ InventoryStockinventories, Userusers)
Nguồn cascade-onlyMột bảng liên quan không có collection riêng; thay đổi của nó fan vào một document đã tồn tại (ví dụ FareSet/Fare → giá variant, ProductOption → facet variant, UserIdentifier → liên hệ user)
EnrichmentBước join fold dữ liệu Postgres liên quan của một document lên nó trước khi index
Cascade triggerMột tín hiệu có-kiểu rằng một thay đổi dùng chung / cha phải patch một tập document phụ thuộc
Dấu phiên bảnVị trí log nguồn (và một dấu tombstone) mang trên mỗi document để ghi mới nhất thắng

Chỉ khái niệm - schema collection, tập mapper, và nội bộ pipeline nằm ở tài liệu developer search. Quan hệ xuyên-thực-thể là soft reference phân giải lúc enrich, không phải join cơ sở dữ liệu.

10. Phụ thuộc & Giả định

Phụ thuộc vào

  • Hạ tầng change-data - Debezium phát các topic nx.bana.cdc.<schema>.<Table> mà consumer đăng ký (URD-CON-008).
  • Search engine (Typesense) - kho index và engine truy vấn sau mỗi collection.
  • @nx/core - danh mục bảng-nguồn CDC, registry topic, và các model dùng chung mà loader enrich đọc.
  • Dữ liệu Commerce / pricing / inventory / identity - các hàng nguồn và dữ liệu liên quan mỗi document được enrich và fan ra từ đó.

Giả định

  • Sự kiện change-data đúng định dạng và đủ thứ tự để dấu phiên bản phân giải phần còn lại.
  • Các collection được provision trong engine trước khi luồng chạy; một collection chưa-được-tạo trả về rỗng thay vì lỗi.
  • Service tạo sự kiện phát các ghi miền của chúng bình thường; chúng không biết và không quan tâm search tiêu thụ chúng.

11. Rủi ro & Câu hỏi mở

Rủi ro / câu hỏiGiảm thiểu / trạng thái
Một sự kiện replay hoặc sai-thứ-tự làm sống lại trạng thái cũ / đã-xóaMỗi document được version bằng vị trí log nguồn; trạng thái mới hơn luôn thắng; patch con→cha không bao giờ lật lifecycle
Một đổi tên cha động tới hàng nghìn con gây bão re-indexFan-out dùng patch có-filter có-đích với một giới hạn concurrency - không bao giờ re-index toàn bộ
Sự cố search engine làm tắc hoặc mất luồngCircuit breaker tạm dừng và probe để hồi phục; lô được báo lỗi và retry; không advance offset khi lỗi
Một message xấu hoặc enrich lỗi chặn lôMessage poison dead-letter; enrich lỗi index un-enrich; try/catch theo-từng-cascade cô lập lỗi
Một id sai định dạng làm thay đổi filter đích của một fan-outId nhúng trong filter engine được kiểm tra trước khi dùng
SaleOrder là một nguồn CDC nhưng chưa indexGhi nhận là không-mục-tiêu cố ý cho increment này (URD-CON-009)

12. Kế hoạch phát hành & Tiêu chí ra mắt

Khía cạnhKế hoạch
PhaseP2 - IDXSCH trong URD feature catalog
RolloutMọi merchant; backbone chạy toàn nền tảng, không flag theo-từng-merchant
MigrationKhông ở tầng dữ liệu - collection được provision và backfill bằng snapshot replay
Toggle vận hànhCircuit breaker bật bằng cấu hình môi trường; dead-letter topic cấu hình được
Tiêu chí ra mắtMột ghi đã commit đến collection của nó kèm enrich; một đổi tên cha fan ra các phụ thuộc; một replay không ghi đè trạng thái mới hơn; một sự cố engine tạm dừng và hồi phục không mất; bất kỳ collection nào search + count được qua API hợp nhất
Giám sátThống kê theo-từng-lô (creates / updates / deletes / snapshots / parse errors / engine ok / fail / throughput), số cascade ok/failed, trip và escalate của circuit breaker, lượng dead-letter

13. FAQ

Mỗi service có ghi vào search engine không? Không - không service tạo sự kiện nào động vào search. Mọi ghi index được điều khiển bởi luồng change-data, nên có một đường và không double-write drift.

Làm sao một kết quả hiện đúng giá hoặc tên sau một sửa? Document được enrich với dữ liệu liên quan lúc index, và một thay đổi của một cha dùng chung (một fare, một đổi tên, một mã dùng chung) fan ra mọi document phụ thuộc bằng patch có đích - nên giá trị denormalize được làm tươi, không bị bỏ cũ.

Một bản ghi đã xóa thì sao? Một delete hoặc soft-delete ghi một tombstone, nên bản ghi rời kết quả trong khi thứ tự phiên bản của nó được giữ.

Một sự kiện replay có làm hỏng index không? Không - mỗi document mang một dấu phiên bản (vị trí log nguồn). Một sự kiện replay hoặc sai-thứ-tự cũ hơn trạng thái hiện tại bị bỏ qua.

Khi search engine sập thì sao? Một circuit breaker tạm dừng luồng và probe để hồi phục; lô được báo lỗi và retry, nên không mất gì. Message poison chuyển sang một dead-letter topic thay vì chặn đường live.

App truy vấn nó thế nào? Qua một hợp đồng: full-text search bất kỳ collection đã đăng ký nào theo tên với một filter kiểu Ignis, cộng một count - keyword mặc định, với khớp hybrid semantic (vector) có sẵn. Search gắn-theo-tài-nguyên tự động giới hạn kết quả theo tenant của caller.

Tham chiếu

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.