PRD: Search indexing & query
| Module | Nền tảng (CORE-16) | PRD ID | PRD-IDX-001 |
| Status | Shipped | Owner | Phát Nguyễn |
| Date | 2026-06-15 | Version | v1.0 |
| Năng lực | Lập chỉ mục · truy vấn tìm kiếm | 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 trực tiếp 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 nạp từ một danh mục các bản ghi nguồn change-data. 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 ở một cha dùng chung (đổi tên merchant, đổi tên location, một mã dùng chung) sẽ lan tỏa tới mọi document phụ thuộc bằng patch có đích - không bao giờ re-index toàn bộ. Document được đánh phiên bản để 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 BANA nằm rải khắp nhiều service và schema cơ sở dữ liệu - 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ể bung một query khắp tất cả các bảng đó lúc đọc, còn 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 vẫn 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 lần sửa fare, đúng tên sau một lần đổi tên), sống sót qua replay và sự cố mà không hỏng trạng thái, và cung cấp 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 & Ngoài mục tiêu
Mục tiêu
- Phản chiếu mọi ghi đã commit của một bản ghi nguồn được index vào search collection của nó, chỉ do luồng change-data điều khiển - không service tạo sự kiện nào ghi vào search (
IDX). - Ánh xạ bản ghi nguồn tới chín collection, mỗi cái một loại bản ghi nguồn làm 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ự chứa đủ thông tin (
IDX). - Lan tỏa 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). - Đánh phiên bản mỗi document để chống sự kiện replay / sai thứ tự; đánh dấu delete và soft-delete bằng một dấu xóa (
IDX). - Suy giảm an toàn - cắt mạch khi engine/dependency sự cố, cách ly poison message, 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).
Ngoài mục tiêu
- Sở hữu việc capture change-data - phát các change-data event là việc của hạ tầng change-data 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 loại bản ghi - đơn hàng bán là một nguồn change-data đã đị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à việc replay snapshot ở mức vận hành (URD-CON-010).
- Logic ghi, giá, hay tính 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 cần bước thủ công |
| Đầy đủ khi denormalize | Một kết quả mang các trường liên quan (tên, giá, ảnh, mã, tồn, facet) mà không cần lookup lần hai |
| Đúng fan-out | Một lần đổi tên cha / thay đổi mã dùng chung cập nhật mọi document phụ thuộc; không còn giá trị denormalize cũ nào sót lại |
| An toàn khi 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 rồi hồi phục luồng mà không mất dữ liệu; poison message vào hàng đợi cách ly, 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 (dạng list chuẩn + header total-range), 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 rằng luồng luôn tươi mới, sống sót qua sự cố, và cô lập message lỗi |
Kịch bản cốt 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; chính lần đổi tên đó lan tỏa tới 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 tìm một thức uống theo tên và nhận một kết quả đã mang sẵn 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, thăm dò để 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ờ phải 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 rằng 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 sẽ 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 merchant của chính mình, để 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ản ghi 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ản ghi nguồn ánh xạ tới chín collection; mỗi collection có một loại bản ghi nguồn làm 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 dấu xóa để nó rời khỏ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 chủ 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 lan tỏa tới 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 luồng; 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 kích hoạt một bộ cắt mạch an toàn để tạm dừng và thăm dò hồi phục; poison message chuyển sang hàng đợi cách ly | URD-IDX-008 |
| FR-9 | Một lần enrich lỗi vẫn index document với dữ liệu sẵn có 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 của search 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 chuẩn (điều kiện / phân trang / sắp xếp / include liên quan / chọn trường), trả về theo dạng list chuẩn của nền tảng kèm header total-range, cộng một count | URD-SCH-001..002 |
| FR-12 | Search hỗ trợ khớp hybrid keyword + semantic (theo nghĩa) - endpoint chung mặc định là hybrid, search gắn theo tài nguyên chỉ keyword kèm opt-in | URD-SCH-003 · URD-SCH-005 |
| FR-13 | Một API tài nguyên có thể gắn một search + count có giới hạn, gộp một caller-scope (merchant của chính user) vào query; search và count đều được xác thực và bảo vệ 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 chỉ có một đường ghi và không có lệch do double-write |
| Idempotency | Dấu phiên bản (vị trí của document trong log thay đổi nguồn) làm cho việc re-delivery và replay trở nên 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 bộ cắt mạch an toàn và thăm dò để hồi phục; poison message được cách ly; lô được retry khi lỗi |
| Cô lập lỗi | Enrich lỗi → index ở trạng thái chưa 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 hỏng cả lô |
| Hiệu năng | Fan-out dùng patch có đích, có filter 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 hoạt re-index toàn bộ |
| An toàn | Id nhúng trong filter của search engine được kiểm tra; lỗi query phân loại rõ ràng (collection thiếu → rỗng, query xấu → một lỗi rõ ràng) |
| Tenancy & authz | Search gắn theo tài nguyên gộp một caller-scope vào query; search / count được xác thực và bảo vệ bằng permission |
| i18n | Tên đã denormalize lưu dạng object song ngữ (Anh + Việt) và search được ở cả hai ngôn ngữ |
8. UX & Luồng
Một bản ghi nguồn làm document (ví dụ một product, một product variant, một bản ghi tồn kho, một 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ản ghi join - đều là cascade-only: thay đổi của chúng lan vào document đã tồn tại thay vì tạo một document 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ản ghi nguồn làm document | Bản ghi change-data duy nhất mà các hàng của nó trở thành document của một collection (ví dụ một bản ghi tồn kho → inventories, một user → users) |
| Nguồn cascade-only | Một bản ghi liên quan không có collection riêng; thay đổi của nó lan vào một document đã tồn tại (ví dụ một fare / bộ giá → giá variant, một product option → facet variant, một user identifier → liên hệ user) |
| Enrichment | Bước join gộp dữ liệu liên quan của một document vào nó trước khi index |
| Cascade trigger | Một tín hiệu có định kiểu báo 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í trong log thay đổi nguồn (và một dấu xóa) mang trên mỗi document để ghi mới nhất luôn thắng |
Chỉ mang tính khái niệm - schema collection, tập mapper, và nội bộ pipeline nằm ở tài liệu developer search. Quan hệ giữa các 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 - phát các luồng change-data theo từng bản ghi mà consumer đăng ký (URD-CON-008).
- Search engine - kho index và engine truy vấn đứng sau mỗi collection.
- Lõi nền tảng - danh mục bản ghi nguồn change-data, registry luồng, 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à mỗi document được enrich và lan tỏa từ đó.
Giả định
- Sự kiện change-data đúng định dạng và đủ trật 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 nghiệp vụ của chúng bình thường; chúng không biết và không quan tâm rằng 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 đánh phiên bản bằng vị trí của nó trong log thay đổi 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 lần đổi tên cha động tới hàng nghìn con gây bão re-index | Fan-out dùng patch có đích, có filter 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 | Bộ cắt mạch an toàn tạm dừng và thăm dò để hồi phục; lô được báo lỗi và retry; không đẩy tiến độ khi lỗi |
| Một message lỗi hoặc một lần enrich lỗi chặn cả lô | Poison message được cách ly; enrich lỗi vẫn index ở trạng thái chưa enrich; cô lập theo từng cascade để khoanh 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 của search engine được kiểm tra trước khi dùng |
| Đơn hàng bán là một nguồn change-data nhưng chưa được index | Ghi nhận là ngoài mục tiêu một cách có chủ đích 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 replay snapshot |
| Toggle vận hành | Bộ cắt mạch an toàn bật bằng cấu hình môi trường; hàng đợi cách ly 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 lần đổi tên cha lan tỏa tới 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 rồi hồi phục mà không mất gì; bất kỳ collection nào cũng 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, số lần trip và escalate của bộ cắt mạch, lượng message cách ly |
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 đều do luồng change-data điều khiển, nên chỉ có một đường ghi và không có lệch do double-write.
Làm sao một kết quả hiện đúng giá hoặc tên sau một lần sửa? Document được enrich với dữ liệu liên quan lúc index, và một thay đổi ở một cha dùng chung (một fare, một lần đổi tên, một mã dùng chung) sẽ lan tỏa tới mọi document phụ thuộc bằng patch có đích - nên giá trị denormalize được làm tươi chứ không bị bỏ cũ.
Một bản ghi đã xóa thì sao? Một delete hoặc soft-delete ghi một dấu xóa, nên bản ghi rời khỏi kết quả trong khi thứ tự phiên bản của nó vẫ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í trong log thay đổi nguồn). Một sự kiện replay hoặc sai thứ tự cũ hơn trạng thái hiện tại sẽ bị bỏ qua.
Khi search engine sập thì sao? Một bộ cắt mạch an toàn tạm dừng luồng và thăm dò để hồi phục; lô được báo lỗi và retry, nên không mất gì. Poison message chuyển sang một hàng đợi cách ly 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 chuẩn, cộng một count - keyword theo mặc định, với khớp hybrid semantic có sẵn. Search gắn theo tài nguyên tự động giới hạn kết quả theo merchant của caller.
Tham chiếu
- URD: Nền tảng - IDX · SCH
- PRD anh em: Activity notifications & realtime 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 - URD
- Developer: @nx/search · @nx/core