Triển khai Chức năng Tìm kiếm: So sánh PostgreSQL Full-Text, Algolia và Meilisearch

07 tháng 4, 2026·4 phút đọc

Khi dữ liệu của bạn tăng lên, câu lệnh LIKE đơn giản không còn đáp ứng được nhu cầu về hiệu suất và độ chính xác. Bài viết này sẽ phân tích chi tiết ba giải pháp tìm kiếm hàng đầu là PostgreSQL Full-Text Search, Algolia và Meilisearch, cùng với hướng dẫn lựa chọn công cụ phù hợp nhất cho ứng dụng của bạn.

Triển khai Chức năng Tìm kiếm: So sánh PostgreSQL Full-Text, Algolia và Meilisearch

Tìm kiếm thường khó khăn hơn vẻ bề ngoài của nó. Câu lệnh LIKE '%query%' hoạt động tốt cho đến khi:

  • Bảng dữ liệu của bạn đạt 100.000 dòng (dẫn đến quét tuần tự - Seq Scan, gây chậm).
  • Người dùng gõ sai chính tả (không có tính năng khớp mờ - fuzzy matching).
  • Người dùng tìm kiếm bằng nhiều ngôn ngữ khác nhau.
  • Người dùng mong đợi kết quả được sắp xếp theo mức độ liên quan.

Đó là lúc bạn cần một giải pháp tìm kiếm thực thụ.

Tùy chọn 1: PostgreSQL Full-Text Search

Đây là lựa chọn đủ tốt cho hầu hết các ứng dụng mà không cần thêm hạ tầng phức tạp.

-- Thêm cột tsvector để tìm kiếm nhanh
ALTER TABLE posts ADD COLUMN search_vector tsvector;

-- Populate nó với dữ liệu
UPDATE posts SET search_vector = 
  to_tsvector('english',
    coalesce(title, '') || ' ' || coalesce(content, '') || ' ' || coalesce(tags::text, '')
  );

-- Tự động cập nhật khi dữ liệu thay đổi
CREATE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
  NEW.search_vector := to_tsvector('english',
    coalesce(NEW.title, '') || ' ' || coalesce(NEW.content, '')
  );
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER posts_search_vector_update
  BEFORE INSERT OR UPDATE ON posts
  FOR EACH ROW EXECUTE FUNCTION update_search_vector();

-- Tạo chỉ mục GIN để tìm kiếm nhanh
CREATE INDEX posts_search_idx ON posts USING GIN(search_vector);
// Tìm kiếm có tính điểm xếp hạng (ranking)
async function searchPosts(query: string) {
  return prisma.$queryRaw`
    SELECT
      id,
      title,
      ts_rank(search_vector, plainto_tsquery('english', ${query})) AS rank,
      ts_headline('english', content, plainto_tsquery('english', ${query}),
        'MaxWords=50, MinWords=20'
      ) AS excerpt
    FROM posts
    WHERE search_vector @@ plainto_tsquery('english', ${query})
    ORDER BY rank DESC
    LIMIT 20
  `;
}

Ưu điểm: Không cần hạ tầng bổ sung, tính nhất quán giao dịch (transactionally consistent) cao.

Hạn chế: Không chấp nhận sai chính tả, thiên về tiếng Anh, khả năng tùy chỉnh độ liên kết còn hạn chế.

Tùy chọn 2: Algolia

Dịch vụ tìm kiếm được quản lý (Managed search). Mang lại trải nghiệm người dụng (UX) tốt nhất trong ngành.

npm install algoliasearch
import algoliasearch from 'algoliasearch';

const client = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_API_KEY!);
const index = client.initIndex('posts');

// Lưu tài liệu vào chỉ mục
await index.saveObject({
  objectID: post.id,
  title: post.title,
  content: post.content.slice(0, 10000), // Algolia có giới hạn kích thước
  author: post.author.name,
  tags: post.tags,
  publishedAt: post.publishedAt.getTime(),
});

// Thực hiện tìm kiếm
const { hits } = await index.search(query, {
  attributesToRetrieve: ['title', 'author', 'tags'],
  attributesToHighlight: ['title', 'content'],
  hitsPerPage: 20,
  typoTolerance: true, // Tìm được 'recat' -> React
});

Ưu điểm: Tốc độ cực nhanh (dưới 10ms), chấp nhận sai chính tả tốt, bảng điều khiển phân tích dữ liệu (dashboard) xuất sắc.

Chi phí: Gói miễn phí: 10.000 tìm kiếm/tháng, 10.000 bản ghi. Chi phí tăng rất nhanh sau khi vượt quá giới hạn.

Tùy chọn 3: Meilisearch

Lựa thay thế mã nguồn mở cho Algolia. Bạn có thể tự chủ server (self-host) hoặc sử dụng Meilisearch Cloud.

# Tự chủ với Docker
docker run -d -p 7700:7700 -v meilidata:/meili_data getmeili/meilisearch

npm install meilisearch
import { MeiliSearch } from 'meilisearch';

const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: process.env.MEILI_API_KEY });
const index = client.index('posts');

// Cấu hình các thuộc tính tìm kiếm và xếp hạng
await index.updateSettings({
  searchableAttributes: ['title', 'content', 'tags'],
  rankingRules: [
    'words',
    'typo',
    'proximity',
    'attribute',
    'sort',
    'exactness'
  ],
  typoTolerance: {
    enabled: true,
    minWordSizeForTypos: { oneTypo: 5, twoTypos: 9 },
  },
});

// Thêm tài liệu
await index.addDocuments(posts.map(p => ({ id: p.id, ...p })));

// Tìm kiếm
const results = await index.search(query, {
  limit: 20,
  attributesToHighlight: ['title', 'content'],
  highlightPreTag: '<mark>',
  highlightPostTag: '</mark>',
});

Ưu điểm: Chấp nhận sai chính tả, tốc độ nhanh, mã nguồn mở, chi phí dễ dự đoán.

Hạn chế: Ít trưởng thành hơn Algolia, hệ sinh thái (ecosystem) còn nhỏ hơn.

Giữ cho Dữ liệu Tìm kiếm Đồng bộ

Khi sử dụng các giải pháp bên ngoài như Algolia hay Meilisearch, việc đồng bộ dữ liệu từ cơ sở dữ liệu chính sang chỉ mục tìm kiếm là rất quan trọng.

// Sau bất kỳ thao tác thay đổi bài viết nào, đồng bộ hóa sang chỉ mục tìm kiếm
async function createPost(data: PostInput) {
  const post = await db.posts.create({ data });

  // Cập nhật chỉ mục bất đồng bộ (không chặn phản hồi)
  setImmediate(async () => {
    await searchIndex.saveObject({
      objectID: post.id,
      title: post.title,
      content: post.content,
    });
  });

  return post;
}

// Hoặc sử dụng background job để độ tin cậy cao hơn
await queue.add('sync-search', { postId: post.id, operation: 'upsert' });

Hướng dẫn Lựa chọn

Tình huốngKhuyến nghị
< 100k bản ghi, tìm kiếm cơ bảnPostgreSQL FTS
Cần chấp nhận sai chính tảMeilisearch (tự chủ server)
Cần UX tốt nhất, có ngân sáchAlgolia
Mã nguồn mở, chi phí dự đoán đượcMeilisearch Cloud
Nội dung đa ngôn ngữElasticsearch hoặc Meilisearch

Tích hợp tìm kiếm với các adapter cho PostgreSQL FTS và Meilisearch có thể tìm thấy trong Whoff Agents AI SaaS Starter Kit.

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗