iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0

在實際搜尋系統中,分頁是必須的。Elasticsearch 預設提供 from + size,但這種方式在深度分頁時會變得非常慢。我們今天的目標是:
理解 from + size 的限制,改用 search_after 作為分頁方案,在 Go 端實作一個支援分頁的查詢


Step 1:傳統 from + size

假設我們要查詢第 3 頁,每頁 10 筆:

curl -X POST "http://localhost:9200/books/_search?pretty" \
  -H 'Content-Type: application/json' \
  -d '{
    "from": 20,
    "size": 10,
    "query": {
      "match": { "title": "golang" }
    }
  }'

這會跳過前 20 筆,再取後 10 筆。

問題

當分頁很深(例如跳過 10 萬筆)時,ES 仍需掃過前面所有資料,效能急速下降。


Step 2:search_after 概念

search_after 不是用「第幾頁」,而是用「上一次最後一筆的排序值」來接續查詢。這種方式是 cursor-based pagination,效能遠比 from + size 好。

用法:

  1. 查詢時指定 sort
  2. 下一頁帶上 search_after

Step 3:範例查詢

先查詢第一頁,每頁 2 筆:

curl -X POST "http://localhost:9200/books/_search?pretty" \
  -H 'Content-Type: application/json' \
  -d '{
    "size": 2,
    "sort": [
      { "published_at": "asc" },
      { "_id": "asc" }
    ],
    "query": {
      "match_all": {}
    }
  }'

回傳結果裡,每筆文件會帶有 sort 欄位,例如:

"sort": ["2025-01-01T12:00:00Z", "1"]

下一頁查詢就帶上它:

curl -X POST "http://localhost:9200/books/_search?pretty" \
  -H 'Content-Type: application/json' \
  -d '{
    "size": 2,
    "search_after": ["2025-01-01T12:00:00Z", "1"],
    "sort": [
      { "published_at": "asc" },
      { "_id": "asc" }
    ],
    "query": {
      "match_all": {}
    }
  }'

Step 4:Go 實作

ESSearchService 新增一個分頁版查詢:

func (es *ESSearchService) SearchPaged(ctx context.Context, query string, after []any, size int) ([]SearchResult, []any, error) {
    body := fmt.Sprintf(`{
      "size": %d,
      "sort": [
        { "published_at": "asc" },
        { "_id": "asc" }
      ],
      "query": {
        "match": { "title": "%s" }
      }`, size, query)

    if after != nil {
        sa, _ := json.Marshal(after)
        body = body + fmt.Sprintf(`, "search_after": %s`, sa)
    }
    body = body + "}"

    results, sorts, err := es.searchRawWithSort(ctx, body)
    return results, sorts, err
}

其中 sorts 會存回最後一筆文件的 sort 值,供下一頁使用。


Step 5:小結

今天我們完成了:

  1. 說明 from + size 的限制
  2. 學會用 search_after 做 cursor-based pagination
  3. 在 Go 端實作 SearchPaged,支援分頁查詢

這樣,我們的 /search API 已經可以處理大規模資料集的分頁,而不怕深度分頁效能崩壞。


上一篇
Day 18 - 查詢打底:`match` / `bool` / `terms`
下一篇
Day 20 - Bulk 匯入 5–10 萬筆 (以 Jeopardy 資料集為例)
系列文
用 Golang + Elasticsearch + Kubernetes 打造雲原生搜尋服務20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言