ElasticSearch 全文字搜尋引擎 — 基本概念介紹

vic
Happy Friday
Published in
10 min readOct 2, 2020

前言

全文字搜尋是與我們生活息息相關的功能,例如當你用谷歌搜尋某個關鍵字時,或者使用學校圖書館找某本書時,都在使用全文字搜尋這項功能,而 ElasticSearch (ES) 是一個開源的全文字搜尋引擎服務,且廣泛地應用在多個服務中,以下來介紹 ES的基本概念。

目錄

  1. 全文字搜尋引擎的用途
  2. 全文字搜尋引擎的搜尋原理
  3. ElasticSearch 的功能介紹
  4. ElasticSearch 資料儲存的機制

內文

全文字搜尋引擎的用途

在網路服務中,查詢是一項重要的功能,依照不同查詢的情境,服務背後也需要不同的搜尋機制,例如在電商的管理後台中,店家需要用時間參數來搜尋雙十一的所有訂單,以及透過訂單編號找到某個特定訂單的內容:

SELECT * FROM orders WHERE created_at between '2020-11-11 00:00:00' AND '2020-11-11 23:59:59';SELECT * FROM orders WHERE order_uid = '6722237e-0d23-478c-b7ce-b43a14446c44';

以上的情境我們可以用關聯式資料庫以及 SQL 語法來完成,接下來讓我們換個情境。

在使用 facebook,我們很常使用特定的關鍵字來搜尋貼文,例如我們想找跟狗有關的英文貼文:

SELECT * FROM posts WHERE content LIKE %dog%;

上面 SQL 的 LIKE 語法就像你在瀏覽器上使用 crtl+f 搜尋一樣,你可以找出所有內容中有 dog 的貼文,然而這個搜尋結果可能不太精準。

例如你可能找出內容中有 ddoggx 的貼文,但 ddoggx 搞不好根本不是英文,或者當你搜尋關鍵字是 programming 時,無法找內容中有 program 或者 programmer 的貼文,以上的情境我們稱為文件 ( document )的搜尋,而在文件搜尋的情境下,全文搜尋就派上用場了。

文件 (document) 指的是內容為文字的資料,例如 書,貼文,產品描述等。

全文字搜尋引擎的搜尋原理

為了克服 LIKE SQL 語法在文件搜尋中的瓶頸,全文字搜尋引擎必須達成兩個功能:

  1. 將搜尋條件 (i.e keyword) 標準化,例如 dogs -> dog
  2. 建立每個文件的文字搜尋索引,例如 index[dog] -> There are many dogs

搜尋索引在資料庫中是用來提高搜尋速度的重要技術,例如在關聯式資料庫中,會用 B+Tree 建立資料中每個欄位的索引,讓資料搜尋的速度從 O(n) 提升到 O(logN)。

而在全文字搜尋引擎中,會使用 invert index 的資料結構來實作文字搜尋引擎。

invert index 是用來儲存文字到文件映射關係的資料結構,也就是透過 invert index,我們可以實現 index[dog] -> There are many dogs,反之儲存從文件到文字映射關係的資料結構為 forward index。

invert index 是一個類似 map 的資料結構,其 key 為文字,而 value 為一組包含該文字的文件 ID:

文件1: This is a dog.

文件2: This is a cat.

invert index:

|key |value |
|this|1,2 |
|is |1,2 |
|a |1,2 |
|dog |1 |
|cat |1 |

有了 invert index,當我們搜尋 this 的時候,就可以馬上獲得文件1,2,而當我們要搜尋 this is 時,我們可以將 this 的搜尋結果跟 is 的搜尋結果取聯集。

實作一個簡單的 invert index只有兩個步驟:

  1. 取出該文件的文字 (token),並將這些文字標準化 (e.g 取詞幹,移除停用詞)。
  2. 迭代所有文字,並在 invert index 中找出該文字的位置,並將文件的 ID 加入。

而從 invert index 找出對應的文件也只有兩個步驟:

  1. 將搜尋條件標準化
  2. 迭代所有搜尋條件,並從 invert index 中找出對應的文件 ID 並取聯集。

雖然 invert index 的實作流程看似簡單,但卻能大大提升全文字搜尋的精準度以及搜尋速度,此外設計 invert index 的難度在於如何提升建立索引的速度以及正確的從文件中取出文字並標準化。

在實作中,invert index 也可以加上 metadata,例如各個 token 在所有 document 中出現的次數,並以此當作 token 的權重,以及 token 在 document 中的位置,以此來精準定位出 document 中的段落。

ElasticSearch 的功能介紹

ES 是一個提供全文字搜尋引擎功能的開源 Java 專案,其底層採用了 lucene 全文字搜尋框架,lucene 為 ES 提供 invert index,文件儲存以及文件搜尋的功能,而 ES 在 lucene 上提供出 cluster 架構,讓使用者能透過多台電腦中的硬碟來處存大量的文件。

除了 cluster 架構,ES 還提供 HTTP 接口,讓使用者可用不同語言實作 client-side 程式來與 ES 交互,以及透過 DSL 設計自己的查詢語法,來做到不單只有全文字的搜尋,還可以做得資料聚合的功能,例如某特定區域剩餘的客房數量。

在全文字搜尋上,ES 提供可擴充的 Analyzer 模組,主要用於文件中取文字以及標準化的邏輯,因此我們可以透過自訂義的 Analyzer 來實作中文版的全文字搜尋引擎。

想要嘗試使用 ES 可透過以下兩個 docker image:

# 啟動 es 服務的 image
docker run -d -p 9200:9200 -p 9300:9300 --name els -e "http.cors.enabled=true" -e "http.cors.allow-origin=http://localhost:8080" -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.9.1
# 啟動 es GUI的 image
docker run -p 8080:8080 -d cars10/elasticvue

此時在瀏覽器上輸入 localhost:8080 就能進到 ES 的 GUI ,並透過該 GUI 與去操作 ES 資料庫。

ES 雖然也是資料庫,但他與關聯式資料庫的差別在於,ES 是 Schema-less 也就是說 ES 無法保證使用者輸入的資料格式與你預期的相符。

ElasticSearch 資料儲存的機制

介紹完全文字搜尋以及 ES 的基本功能後,接下來來談談 ES 是如何處理文件資料的吧。

首先介紹 ES 描述資料的專有名詞,IndexTypeDocumentField

Index: 代表文件資料庫,相當於 RDBMS 中的 Database。

Type: 代表文件類型,相當於 RDBMS 中的 Table。

Document: 代表文件資料,ES 使用 JSON 格式來儲存文件,相當於 RDBMS 中的 Row。

Field: 代表文件中的欄位,也就是 JSON 格式中的 Key,相當於 RDMS 中的 Column。

在 ES 建立資料的過程,會需要幫 Document 中的 Field 定義 Data Type,ES 中 Data Type 的功能並不像 RDBMS 一樣是定義 Schema,而是用來告訴 ES 如何為這個 Field 建立索引,以及該 Field 有哪些對應的搜尋功能。

而在 ES 中所有的 Field 可以廣泛地被分為兩個類別,Full-Text Exact Values

Full-Text: 全文字,代表該 Field 屬於文字或文件,會建立 invert index,並提供全文字搜尋功能。

Exact Values: 精準值,代表該 Field 是用來描述一個準確的值,例如 Email,ID,日期等,該 Field 能提供像是聚合,範圍以及 wildcard 配對查詢。

client program 可以透過 data mapping 為一個 index 建立 field data type,如果再沒有設置 data mapping 的情況下,建立一個 document,ES 會使用 dynamic mapping 來自行判斷 document 中的各個 field 為什麼類型。

這裡簡單舉幾個 field type:

keyword: 代表該 field 為 exact value,例如 id, email,可用來排序以及 wildcard 搜尋

text: 代表該 field 為 full-text。

numeric: 代表該 field 為數字,可用於 aggregate 以及 range 搜尋。

定義完 field type 後,當 client 向 ES 建立 document 時,ES 就能知道 document 中的哪些 field 屬於 full-text 類型,而 full-text 類型的資料就會需要執行 ES 中的 Analyzer 模組。

上節介紹 ES 基本功能時有提到 Analyzer 模組,該模組主要用於建立 document 的 invert index,以及 preprocess query 時會執行的,而 ES 有三個內建的 Analyzer:

character filter: 用於清除 document 中一些不要的字元,例如 HTML tag,或用於轉換一些特殊的逃脫字元。

tokenizer: 用來將 document 中的 text field 拆解成文字 (token)。

token filter: 用來標準化 token ,例如移除 stopword,取詞幹等。

當 ES 使用 Analyzer 分析完 document 後,ES 就會使用 lucene 將 invert index 以及 document 儲存在 shard 中,我們可以將 shard 視為一個 document 的容器,主要會透過 lucene 壓縮後儲存在 disk 中,而查詢時也必須透過 lucene 在 shard 中查詢。

shard 在 ES 中為最小的工作單位,主要由 lucene管理並將 document, invert index 儲存在內,es 中的 index 實際上是由多個 shard 所組成的。

而一個 index 可以有多個 shard,多個 shard 的好處是在 cluster 架構中,可將不同 shard 儲存在不同的 machine 上,提升 index 的儲存量,但 shard 過多也會導致 ES 記憶體過量 (process per shard) 以及效能低落 (segment migration) 等等問題,但 shard 的管理以及運作機制就不在本文章範圍內了。

總結

  • 不同搜尋情境需要不同搜尋機制,而全文字搜尋引擎主要用於文件的內容搜尋。
  • 文件內容搜尋的條件在於辨識文件中的文字以及如何標準化文字。
  • 索引機制主要用於提高搜尋速度,而全文字搜尋引擎使用 invert index 去提高搜尋文件的速度。
  • ElasticSearch 是開源的全文字搜尋引擎,其底層使用 lucene 框架,並提供了 cluster 架構。
  • ElasticSearch 使用 JSON 儲存 document,一個 document 會有不同 field 以及 field type,field type 主要用於告知 ES 如何建立索引以及如何搜尋。
  • ElasticSearch 會將 index 拆成多個 shard ,而 shard 是由 lucene 管理,並壓縮儲存在 disk 中。

參考資料

--

--