Website logo

Robert Chang

技術部落格

Elasticsearch - 如何控制回傳結果

這篇文章主要會針對該如何控制回傳的結果做介紹,像是一般常見的 filtering 或是 pagination 等等。

改變格式

首先,我想先介紹一個 Elasticsearch ( 以下簡稱 ES ) 實用的功能,我們可以讓 ES 回傳 YAML 或是 JSON 的格式。

只需要在 endpoint 的後方加上 format=yaml 的參數就可以。

// GET /movies/_search?format=yaml

{
  "query": {
    "match": {
      "title": {
        "query": "Wedidng",
        "fuzziness": 1
      }
    }
  }
}

回傳的結果:

screen shot

至於另外一個格式是 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 之類的會有多痛苦。

screen shot

如何改善結果呢?只需要加上 ?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 裡面一樣:

screen shot

Filtering

這個單元則會專注在把回傳的 _source 內容進行篩選,有時候不是所有的內容我們都有興趣,只拿到我們需要的內容很好用。

很簡單,只需要加上 _source 的 key 然後選擇想要的欄位即可。

// GET /movies/_search

{
  "_source": ["title"], 
  "query": {
    "match": {
      "title": {
        "query": "Wedidng",
        "fuzziness": 1
      }
    }
  }
}

上面的查詢只會回傳 title 的欄位,如果整個都不想要,可以直接使用 _source: false 的參數。

結果如下圖所示:

screen shot

實現分頁效果

分頁效果在所有的應用程式中都是一種減少負擔的方式,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 筆嗎?當然不是,大概有幾種解法:

  1. 使用 search_after 的參數,這個有機會可以介紹一下。
  2. 更改 index.max_result_window 的設定,粗暴簡單,加大硬體設施,只要記憶體吃得下去,都只是 $$ 的問題。
  3. 使用 Scroll API,但這不太適合即時的搜尋,更適合要在背景拿大量資料使用。
  4. Search Cursor 或是 Slice 的方式,這就更麻煩一點了,應該不會介紹。

上一篇文章Elasticsearch - Fuzzy Match Query ( 模糊搜尋 )

下一篇文章Elasticsearch - 學習地圖