Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 49 — Search Architecture — Elasticsearch, OpenSearch

Architecture Decision-Making Bài 49/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là kiến trúc sư của một sàn thương mại điện tử như Tiki hay Shopee. Người dùng gõ "áo thun nam cotton" vào ô tìm kiếm, và họ mong đợi kết quả trả về trong dưới 200 mili-giây, đúng ý, có gợi ý sửa lỗi chính tả, có lọc theo giá và thương hiệu. Nếu bạn xử lý điều này bằng một câu SELECT * FROM products WHERE name LIKE '%áo thun%' trên PostgreSQL, hệ thống của bạn sẽ sụp đổ ngay khi lượng truy vấn tăng lên — và ngay cả khi nó không sụp, kết quả trả về cũng nghèo nàn đến mức người dùng bỏ đi.

Đây chính là lý do bài học này quan trọng đối với một người ra quyết định kiến trúc. Tìm kiếm (search) không phải là một tính năng phụ mà bạn "gắn thêm" vào cơ sở dữ liệu quan hệ. Nó là một loại workload hoàn toàn khác, đòi hỏi một loại hạ tầng chuyên biệt: search engine. Trong bài này, chúng ta sẽ đi sâu vào lý do vì sao SQL không đủ, search engine giải quyết vấn đề gì, và làm sao để bạn ra quyết định chọn Elasticsearch hay OpenSearch — hai lựa chọn phổ biến nhất hiện nay — một cách có cơ sở, thay vì theo trào lưu.

Điều bạn cần rút ra không phải là "cách viết query Elasticsearch" (đó là việc của tài liệu chính thức), mà là khi nào bạn cần một search engine riêng, đánh đổi gì khi đưa nó vào kiến trúc, và cách ghi lại quyết định đó.

Khái niệm cốt lõi

Vì sao SQL LIKE '%query%' không đủ?

Có ba giới hạn nền tảng khiến cơ sở dữ liệu quan hệ không phù hợp cho tìm kiếm nghiêm túc.

Thứ nhất, LIKE '%query%' không dùng được index. Khi bạn đặt dấu % ở đầu chuỗi, database buộc phải quét toàn bộ bảng (full table scan) vì B-tree index chỉ hoạt động khi biết tiền tố. Với một bảng 10 triệu sản phẩm, mỗi truy vấn tìm kiếm sẽ đọc toàn bộ 10 triệu dòng. Khi có vài trăm người dùng tìm kiếm đồng thời, database của bạn nghẹt thở.

Thứ hai, không có relevance scoring (chấm điểm liên quan). Với LIKE, một sản phẩm hoặc khớp hoặc không khớp — nhị phân. Nhưng người dùng muốn kết quả liên quan nhất xếp lên đầu. Nếu ai đó tìm "iPhone 15 Pro Max", kết quả khớp cả ba từ phải xếp trên kết quả chỉ khớp mỗi từ "iPhone". Search engine dùng thuật toán như TF-IDF hoặc BM25 để tính điểm liên quan cho từng tài liệu, rồi sắp xếp theo điểm đó.

Thứ ba, không có tính năng full-text thực thụ. Đây là điểm ghi chú gốc của bài bỏ ngỏ, và cũng là phần thú vị nhất:

  • Stemming (rút gọn về từ gốc): người dùng gõ "running" nhưng tài liệu chứa "run" — search engine hiểu chúng cùng gốc. Với tiếng Việt, vấn đề phức tạp hơn: tách từ (word segmentation) cho "máy giặt cửa trước" cần hiểu đây là ba khái niệm.
  • Synonyms (từ đồng nghĩa): người dùng gõ "điện thoại" nhưng sản phẩm ghi "smartphone" — bạn muốn cả hai khớp nhau.
  • Fuzzy matching (khớp mờ): người dùng gõ sai "sămung" thay vì "samsung" — search engine vẫn tìm ra nhờ khoảng cách chỉnh sửa (edit distance).
  • Faceted search (lọc theo khía cạnh): hiển thị số lượng kết quả theo từng thương hiệu, từng khoảng giá, ngay bên cạnh kết quả.
  • Highlighting, autocomplete, gợi ý "did you mean".
Không một tính năng nào trong danh sách trên khả thi ở quy mô lớn với SQL thuần.

Inverted index — trái tim của search engine

Điểm khác biệt kỹ thuật cốt lõi giữa search engine và database quan hệ nằm ở cấu trúc dữ liệu. Database dùng B-tree ánh xạ "dòng → giá trị". Search engine dùng inverted index (chỉ mục ngược), ánh xạ ngược lại: "từ khóa → danh sách tài liệu chứa từ đó".

Ví dụ, sau khi index ba câu, inverted index trông như:

"áo"    → [doc1, doc3, doc7]
"thun"  → [doc1, doc7]
"nam"   → [doc3, doc7]

Khi tìm "áo thun nam", engine chỉ cần lấy giao/hợp của các danh sách này — cực nhanh, không cần quét toàn bộ. Đây là lý do search engine trả kết quả trong vài mili-giây trên hàng chục triệu tài liệu. Elasticsearch và OpenSearch đều xây trên thư viện Apache Lucene, vốn là nơi cài đặt inverted index này.

Elasticsearch vs OpenSearch — hiểu đúng bối cảnh

Đây là câu hỏi khiến nhiều đội bối rối, vì hai sản phẩm gần như giống hệt về mặt kỹ thuật. Lý do có tới hai cái tên là chuyện giấy phép (licensing).

  • Elasticsearch do công ty Elastic phát triển. Đầu 2021, Elastic đổi giấy phép từ Apache 2.0 (mã nguồn mở tự do) sang SSPL/Elastic License — vốn hạn chế việc các nhà cung cấp cloud bán Elasticsearch dưới dạng dịch vụ quản lý.
  • OpenSearch là bản fork (nhánh tách ra) do AWS khởi xướng từ Elasticsearch 7.10 — phiên bản cuối còn giấy phép Apache 2.0. OpenSearch giữ giấy phép Apache 2.0 hoàn toàn tự do.
Về mặt tính năng nền tảng (inverted index, BM25, aggregation, faceting), hai bên tương đương. Khác biệt nằm ở:

Khía cạnhElasticsearchOpenSearch
Giấy phépElastic License / SSPL (từ 2024 thêm AGPL)Apache 2.0 hoàn toàn tự do
Hệ sinh tháiElastic Stack (Kibana, Beats, APM) trưởng thành hơnDashboards fork từ Kibana, cộng đồng đang lớn
Managed serviceElastic Cloud, hoặc Elastic Cloud trên AWS/GCP/AzureAmazon OpenSearch Service tích hợp sâu với AWS
Tính năng mới (ví dụ vector search, ML)Ra sớm, thường tiên phongTheo sau nhưng bắt kịp nhanh
Nguyên tắc ra quyết định đơn giản: nếu bạn đã ở sâu trong hệ sinh thái AWS và muốn managed service tích hợp IAM, VPC, CloudWatch — Amazon OpenSearch Service là lựa chọn tự nhiên. Nếu bạn muốn hệ sinh thái công cụ trưởng thành nhất, tính năng tiên phong, và chấp nhận ràng buộc giấy phép — Elasticsearch/Elastic Cloud. Nếu bạn cực kỳ quan tâm tránh vendor lock-in và giấy phép tự do tuyệt đối — OpenSearch.

Search engine KHÔNG phải database chính (source of truth)

Đây là nguyên tắc kiến trúc quan trọng nhất của bài. Search engine là một index phái sinh (derived index), không phải nơi lưu trữ nguồn dữ liệu gốc. Dữ liệu gốc vẫn nằm ở PostgreSQL/MySQL của bạn; search engine chỉ giữ một bản sao được tối ưu cho việc tìm kiếm, đồng bộ từ nguồn gốc. Nếu index hỏng, bạn phải có khả năng dựng lại (reindex) từ nguồn gốc bất kỳ lúc nào. Vi phạm nguyên tắc này — coi Elasticsearch là database chính — là một trong những sai lầm đắt giá nhất mà đội kỹ thuật hay mắc.

Tình huống thực tế

Ví dụ 1 — Sàn TMĐT Việt Nam vượt trần PostgreSQL

Một startup TMĐT tại TP.HCM (gọi là "ChợViệt") khởi đầu với 50.000 sản phẩm, dùng PostgreSQL và ILIKE cho tìm kiếm. Mọi thứ ổn trong năm đầu. Sang năm thứ hai, catalog tăng lên 3 triệu sản phẩm khi họ mở marketplace cho người bán thứ ba. Đội vận hành phát hiện thời gian phản hồi tìm kiếm nhảy từ 80ms lên 4-6 giây vào giờ cao điểm, CPU của database master chạm 95%, và các truy vấn ghi (đặt hàng, cập nhật tồn kho) bắt đầu bị chậm theo vì tranh chấp tài nguyên.

Họ đưa Amazon OpenSearch Service vào. Sản phẩm được index qua một luồng đồng bộ đọc từ binlog của MySQL (Change Data Capture) đẩy vào OpenSearch qua một service trung gian. Kết quả: p99 latency tìm kiếm giảm về 120ms, database chính được giải phóng khỏi tải đọc nặng, và họ bổ sung được faceted search theo thương hiệu + khoảng giá, autocomplete, và sửa lỗi chính tả tiếng Việt.

Bài học: Ngưỡng chuyển đổi không phải là số lượng sản phẩm cố định, mà là thời điểm workload tìm kiếm bắt đầu cạnh tranh tài nguyên với workload giao dịch. Tách search ra một hệ thống riêng đồng thời bảo vệ luôn database giao dịch.

Ví dụ 2 — Nền tảng logistics chọn OpenSearch cho log, không phải tìm kiếm sản phẩm

Một công ty giao vận ở Đông Nam Á cần tìm kiếm trong hàng terabyte log vận hành để truy vết đơn hàng khi khách khiếu nại: "đơn X đang ở đâu, đã qua những trạm nào". Ở đây use-case không phải tìm kiếm sản phẩm mà là log analytics — một ứng dụng cực phổ biến khác của search engine, thường gọi là stack ELK/EFK.

Họ chọn Amazon OpenSearch Service vì log của họ đã chảy qua hạ tầng AWS, và OpenSearch Dashboards cho phép đội vận hành xây bảng điều khiển truy vết mà không cần lập trình viên. Họ áp dụng Index Lifecycle Management: log 7 ngày gần nhất ở tầng "hot" (SSD nhanh), 8-30 ngày ở tầng "warm", sau 90 ngày tự động xóa. Nhờ vậy chi phí lưu trữ giảm 60% so với giữ tất cả ở tầng nóng.

Bài học: Search engine không chỉ dùng cho ô tìm kiếm của người dùng cuối. Log/metrics analytics là mảnh đất lớn của Elasticsearch/OpenSearch. Và khi dữ liệu chỉ tăng theo thời gian, quản lý vòng đời index (lifecycle) là quyết định kiến trúc quan trọng ngang với việc chọn engine.

Ví dụ 3 — Một team lạm dụng Elasticsearch làm database chính

Một fintech (giả định "PayNhanh") lưu toàn bộ dữ liệu giao dịch chỉ trong Elasticsearch vì "nó nhanh và query linh hoạt". Sau một sự cố cluster mất một node và bị split-brain, một phần dữ liệu index bị hỏng. Vì không có nguồn gốc nào khác để dựng lại, họ mất dữ liệu giao dịch thật — hậu quả nghiêm trọng với một công ty tài chính.

Bài học: Elasticsearch/OpenSearch không được thiết kế để đảm bảo tính nhất quán giao dịch mạnh (ACID) như database quan hệ. Nó nghiêng về availability trong đánh đổi CAP. Luôn giữ source of truth ở một database phù hợp giao dịch, và coi search engine chỉ là index phái sinh có thể dựng lại.

Hướng dẫn từng bước

Đây là quy trình ra quyết định và triển khai khi bạn cân nhắc đưa search engine vào kiến trúc.

Bước 1 — Xác định bạn có thực sự cần search engine chưa. Đừng thêm hạ tầng vội. Nếu catalog dưới ~100.000 bản ghi và tìm kiếm đơn giản, hãy cân nhắc full-text search tích hợp sẵn của PostgreSQL (tsvector, GIN index) trước. Nó xử lý được stemming và relevance cơ bản mà không cần thêm hệ thống. Chỉ khi bạn cần faceting nâng cao, quy mô hàng triệu bản ghi, hoặc log analytics thì mới cần engine riêng.

Bước 2 — Chọn engine dựa trên hệ sinh thái, không dựa trên tính năng. Vì Elasticsearch và OpenSearch tương đương về lõi, hãy quyết định theo: bạn đã ở đâu (AWS → OpenSearch Service), yêu cầu giấy phép (tự do tuyệt đối → OpenSearch), và độ trưởng thành công cụ (→ Elastic).

Bước 3 — Thiết kế luồng đồng bộ dữ liệu (indexing pipeline). Quyết định cách dữ liệu từ source of truth chảy vào index. Hai mô hình chính:

  • Dual write: ứng dụng ghi đồng thời vào database và search engine. Đơn giản nhưng dễ lệch dữ liệu nếu một bên lỗi.
  • Change Data Capture (CDC): đọc từ binlog/WAL của database, đẩy sang search engine. Đáng tin cậy hơn, giữ database là nguồn duy nhất.
Bước 4 — Thiết kế mapping và analyzer. Định nghĩa cách các trường được phân tích. Với tiếng Việt, bạn cần cấu hình analyzer tách từ phù hợp và xử lý dấu. Xác định trường nào cần full-text (dùng text) và trường nào chỉ lọc/sắp xếp (dùng keyword).

Bước 5 — Lên kế hoạch reindex. Giả định index sẽ phải dựng lại (thay đổi mapping, khôi phục sự cố). Dùng bí danh (alias) trỏ tới index thật, để bạn reindex sang index mới rồi chuyển alias mà không downtime.

Bước 6 — Cấu hình cluster: shard, replica, và tài nguyên. Xác định số shard hợp lý (quá nhiều shard nhỏ gây lãng phí), số replica cho khả năng chịu lỗi và tải đọc, và dung lượng heap. Với dữ liệu tăng theo thời gian, thiết lập Index Lifecycle Management ngay từ đầu.

Bước 7 — Giám sát và ghi ADR. Theo dõi latency truy vấn, tình trạng cluster (xanh/vàng/đỏ), và độ trễ đồng bộ index. Ghi lại quyết định vào một ADR: vì sao chọn engine này, luồng đồng bộ, và rõ ràng khẳng định "search engine là index phái sinh, không phải source of truth".

Lỗi thường gặp & mẹo

Lỗi 1 — Coi search engine là database chính. Như ví dụ PayNhanh, đây là sai lầm nghiêm trọng nhất. Mẹo: luôn giữ khả năng reindex từ nguồn gốc, và thử nghiệm dựng lại index định kỳ.

Lỗi 2 — Chọn engine theo trào lưu thay vì theo bối cảnh. Nhiều đội chọn Elasticsearch chỉ vì "ai cũng dùng". Mẹo: nếu bạn ở AWS, OpenSearch Service thường giảm đáng kể chi phí vận hành nhờ tích hợp sẵn.

Lỗi 3 — Bỏ qua xử lý tiếng Việt. Dùng analyzer mặc định (chuẩn tiếng Anh) khiến tìm kiếm tiếng Việt kém: không tách từ đúng, không xử lý dấu. Mẹo: cấu hình analyzer tiếng Việt, và cân nhắc chuẩn hóa dấu để "cà phê" và "ca phe" cùng khớp.

Lỗi 4 — Dual write mà không xử lý lỗi. Nếu ghi vào database thành công nhưng ghi vào search engine thất bại, index bị lệch âm thầm. Mẹo: ưu tiên CDC, hoặc có hàng đợi retry và job đối soát định kỳ.

Lỗi 5 — Over-sharding. Tạo hàng trăm shard cho vài GB dữ liệu làm cluster chậm và tốn bộ nhớ. Mẹo: bắt đầu với ít shard, mỗi shard tầm 10-50GB, mở rộng khi cần.

Lỗi 6 — Không đặt giới hạn tài nguyên cho query nặng. Aggregation hoặc deep pagination có thể làm sập cluster. Mẹo: dùng search_after thay vì phân trang sâu, và đặt timeout cho query.

Bài tập thực hành

  • Phân tích ngưỡng chuyển đổi: Cho một ứng dụng blog có 200.000 bài viết, tìm kiếm theo tiêu đề và nội dung, khoảng 30 truy vấn/giây. Hãy lập luận: nên dùng PostgreSQL full-text search hay đưa vào một search engine riêng? Liệt kê các yếu tố khiến bạn nghiêng về mỗi bên.
  • So sánh và chọn engine: Viết một bảng so sánh Elasticsearch và Amazon OpenSearch Service cho ba tình huống: (a) một startup đã dùng toàn bộ AWS, (b) một công ty on-premise không dùng cloud, (c) một dự án cực kỳ nhạy cảm với vendor lock-in. Với mỗi tình huống, chọn engine và giải thích trong 2-3 câu.
  • Thiết kế indexing pipeline: Vẽ sơ đồ luồng dữ liệu cho một hệ thống TMĐT có MySQL là source of truth và OpenSearch làm search index. Chỉ rõ dùng dual write hay CDC, và giải thích cách bạn xử lý khi đồng bộ thất bại.
  • Viết ADR: Soạn một ADR ngắn (theo cấu trúc bạn đã học ở Bài 4) cho quyết định "Đưa OpenSearch vào để thay thế tìm kiếm bằng SQL LIKE", nêu rõ context, các phương án đã cân nhắc, quyết định, và hệ quả — đặc biệt khẳng định vai trò index phái sinh của nó.

Tóm tắt

Search engine chuyên biệt tồn tại vì cơ sở dữ liệu quan hệ không sinh ra để làm tìm kiếm ở quy mô lớn: LIKE '%query%' không dùng được index, không có relevance scoring, và thiếu các tính năng full-text như stemming, synonyms, fuzzy matching và faceting. Trái tim của search engine là inverted index — ánh xạ từ khóa tới danh sách tài liệu — cho phép trả kết quả trong vài mili-giây trên hàng triệu bản ghi, với điểm liên quan tính bằng BM25.

Elasticsearch và OpenSearch tương đương về mặt kỹ thuật (cùng dựa trên Lucene); khác biệt cốt lõi nằm ở giấy phép và hệ sinh thái. Chọn theo bối cảnh: ở sâu trong AWS thì OpenSearch Service, cần công cụ trưởng thành thì Elastic, cần giấy phép tự do tuyệt đối thì OpenSearch. Nguyên tắc kiến trúc quan trọng nhất: search engine là index phái sinh, không phải source of truth — luôn giữ database gốc và khả năng dựng lại index. Cuối cùng, đừng thêm hạ tầng vội: nếu quy mô còn nhỏ, full-text search của PostgreSQL có thể đã đủ. Khi quyết định, hãy ghi lại bằng một ADR rõ ràng.