Tìm kiếm fulltext trong redisearch

Tìm kiếm fulltext trong redisearch

Tin ngắn hàng ngày dành cho bạn
  • Bắt đầu kỳ nghỉ tết rồi nên mình cũng không đăng bài nữa. Hẹn gặp lại các bạn qua tết nha 😁

    » Xem thêm
  • Tiếp tục về jj. Đang thắc mắc là nó mới thế liệu có ai làm mấy phần mềm dạng GUI cho dễ nhìn chưa. Kiểu giống như git thì có quá nhiều rồi không đếm xuể.

    May quá, tác giả tổng hợp lại luôn rồi Community-built tools around Jujutsu 🥳

    » Xem thêm
  • Turso thông báo rằng họ đang viết lại SQLite bằng Rust. Thế là lại có thêm một bằng chứng nữa cũng cố cho câu nói Rust đang "tái định nghĩa" lại nhiều thứ.

    Nhưng nguyên nhân sâu xa mới thú vị. Tại sao họ lại làm vậy? Ai cũng biết SQLite là nguồn mở, ai cũng có thể tạo bản sao (fork) để chỉnh sửa lại theo ý mình. Lẽ nào nhóm của Turso không thích hoặc không tin vào C - vốn là ngôn ngữ dùng để cấu thành SQLite.

    Mình xin kể chuyện một chút. Turso là một bên cung cấp dịch vụ máy chủ cơ sở dữ liệu dựa trên SQLite, họ đã thực hiện một vài tùy chỉnh trên bản sao của SQLite để phục vụ cho mục đích của mình, gọi nó là libSQL. Họ "hào phóng" cho cộng đồng đóng góp thoải mái.

    Quay trở lại SQLite là mã nguồn mở chứ không phải là đóng góp mở. Chỉ có một nhóm người đứng đằng sau duy trì mã nguồn này, và họ không tiếp nhận yêu cầu kéo (pull request) từ những người khác. Đồng nghĩa mọi thay đổi hoặc tính năng đều là của nhóm người này tạo ra. Có vẻ như SQLite rất phổ biến nhưng cộng đồng không thể làm điều mà họ muốn là đóng góp cho sự phát triển của nó.

    Chúng ta biết rằng hầu hết ứng dụng mã nguồn mở thường đi kèm với một thư mục "tests" với các bài kiểm tra rất nghiêm ngặt. Điều đó giúp cho sự cộng tác trong phát triển trở nên dễ dàng hơn. Nếu muốn chỉnh sửa hoặc thêm một tính năng mới, trước hết bạn cần phải đảm bảo sự thay đổi vượt qua được tất cả bài kiểm tra. Nhiều thông tin cho rằng SQLite không công khai bộ kiểm tra này. Điều này vô tình gây khó khăn cho những ai muốn chỉnh sửa mã nguồn. Vì họ không chắc chắn rằng liệu triển khai mới của mình có phù hợp với những tính năng cũ hay không.

    tursodatabase/limbo là dự án viết lại SQLite bằng Rust đã nhắc đến ở đầu bài. Họ nói rằng nó hoàn toàn tương thích với SQLite và nguồn mở hoàn toàn. limbo đang trong giai đoạn hoàn thiện. Chúng ta hãy chờ xem kết quả trong tương lai thế nào nhé. Bài viết chi tiết tại Introducing Limbo: A complete rewrite of SQLite in Rust.

    » Xem thêm

Vấn đề

Bài toán tìm kiếm dữ liệu sao cho thật nhanh và chính xác nhất luôn là vấn đề muôn thuở của các developer. Tùy mục đích, tùy bài toán và tài nguyên cho phép mà chúng ta có thể lựa chọn những công cụ và phương pháp khác nhau.

Ví dụ khi bạn thao tác với một tập dữ liệu nhỏ thì bạn có thể sử dụng toán tử LIKE trong sql, nhưng khi dữ liệu trở nên nhiều hơn thì LIKE không còn là phương pháp tối ưu nữa. Lúc đó bạn có thể chuyển sang sử dụng những module hỗ trợ fulltext search có trong cơ sở dữ liệu đang sử dụng. Tuy nhiên những module này cũng chỉ là giải pháp tạm thời vì chúng có thể không cung cấp được hết các tính năng mạnh mẽ khác so với công cụ fulltext khác đang có mặt trên thế giới.

Elasticsearch và Apache Solr là hai thư viện rất mạnh mẽ và được cộng đồng sử dụng rất phổ biến, tuy nhiên chúng lại yêu cầu một phần cứng khá "khiêm tốn" không dễ dàng để tiếp cận cho những bạn nào phát triển dự án với mức kinh phí cũng khiêm tốn.

Redisearch ra đời dựa trên redis, nó cung cấp một cỗ máy tìm kiếm rất mạnh mẽ cùng với mức "tiêu thụ" tài nguyên ít nhất mà bạn có thể dễ dàng tích hợp vào trong dự án của mình. Cho những bạn chưa biết thì redis là một cơ sở dữ liệu dạng key-value mà dữ liệu được lưu trữ trong bộ nhớ truy cập ngẫu nhiên (RAM) cho tốc độ truy xuất rất nhanh nên thường được sử dụng để làm bộ nhớ đệm (cache). Tuy là nhỏ bé nhưng nó cũng không kém phần mạnh mẽ so với hai đàn anh đi trước. Trong bài viết này chúng ta hãy xem redisearch có thể làm được những gì nhé.

Tạo index

Việc cần làm đầu tiên là cần phải tạo một index để có thể tìm kiếm. Index như là một cách để khai báo cho cỗ máy tìm kiếm biết dữ liệu của bạn nên được xử lý như thế nào cho tối ưu nhất.

Tạo index rất đơn giản trong redisearch, ví dụ tôi tạo một index phục vụ cho việc tìm kiếm bài viết gồm có ba trường dữ liệu là title, content và created_at tương ứng với tiêu đề, nội dung và ngày tạo bài viết.

FT.CREATE article ON HASH PREFIX 1 article: SCHEMA title TEXT WEIGHT 5.0 content TEXT created_at NUMERIC SORTABLE

Index của tôi có tên là article, ở trường title tôi đặt WEIGHT = 5 để kết quả tìm kiếm trường dữ liệu title sẽ ưu tiên hơn so với content, created_at là SORTABLE để có thể sắp xếp thứ tự kết quả tìm kiếm. Nếu không khai báo SORTABLE thì bạn sẽ không thể sắp xếp được kết quả tìm kiếm.

Được rồi, sau khi tạo xong index thì chúng ta sẽ tìm hiểu cách tìm kiếm dữ liệu nhé.

Nguyên tắc tìm kiếm

Trước khi đến với cú pháp tìm kiếm, bạn cần biết một vài nguyên tắc tìm kiếm của redisearch:

  • Khi tìm kiếm một cụm từ ví dụ như hello world thì đơn giản là bạn đang tìm kiếm những đoạn có chứa cả hai từ hello và (AND) world.
  • Nếu muốn tìm kiếm chính xác cụm từ hello world thì bạn phải đặt nó trong dấu nháy kép (""). Ví dụ "hello world".
  • Khi muốn tìm kiếm theo kiểu có chứa hello hoặc world (OR) thì ngăn cách chúng bằng dấu |. Ví dụ hello|world.
  • Khi muốn tìm kiếm NOT thì sử dụng dấu -. Ví dụ tìm kiếm có chứa hello nhưng không chứa world thì là hello -world. Bạn cũng có thể kết hợp NOT nhiều từ bằng cách kết hợp với OR, ví dụ tìm kiếm trong trường title không chứa từ hello hoặc world: -@title:(hello|world).
  • Mặc định nếu không chỉ định tìm kiếm dữ liệu trên trường nào thì redisearch sẽ tìm kiếm trên tất cả các trường dữ liệu của index. Để xác định trường dữ liệu cần tìm, bạn sử dụng cú pháp @filed:query. Ví dụ @title:hello world.
  • Tìm kiếm dữ liệu trên trường NUMERIC phải sử dụng cú pháp [min max].
  • Tìm kiếm dữ liệu trên trường TAG phải sử dụng cú pháp {tag1 | tag2 | ...}
  • Fuzzy matching (gợi ý tìm kiếm) là một tính năng gợi ý từ trong tìm kiếm. Ví dụ cho trường hợp này là khi bạn gõ từ trên google thì nó gợi ý ra những từ tiếp theo. Cú pháp sẽ là %text.
  • ...

Còn một vài nguyên tắc nữa các bạn có thể xem thêm tại Search Query Syntax.

Và cuối cùng là một bảng cheatsheet thần thánh để so sánh một số lệnh tìm kiếm dữ liệu giữa SQL và redisearch:

Cú pháp tìm kiếm

Đầu tiên chúng ta hãy thêm một ít dữ liệu vào redis bằng cách sử dụng luôn index article đã tạo ở trên. Để cho đơn giản và dễ nhìn tôi sẽ thêm một vài dữ liệu nhỏ nhẹ cho mọi người dễ quan sát.

HSET article:1 url "url-1" title "bài viết số một" content "nội dung của bài viết số một" created_at 1630245601
HSET article:2 url "url-2" title "bài viết số hai" content "nội dung của bài viết số hai" created_at 1630245602
HSET article:3 url "url-3" title "bài viết số ba" content "nội dung của bài viết số ba" created_at 1630245603

Tìm tất cả record có chứa từ bài viết:

FT.SEARCH article "bài viết"

Tìm tất cả record mà content có chứa từ nội dung:

FT.SEARCH article "@content:nội dung"

Tìm tất cả record mà title có chứa từ bài viết và content không chứa từ số một:

FT.SEARCH article "@title:bài viết -@content:số một"

Tìm tất cả record mà title có chứa từ số một hoặc số hai và content không chứa từ số ba, sắp xếp theo thứ tự giảm dần của created_at:

FT.SEARCH article "@title:(số một | số hai) -@content:số ba" SORTBY created_at DESC

Stop Words

Stop words là một thuật ngữ chỉ cách mà redisearch sẽ bỏ qua một số từ quá phổ biến nhưng lại không mang lại giá trị trong tìm kiếm ví dụ như a, is, the... Những từ này nếu được đánh index sẽ chiếm nhiều không gian lưu trữ đồng thời làm ngốn lượng CPU trong lúc tìm kiếm dữ liệu.

Bởi vì redisearch được tạo ra cho tất cả người dùng thế nên họ chỉ tích hợp mặc định một số từ tiếng anh để loại bỏ Stop Words. Tuy vậy, bạn hoàn toàn có thể dịch nghĩa nó về tiếng Việt để thêm vào từ điển, hoặc bạn cũng có thể thêm những từ mà bạn không muốn sử dụng để tìm kiếm.

Stop Words được khai báo lúc tạo index. Ví dụ dưới đây tôi đang thêm 2 từ "thì", "là" vào Stop Words ở index article:

FT.CREATE article STOPWORDS 2 thì là ON HASH PREFIX 1 article: SCHEMA title TEXT WEIGHT 5.0 content TEXT created_at NUMERIC SORTABLE

Chú ý: Vì Stop Words phải được thêm ở lúc tạo index thế nên nếu các bạn đã có index từ trước thì bắt buộc phải xóa trước khi thêm lại. Sử dụng lệnh FT.DROPINDEX để xóa index, mặc định khi xóa index thì dữ liệu của index sẽ không bị xóa. Sau đó thì chúng ta tiến hành tạo lại index như bình thường.

Nếu như không muốn dùng Stop Words nữa thì đặt STOPWORDS 0 ở lệnh tạo index.

Tokenization và Escaping

Tokenization và Escaping được hiểu là trình mã hóa kí tự đầu vào và mã hóa kí tự trong lúc truy vấn dữ liệu. Dữ liệu khi được đưa vào redisearch phải qua một bước xử lý ví dụ như loại bỏ các khoảng trắng, các kí tự đặc biệt... Dưới đây là một số quy tắc Tokenization của redisearch:

  • Các kí tự ,.<>{}[]"':;!@#$%^&*()-+=~ và khoảng trắng (space) sẽ ngắt văn bản để đánh index. Ví dụ một đoạn văn bản hello-world...1 sẽ được mã hóa thành [hello world 1].
  • Nếu muốn vượt qua quy tắc trên, tức là muốn redisearch index cả những kí tự đặc biệt và khoảng trắng thì chúng ta phải thêm dấu "gạch chéo ngược" () vào trước mỗi kí tự đặc biệt. Ví dụ tôi muốn đưa được cụm hello-world vào redisearch thì phải sửa lại đoạn text thành hello\-world, khi đó lúc tìm kiếm thì cũng phải dùng hello\-world để tìm kiếm.
  • Dấu gạch dưới (_) không bị ảnh hưởng bởi Tokenization và Escaping.
  • Các khoảng trắng (space) lặp lại và các kí tự ở trong mục một được loại bỏ trong lúc truy vấn. Nếu muốn dùng thì phải thêm dấu "gạch chéo ngược" vào trước.
  • Các kí tự Latinh (A-Z a-z) đều được chuyển về chữ thường.

Đó là một số nguyên tắc của trường dữ liệu TEXT, đối với dữ liệu dạng TAG sẽ có một ít sự khác biệt mà tôi sẽ nói ở bài viết sau.

Highlighting Result

Highlighting API cho phép chúng ta thao tác với vùng dữ liệu được tìm thấy trong redisearch như chèn thêm kí tự để làm nổi bật kết quả...

Để bao bọc kết quả tìm kiếm, ví dụ như một thẻ mở/đóng xung quanh nó chúng ta sử dụng tùy chọn HIGHLIGHT:

FT.SEARCH article "bài viết" HIGHLIGHT TAGS <b> </b>

Kết quả tìm kiếm nếu có tìm được ở tất cả các trường dữ liệu sẽ được chèn vào trong thẻ <b> </b>. Nếu muốn chỉ định trường cụ thể sử dụng HIGHLIGHT chúng ta sử dụng thêm tùy chọn FIELDS:

FT.SEARCH article "bài viết" HIGHLIGHT FIELDS 1 title TAGS <b> </b>

Ngoài ra, redisearch còn hỗ trợ chúng ta hiển thị ngữ cảnh của nội dung mà chúng ta đang tìm kiếm nằm ở đâu. Ví dụ nguyên văn một câu "estacks là blog chia sẻ lập trình" thì khi tìm kiếm với từ "blog", redisearch sẽ hiển thị "...estacks là blog chia sẻ lập trình...".

FT.SEARCH article "bài viết" SUMMARIZE FIELDS 1 content

Bạn cũng có thể kết hợp cả HIGHLIGHT và SUMMARIZE trong một truy vấn.

Tổng kết

Qua bài viết này tôi hy vọng mọi người nắm được công cụ redisearch được dùng để làm gì, nó có phù hợp hay cần thiết với các bạn trong các dự án sắp tới không và các câu lệnh cơ bản để bắt đầu. Hãy không ngừng học thêm những công cụ mới để có nhiều cách giải quyết vấn đề hơn nữa!

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (0)

Nội dung bình luận...
Bấm hoặc cuộn mạnh để sang bài mới