這篇文章主要會針對該如何控制回傳的結果做介紹,像是一般常見的 filtering 或是 pagination 等等。
改變格式
首先,我想先介紹一個 Elasticsearch ( 以下簡稱 ES ) 實用的功能,我們可以讓 ES 回傳 YAML 或是 JSON 的格式。
只需要在 endpoint 的後方加上 format=yaml
的參數就可以。
// GET /movies/_search?format=yaml
{
"query": {
"match": {
"title": {
"query": "Wedidng",
"fuzziness": 1
}
}
}
}
回傳的結果:
至於另外一個格式是 JSON,你可能會想說我在說什麼?不是原本就已經回傳 JSON 了嗎?那是因為我們在 Kibana 的 DevTools 裡面才會是這樣,如何直接透過應用程式或是 cURL
這樣的工具呼叫 endpoint 是很難閱讀的,我們嘗試在終端機使用 cURL
的方式呼叫相同的查詢。
$ curl -XGET "http://localhost:9202/movies/_search" -H "kbn-xsrf: reporting" -H "Content-Type: application/json" -d'
{
"query": {
"match": {
"title": {
"query": "Wedidng",
"fuzziness": 1
}
}
}
}'
結果如下,非常難以閱讀,想想看這如果是在 production 的環境中 debug 之類的會有多痛苦。
如何改善結果呢?只需要加上 ?pretty
的參數就可以達到完美的效果。
curl -XGET "http://localhost:9202/movies/_search?pretty" -H "kbn-xsrf: reporting" -H "Content-Type: application/json" -d'
{
"query": {
"match": {
"title": {
"query": "Wedidng",
"fuzziness": 1
}
}
}
}'
出現的結果就像是在 Kibana 的 DebTools 裡面一樣:
Filtering
這個單元則會專注在把回傳的 _source
內容進行篩選,有時候不是所有的內容我們都有興趣,只拿到我們需要的內容很好用。
很簡單,只需要加上 _source
的 key 然後選擇想要的欄位即可。
// GET /movies/_search
{
"_source": ["title"],
"query": {
"match": {
"title": {
"query": "Wedidng",
"fuzziness": 1
}
}
}
}
上面的查詢只會回傳 title
的欄位,如果整個都不想要,可以直接使用 _source: false
的參數。
結果如下圖所示:
實現分頁效果
分頁效果在所有的應用程式中都是一種減少負擔的方式,ES 也能輕鬆做到。
Size
使用 size
參數可以控制回傳的資料筆數。
// GET /movies/_search
{
"size": 2,
"query": {
"match": {
"title": {
"query": "American"
}
}
}
}
這樣每次回傳都會是 2 筆的資料。
From
預設查詢的 from
參數是 0,也就是第一頁的概念。
所以當我們設定 from
變成 1 的時候,就會從第二頁開始,其實就是關聯式資料庫常見的 offset 的作法,只是在 ES 叫做 from
。
如何透過 size & from 實現分頁
接著就是如何在應用程式面實作分頁了。
首先就是要知道如何得到最大的分頁,可以透過 ES 的 hits.total.value
來得到總數,接著除以你想要每一頁的數量多寡,然後無條件進位。
total_pages_size = ceil(hits.total.value / page_size)
接著我們要傳的 from
參數要怎麼計算出來呢?
from = (page_size) * (page_number -1))
為什麼要 -1
呢?因為 from
是從 0 開始。
所以就可以拿著你訂好的 size
搭配不斷計算出來的 from
來得到想要的結果。
分頁的極限
使用 from
搭配 size
的分頁極限是 10000 筆資料,這個限制是來自 ES 的 index.max_result_window
設定的,預設是 10000。
那為什麼要預設這個極限值呢?
這個限制的存在主要是為了保護 ES 的叢集不會因為過大的查詢而耗盡資源,當我們使用 from
搭配 size
搜尋時,ES 會保留所有先前頁面上的結果,這會消耗大量的記憶體。
假設,請求從第 100000 個結果開始的頁面,ES 必須首先找到前 100000 個結果,並且在記憶體中保留它們,這件事聽起來就很可怕吧?
所以說 ES 的分頁最多就是做到 10000 筆嗎?當然不是,大概有幾種解法:
- 使用
search_after
的參數,這個有機會可以介紹一下。 - 更改
index.max_result_window
的設定,粗暴簡單,加大硬體設施,只要記憶體吃得下去,都只是 $$ 的問題。 - 使用 Scroll API,但這不太適合即時的搜尋,更適合要在背景拿大量資料使用。
- Search Cursor 或是 Slice 的方式,這就更麻煩一點了,應該不會介紹。