Chương 32. Search
Ở chương trước, ta nói về cache:
> Cache là bản nhớ tạm để đọc nhanh hơn, không phải nguồn sự thật.
Chương này nói về một loại read model rất phổ biến khác:
> Search index.
Search là bài toán:
Người dùng gõ một từ khóa.
Hệ thống trả về thứ họ đang tìm.
Nghe đơn giản.
Nhưng search tốt không chỉ là:
WHERE name LIKE '%keyword%'
Search tốt thường cần:
- Tìm gần đúng.
- Tìm theo nhiều trường.
- Xếp hạng kết quả.
- Autocomplete.
- Gợi ý sửa lỗi chính tả.
- Filter.
- Sort.
- Highlight.
- Tốc độ nhanh khi dữ liệu lớn.
Database quan hệ có thể làm search ở mức nhất định.
Nhưng khi nhu cầu tìm kiếm phức tạp hơn, ta có thể cần search engine như Elasticsearch, OpenSearch, Meilisearch, Typesense, Solr, hoặc dịch vụ search managed.
Thông điệp chính của chương:
> Search engine giúp tìm kiếm tốt hơn, nhưng search index không phải source of truth. Database vẫn nên giữ sự thật chính.
---
32.1. Ví dụ quán bánh: khách tìm "bánh dâu"
Website bán bánh có ô tìm kiếm.
Khách gõ:
bánh dâu
Hệ thống nên trả về:
- Bánh kem dâu.
- Tart dâu.
- Bánh mousse dâu.
- Bánh sinh nhật vị dâu.
Nếu khách gõ:
banh dau
không dấu, hệ thống vẫn nên hiểu.
Nếu khách gõ sai:
bánh dâuu
hệ thống có thể vẫn gợi ý đúng.
Nếu khách gõ:
sinh nhật bé gái dâu
hệ thống nên ưu tiên bánh liên quan đến sinh nhật, dâu, chủ đề bé gái.
Đây không còn là query đơn giản.
Đây là search.
---
32.2. Database search là gì?
Database quan hệ có thể tìm kiếm bằng SQL.
Ví dụ đơn giản:
SELECT * FROM products
WHERE name LIKE '%dâu%';
Hoặc:
WHERE title ILIKE '%python%'
Với PostgreSQL, còn có full-text search:
to_tsvector(title || ' ' || description)
Database search phù hợp khi:
- Dữ liệu chưa quá lớn.
- Search đơn giản.
- Không cần ranking phức tạp.
- Không cần typo tolerance.
- Không cần autocomplete mạnh.
- Muốn giữ hệ thống đơn giản.
Ví dụ admin tìm order theo mã:
order_id = 'order_123'
Database là đủ.
Ví dụ tìm user theo email:
email = 'a@example.com'
Database là đúng.
Không cần Elasticsearch cho mọi ô tìm kiếm.
---
32.3. Search engine là gì?
Search engine là hệ thống được thiết kế chuyên cho tìm kiếm.
Nó tạo một index riêng để truy vấn nhanh theo từ khóa.
Ví dụ:
Product database -> Search index
Search index có thể lưu:
product_id
name
description
category
tags
price
popularity
availability
created_at
Khi user search:
bánh dâu sinh nhật
Search engine tìm trong index và trả kết quả đã xếp hạng.
Search engine thường mạnh ở:
- Full-text search.
- Ranking.
- Fuzzy search.
- Autocomplete.
- Faceted filtering.
- Highlight.
- Query nhanh trên dữ liệu lớn.
Database vẫn giữ dữ liệu thật.
Search engine giữ bản index để tìm nhanh.
---
32.4. Search index không phải source of truth
Đây là điểm rất quan trọng.
Search index là bản phục vụ đọc/tìm kiếm.
Nó không nên là nơi giữ sự thật chính.
Ví dụ:
products table trong database:
source of truth
product_search_index:
bản sao phục vụ search
Nếu search index mất, có thể rebuild từ database hoặc event.
Nếu database mất, search index không đủ để khôi phục toàn bộ nghiệp vụ.
Search index có thể:
- Trễ vài giây.
- Thiếu record tạm thời.
- Có field được denormalize.
- Được rebuild.
- Bị xóa và tạo lại.
Vì vậy không nên để checkout tin search index là sự thật cuối cùng về giá/tồn kho.
Checkout nên kiểm tra lại database/source of truth.
---
32.5. Khi nào database search là đủ?
Database search thường đủ khi:
- Search theo id, email, mã đơn, mã học viên.
- Dữ liệu nhỏ hoặc vừa.
- Admin search nội bộ đơn giản.
- Không cần typo tolerance.
- Không cần ranking phức tạp.
- Query vẫn nhanh với index phù hợp.
Ví dụ:
Tìm order theo order_id.
Tìm user theo email.
Tìm grading job theo submission_id.
Filter payment theo status và created_at.
Đây là truy vấn database bình thường.
Đừng thêm search engine chỉ vì có ô search.
Search engine thêm chi phí:
- Đồng bộ dữ liệu.
- Vận hành thêm hệ thống.
- Index có thể trễ.
- Schema index.
- Rebuild.
- Debug lệch dữ liệu.
Nếu database đang làm tốt, cứ dùng database.
---
32.6. Khi nào cần search engine?
Cần nghĩ đến search engine khi:
- Dữ liệu lớn và search LIKE chậm.
- Cần tìm theo nhiều trường văn bản.
- Cần ranking kết quả.
- Cần autocomplete.
- Cần fuzzy search.
- Cần filter/facet phức tạp.
- Cần highlight từ khóa.
- Cần search tiếng Việt/đa ngôn ngữ tốt hơn.
- Search trở thành trải nghiệm chính của sản phẩm.
Ví dụ:
Marketplace có hàng triệu sản phẩm.
Học online có hàng chục nghìn khóa học/bài học.
Ứng dụng docs cần search nội dung văn bản.
AI Judge muốn search feedback, submission, rubric.
News site cần search bài viết.
Khi search là feature quan trọng, search engine đáng giá.
---
32.7. Search khác filter
Filter là lọc theo điều kiện rõ.
Ví dụ:
status = PAID
created_at >= 2026-05-01
price between 100000 and 200000
category = cakes
Search là tìm theo từ khóa/ngữ nghĩa.
Ví dụ:
"bánh dâu sinh nhật"
"python căn bản"
"feedback thiếu ví dụ"
Một màn hình thường có cả hai:
Search keyword: "dâu"
Filter category: "birthday cake"
Filter price: 200k-500k
Sort by popularity
Database làm filter rất tốt.
Search engine làm text search/ranking tốt.
Search engine cũng có thể filter, nhưng đừng quên source of truth vẫn nằm ở database.
---
32.8. Ranking là gì?
Ranking là xếp kết quả nào lên trước.
Ví dụ user gõ:
bánh dâu
Kết quả nào nên đứng đầu?
- Bánh có tên chứa "bánh dâu"?
- Bánh bán chạy nhất?
- Bánh còn hàng?
- Bánh có đánh giá cao?
- Bánh gần khu vực user?
- Bánh đang khuyến mãi?
Search tốt không chỉ tìm đúng.
Nó còn xếp đúng.
Ranking có thể dựa trên:
- Mức khớp từ khóa.
- Field quan trọng hơn.
- Popularity.
- Freshness.
- Rating.
- Availability.
- Business rule.
Database search đơn giản thường khó làm ranking tinh tế.
Search engine hỗ trợ tốt hơn.
---
32.9. Autocomplete là gì?
Autocomplete là gợi ý khi user đang gõ.
Ví dụ user gõ:
banh d
Hệ thống gợi ý:
bánh dâu
bánh dừa
bánh dứa
Autocomplete cần phản hồi rất nhanh.
Nó thường có traffic cao vì mỗi ký tự có thể tạo request.
Cần:
- Debounce ở frontend.
- Rate limit.
- Index phù hợp.
- Cache nếu cần.
- Không query database nặng mỗi phím.
Với hệ thống nhỏ, database prefix search có thể đủ.
Với sản phẩm lớn, search engine hoặc autocomplete index riêng thường tốt hơn.
---
32.10. Fuzzy search là gì?
Fuzzy search giúp tìm dù user gõ sai.
Ví dụ:
bánh dâuu
pyhton
jaavscript
Hệ thống vẫn gợi ý:
bánh dâu
python
javascript
Fuzzy search hữu ích cho trải nghiệm người dùng.
Nhưng nó cũng có rủi ro:
- Trả kết quả quá rộng.
- Tìm sai ý.
- Chậm nếu cấu hình không đúng.
Không phải ô tìm kiếm nào cũng cần fuzzy.
Admin tìm order_id thì không nên fuzzy quá mức.
User tìm sản phẩm/khóa học thì fuzzy rất hữu ích.
---
32.11. Search tiếng Việt
Tiếng Việt có dấu, tách từ, và biến thể gõ.
Ví dụ:
bánh dâu
banh dau
banh dâu
bánh dau
Search tốt nên xử lý được.
Cần nghĩ đến:
- Bỏ dấu khi index/search.
- Lowercase.
- Tách từ.
- Synonym nếu cần.
- Bộ analyzer phù hợp.
Ví dụ:
"AI" và "trí tuệ nhân tạo"
"bánh kem" và "bánh sinh nhật" có thể liên quan
Search ngôn ngữ không chỉ là lưu text rồi LIKE.
Nhưng cũng đừng làm quá nếu nhu cầu đơn giản.
---
32.12. Đồng bộ dữ liệu sang search như thế nào?
Vì database là source of truth, search index cần được cập nhật khi database đổi.
Các cách phổ biến:
Cách 1: Update trực tiếp sau khi ghi database
Update product DB
Update search index
Đơn giản, nhưng nếu update search lỗi thì sao?
Cách 2: Outbox/event
Update product DB
Save ProductUpdated event
Worker cập nhật search index
Đáng tin hơn, dễ retry hơn.
Cách 3: CDC
Đọc thay đổi từ database log rồi cập nhật search.
Hữu ích khi hệ thống lớn hoặc legacy.
Với nhiều hệ thống thực dụng, outbox/event là cách rõ ràng.
---
32.13. Vì sao search có thể chậm đồng bộ vài giây?
Nếu cập nhật search qua event/queue, sẽ có độ trễ.
Ví dụ:
Admin đổi tên sản phẩm lúc 10:00:00
Database cập nhật ngay
Search index cập nhật lúc 10:00:03
Trong 3 giây đó, search có thể trả tên cũ.
Đây là eventual consistency.
Với nhiều search use case, trễ vài giây chấp nhận được.
Nhưng cần hiểu và thiết kế UI/ops phù hợp.
Nếu admin vừa sửa sản phẩm và search ngay chưa thấy, có thể do index lag.
Đó không nhất thiết là bug, nếu nằm trong kỳ vọng.
---
32.14. Rebuild index là gì?
Rebuild index là xây lại search index từ nguồn sự thật.
Ví dụ:
Xóa product index cũ
Đọc toàn bộ products từ database
Index lại sang search engine
Cần rebuild khi:
- Đổi mapping/schema index.
- Index bị lỗi.
- Thêm field search mới.
- Search engine mất dữ liệu.
- Cần sửa dữ liệu đã index sai.
Rebuild có thể nặng.
Cần nghĩ đến:
- Chạy nền.
- Batch.
- Không làm quá tải database.
- Index mới song song rồi switch alias.
- Theo dõi tiến độ.
Search index phải rebuild được.
Vì nó không phải source of truth.
---
32.15. Mapping/schema của search index
Search index cũng có schema/mapping.
Ví dụ:
name: text
category_id: keyword
price: number
created_at: date
available: boolean
Mapping quyết định:
- Field nào full-text search.
- Field nào dùng filter chính xác.
- Field nào sort được.
- Analyzer nào dùng.
Nếu mapping sai, search có thể chậm hoặc sai.
Ví dụ:
category_id nên là keyword để filter chính xác, không phải text phân tích từ.
name nên là text để search.
Thiết kế search index cần hiểu query sẽ dùng.
---
32.16. Denormalization trong search index
Search index thường denormalize dữ liệu.
Ví dụ product index có:
product_id
product_name
category_name
brand_name
price
rating
availability
Trong database, category/brand có thể nằm bảng riêng.
Nhưng trong search index, ta copy tên category/brand vào document product để search nhanh.
Đây là bình thường.
Đổi lại, khi category name đổi, phải cập nhật nhiều product document.
Denormalization giúp đọc/search nhanh.
Nhưng làm đồng bộ phức tạp hơn.
---
32.17. Search result phải kiểm tra lại source of truth khi cần
Search index có thể stale.
Vì vậy với hành động quan trọng, cần kiểm tra lại database.
Ví dụ user search sản phẩm thấy:
Bánh dâu còn hàng, giá 120.000
Khi checkout, hệ thống phải kiểm tra lại database:
- Giá hiện tại.
- Tồn kho.
- Trạng thái còn bán.
Không dùng search index để quyết định cuối cùng.
Tương tự:
Search khóa học thấy khóa học public.
Khi user enroll/pay, kiểm tra lại database.
Search là để tìm và hiển thị.
Nghiệp vụ quan trọng vẫn phải dựa source of truth.
---
32.18. Search và cache khác nhau thế nào?
Cache lưu kết quả hoặc dữ liệu để đọc nhanh.
Search index tổ chức dữ liệu để tìm kiếm hiệu quả.
Ví dụ cache:
product:123 -> product detail
Ví dụ search index:
keyword "dâu" -> các product liên quan
Cache trả lời:
> Tôi đã biết key này, lấy dữ liệu nhanh hơn được không?
Search trả lời:
> Với từ khóa này, những document nào liên quan nhất?
Search index có thể được cache thêm, nhưng bản thân search index không chỉ là cache đơn giản.
---
32.19. Search và database filter kết hợp
Một cách phổ biến:
Search engine tìm danh sách id phù hợp.
Database lấy chi tiết mới nhất theo id.
Ví dụ:
Search "bánh dâu" -> [p1, p9, p20]
Database fetch p1,p9,p20 để lấy giá/tồn kho mới nhất
Cách này giúp:
- Search tốt.
- Dữ liệu hiển thị quan trọng mới hơn.
Nhưng có chi phí:
- Thêm một bước query DB.
- Cần giữ thứ tự kết quả.
- Nếu nhiều id, query có thể nặng.
Không phải lúc nào cũng cần.
Nhưng với dữ liệu nhạy như giá/tồn kho, rất đáng cân nhắc.
---
32.20. Search trong AI Judge
AI Judge có thể cần search ở nhiều chỗ:
- Tìm bài nộp theo nội dung.
- Tìm feedback theo từ khóa.
- Tìm rubric.
- Tìm học viên/lớp/assignment.
- Tìm lỗi phổ biến trong feedback.
Database có thể đủ cho:
Tìm submission theo id.
Tìm job theo status.
Tìm học viên theo email.
Search engine hữu ích hơn cho:
Tìm tất cả feedback có nhắc "thiếu ví dụ".
Tìm bài nộp chứa một đoạn code/text.
Tìm rubric theo nội dung dài.
Tìm lỗi phổ biến trong nhận xét.
Nhưng kết quả chấm chính thức vẫn nằm trong database.
Search index chỉ giúp tìm.
---
32.21. Search autocomplete trong sản phẩm học online
User gõ:
py
Hệ thống gợi ý:
Python cơ bản
Python nâng cao
PyTorch
Autocomplete cần:
- Nhanh.
- Chịu nhiều request.
- Debounce frontend.
- Rate limit.
- Search/index phù hợp.
Với dữ liệu nhỏ, database prefix index có thể đủ.
Với dữ liệu lớn, search engine hoặc autocomplete service riêng sẽ tốt hơn.
Đừng để mỗi phím gõ tạo query database nặng.
---
32.22. Search analytics
Search không chỉ trả kết quả.
Nó còn tạo dữ liệu hành vi rất quý:
- Người dùng search gì?
- Query nào không có kết quả?
- Query nào dẫn đến mua/enroll?
- Query nào bị sửa nhiều?
- Kết quả nào được click?
Ví dụ:
Nhiều người search "machine learning" nhưng không click kết quả nào.
Có thể kết quả không tốt.
Hoặc thiếu khóa học phù hợp.
Search analytics nên đi vào hệ thống analytics, không nhất thiết vào database nghiệp vụ chính.
---
32.23. Search observability
Search cũng cần theo dõi:
- Query latency.
- Error rate.
- Indexing lag.
- Index size.
- Zero-result rate.
- Top queries.
- Click-through rate.
- Rebuild progress.
Indexing lag rất quan trọng:
ProductUpdated xảy ra lúc 10:00
Search index cập nhật lúc 10:05
lag = 5 phút
Nếu lag quá cao, user thấy dữ liệu cũ lâu.
Không có metric, rất khó biết search stale bao lâu.
---
32.24. Search security
Search rất dễ leak dữ liệu nếu không cẩn thận.
Ví dụ:
User chỉ được xem bài của lớp mình.
Search index chứa bài của mọi lớp.
Nếu query không filter theo quyền, user có thể thấy dữ liệu lớp khác.
Cần:
- Filter theo tenant/user/permission.
- Không index dữ liệu nhạy cảm nếu không cần.
- Tách index theo tenant nếu cần.
- Kiểm tra quyền khi mở chi tiết.
- Không trả snippet chứa dữ liệu private cho người không có quyền.
Search permission thường khó hơn tưởng tượng.
Với dữ liệu private, hãy rất cẩn thận.
---
32.25. Multi-tenant search
Với SaaS nhiều tenant, cần đảm bảo tenant này không thấy dữ liệu tenant khác.
Cách làm:
- Mỗi document có
tenant_id. - Mọi query bắt buộc filter
tenant_id. - Có thể tách index theo tenant lớn.
- Test bảo mật search.
Nếu dùng một index chung:
tenant_id là filter bắt buộc.
Đừng để frontend gửi tenant_id rồi server tin ngay.
Server phải lấy tenant từ auth/session.
Search leak dữ liệu cross-tenant là lỗi rất nặng.
---
32.26. Công cụ search phổ biến
Một vài lựa chọn:
PostgreSQL full-text search
Elasticsearch
OpenSearch
Meilisearch
Typesense
Solr
Algolia
Không cần học tất cả.
Chọn theo:
- Dữ liệu lớn hay nhỏ.
- Cần managed service không.
- Cần typo tolerance không.
- Cần autocomplete không.
- Cần filter/facet không.
- Team vận hành được gì.
- Search có phải core product không.
Ví dụ:
- PostgreSQL full-text search đủ cho hệ thống nhỏ/vừa.
- Meilisearch/Typesense dễ bắt đầu cho search sản phẩm/app.
- Elasticsearch/OpenSearch mạnh nhưng vận hành nặng hơn.
- Algolia tiện nếu chấp nhận managed cost/vendor.
---
32.27. Khi nào Kafka/event streaming liên quan đến search?
Nếu dữ liệu thay đổi nhiều và cần đồng bộ search ổn định, event stream có thể giúp.
Ví dụ:
ProductUpdated
CoursePublished
SubmissionIndexed
Search indexer đọc event và cập nhật index.
Nếu index lỗi, có thể replay event nếu stream còn lịch sử.
Nhưng với hệ thống nhỏ, không cần Kafka chỉ để cập nhật search.
Outbox + queue worker là đủ trong rất nhiều trường hợp.
---
32.28. Những lỗi phổ biến
Lỗi 1: Dùng LIKE cho search phức tạp quá lâu
Khi dữ liệu lớn và search cần ranking, LIKE bắt đầu đau.
Lỗi 2: Thêm Elasticsearch quá sớm
Search đơn giản nhưng tự thêm hạ tầng nặng.
Lỗi 3: Xem search index là source of truth
Checkout hoặc permission tin dữ liệu stale trong index.
Lỗi 4: Không có cơ chế rebuild index
Index lỗi không biết dựng lại từ đâu.
Lỗi 5: Không đo indexing lag
Không biết dữ liệu search trễ bao lâu.
Lỗi 6: Không filter theo quyền/tenant
Search leak dữ liệu private.
Lỗi 7: Mapping sai
Field cần filter chính xác lại index như text.
Lỗi 8: Không debounce autocomplete
Mỗi ký tự tạo request nặng.
---
32.29. Checklist thiết kế search
Khi thiết kế search, hãy hỏi:
- Search theo gì: id, tên, nội dung dài, nhiều field?
- Database search có đủ không?
- Dữ liệu lớn đến đâu?
- Có cần ranking không?
- Có cần typo/fuzzy không?
- Có cần autocomplete không?
- Có cần filter/facet không?
- Source of truth nằm ở đâu?
- Search index cập nhật bằng cách nào?
- Index được phép trễ bao lâu?
- Có cần rebuild index không?
- Mapping/schema index là gì?
- Có dữ liệu private không?
- Có cần filter theo user/tenant/permission không?
- Có đo query latency và indexing lag không?
- Có search analytics không?
Nếu chưa rõ các câu này, đừng vội chọn công cụ.
---
32.30. Bảng chọn nhanh
| Tình huống | Cách làm thường hợp | |---|---| | Tìm theo id/email/mã đơn | Database index | | Filter order/payment/job theo status/date | Database | | Search product/course theo text đơn giản | Database full-text hoặc search engine nhẹ | | Search hàng triệu sản phẩm với ranking/facet | Search engine | | Autocomplete traffic cao | Search/autocomplete index + debounce | | Search feedback/submission nội dung dài | Search engine đáng cân nhắc | | Checkout giá/tồn kho | Kiểm tra database/source of truth | | Search dữ liệu private | Filter permission rất chặt | | Rebuild index cần replay | Event/outbox/stream tùy scale |
---
32.31. Tóm tắt bằng AI Judge
Trong AI Judge:
Database vẫn nên giữ:
Submission
GradingJob
GradingResult
Rubric
Assignment
Search có thể giúp:
Tìm feedback có nhắc một lỗi.
Tìm submission theo nội dung.
Tìm rubric theo text.
Tìm assignment/course nhanh hơn.
Luồng đồng bộ:
GradingResult saved
-> outbox event GradingResultCreated
-> search indexer cập nhật index
Nếu search index trễ vài giây, thường chấp nhận được.
Nhưng khi hiển thị điểm chính thức hoặc quyền xem feedback, phải kiểm tra database/permission thật.
Search giúp tìm.
Database giữ sự thật.
---
32.32. Kết luận của chương
Search là một nhu cầu đọc đặc biệt.
Database có thể search tốt ở mức đơn giản, nhất là khi tìm theo id, email, status, date, hoặc full-text vừa phải.
Search engine hữu ích khi cần:
- Full-text search mạnh.
- Ranking.
- Fuzzy search.
- Autocomplete.
- Facet/filter lớn.
- Search trên dữ liệu lớn.
- Trải nghiệm search là phần quan trọng của sản phẩm.
Nhưng search engine kéo theo chi phí:
- Đồng bộ dữ liệu.
- Eventual consistency.
- Mapping/index schema.
- Rebuild.
- Security/filter quyền.
- Monitoring indexing lag.
Thông điệp quan trọng nhất:
> Search index không phải source of truth. Nó là bản phục vụ tìm kiếm. Dữ liệu nghiệp vụ quan trọng vẫn phải được kiểm tra ở nguồn sự thật trước khi quyết định.
Ở chương tiếp theo, ta sẽ nói về file và object storage: vì sao không nên lưu file lớn trong database, object storage là gì, presigned URL hoạt động ra sao, và CDN giúp phân phối file nhanh hơn như thế nào.