Trả lời theo quy mô, đừng nhảy thẳng vào Elasticsearch. Dưới khoảng vài triệu sản phẩm, PostgreSQL làm được cả ba yêu cầu.
Tìm không dấu: dùng extension unaccent để bỏ dấu tiếng Việt, và lưu kết quả vào cột sinh sẵn có index, không gọi hàm trong WHERE (gọi hàm làm mất index).
CREATE EXTENSION IF NOT EXISTS unaccent;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
ALTER TABLE products
ADD COLUMN name_norm text
GENERATED ALWAYS AS (lower(immutable_unaccent(name))) STORED;
CREATE INDEX products_name_trgm ON products USING gin (name_norm gin_trgm_ops);pg_trgm cho phép LIKE '%...%' và chịu được lỗi gõ sai; full-text search (tsvector + GIN) hợp hơn khi truy vấn nhiều từ và cần xếp hạng theo độ liên quan. Nhiều hệ thống dùng cả hai: FTS cho câu dài, trigram cho gợi ý gõ dở.
Lọc nhiều thuộc tính: build query động nhưng luôn qua tham số hoá (không nối chuỗi), giữ danh sách trường được phép lọc/sắp xếp ở dạng allowlist — cho client tự truyền tên cột vào ORDER BY là lỗ hổng. Index tổ hợp đặt theo cột lọc chọn lọc nhất; các thuộc tính động (màu, size) thì cân nhắc jsonb + GIN.
Phân trang: với danh sách sâu, dùng keyset (WHERE (sort_key, id) < (:last_key, :last_id)) thay OFFSET lớn.
Chuyển sang Elasticsearch/OpenSearch khi thật sự cần đếm facet trên nhiều triệu bản ghi, chấm điểm liên quan phức tạp hoặc gợi ý tức thời — và phải chấp nhận thêm luồng đồng bộ dữ liệu cùng độ trễ.