PRD: Search indexing (CDC / Typesense)
| Module | Nền tảng (CORE-16) | PRD ID | PRD-IDX-001 |
| Status | Shipped | Owner | Platform / Search squad |
| Date | 2026-06-15 | Version | v1.0 |
| Packages | @nx/search · @nx/core | URD | IDX · 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 -
SaleOrderlà 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ới | Mộ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 đủ denormalize | Mộ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-out | Mộ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 replay | Mộ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ấn | Mọ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
| Persona | Mục tiêu trong tính năng này |
|---|---|
| Cashier / Storefront | Tìm một sản phẩm, variant, hay khách hàng tức thì theo tên, barcode, hay option facet |
| Owner / Manager | Search 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-office | Truy 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ảng | Tin 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ầu | URD ref |
|---|---|---|
| FR-1 | Mọ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 search | URD-IDX-001 |
| FR-2 | Bả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ất | URD-IDX-002 |
| FR-3 | Sự 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-4 | Mỗ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 index | URD-IDX-004 |
| FR-5 | Mộ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-6 | Mỗ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-7 | Sự 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ỏ qua | URD-IDX-007 |
| FR-8 | Sự 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 topic | URD-IDX-008 |
| FR-9 | Mộ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-10 | Id 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-out | URD-IDX-010 |
| FR-11 | Full-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 count | URD-SCH-001..002 |
| FR-12 | Search 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-in | URD-SCH-003 · URD-SCH-005 |
| FR-13 | Mộ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 permission | URD-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 - IDX và SCH. 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ực | Yêu cầu |
|---|---|
| Độ tươi mới | Index 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 |
| Idempotency | Dấ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ững | Sự 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ỗi | Enrich 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ăng | Fan-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àn | Id 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 & authz | Search 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 |
| i18n | Tê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ệm | Vai trò |
|---|---|
| Search collection | Một trong chín index denormalize: organizers, merchants, categories, devices, sale-channels, products, product-variants, inventories, users |
| Bảng-nguồn-document | Bảng CDC duy nhất mà hàng của nó thành document của một collection (ví dụ InventoryStock → inventories, User → users) |
| Nguồn cascade-only | Mộ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) |
| Enrichment | Bước join fold dữ liệu Postgres liên quan của một document lên nó trước khi index |
| Cascade trigger | Mộ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ản | Vị 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ỏi | Giả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óa | Mỗ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-index | Fan-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ồng | Circuit 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-out | Id 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 index | Ghi 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ạnh | Kế hoạch |
|---|---|
| Phase | P2 - IDX và SCH trong URD feature catalog |
| Rollout | Mọi merchant; backbone chạy toàn nền tảng, không flag theo-từng-merchant |
| Migration | Không ở tầng dữ liệu - collection được provision và backfill bằng snapshot replay |
| Toggle vận hành | Circuit 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ắt | Mộ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át | Thố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
- URD: Nền tảng - IDX · SCH
- PRD anh em: Activity notifications & websocket push · Quản lý asset & media
- Liên quan: Product options & variant generation - denormalize option facet vào backbone này
- Module: Nền tảng - tổng quan + traceability
- Developer: @nx/search · @nx/core