Golang中的Elasticsearch分页查询

821 阅读1分钟

在这个例子中,我们将使用Elasticsearch的Search API来查询索引,并在Golang中分页显示结果集。我们将使用经典的fromsize 参数,因为from+size 总是小于或等于10000然而,如果你不能保证这一点,你应该坚持使用Search after功能。这个功能最好的地方是它使用了时间点API(PIT)。当刷新发生在分页请求之间时,结果的顺序可能会改变,这将导致不同页面之间的不一致。时间点API保留了当前的索引状态以防止这种问题。

elasticsearch.go

package elasticsearch

...

// postsResponse represents list of posts in Search API response body.
type postsResponse struct {
	Hits struct {
		Total struct {
			Value int `json:"value"`
		} `json:"total"`
		Hits []struct {
			Source *storage.Post `json:"_source"`
		} `json:"hits"`
	} `json:"hits"`
}

post_storer.go

package storage

...

type PostStorer interface {
	...
	// The returned `int` represents all the found docs, not the ones in the current page!
	ListAll(ctx context.Context, from, size int) (int, []*Post, error)
}

elasticsearch.go

package elasticsearch

...

// Validation
// from >= 0
// size >= 0
// from+size <= 10000
func (p PostStorage) ListAll(ctx context.Context, from, size int) (int, []*storage.Post, error) {
	// res, err := p.elastic.client.Search()
	req := esapi.SearchRequest{
		Index: []string{p.elastic.alias},
		From:  &from,
		Size:  &size,
	}

	ctx, cancel := context.WithTimeout(ctx, p.timeout)
	defer cancel()

	res, err := req.Do(ctx, p.elastic.client)
	if err != nil {
		return 0, nil, fmt.Errorf("list all: request: %w", err)
	}
	defer res.Body.Close()

	if res.IsError() {
		return 0, nil, fmt.Errorf("list all: response: %s", res.String())
	}

	var body postsResponse
	if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
		return 0, nil, fmt.Errorf("list all: decode: %w", err)
	}

	posts := make([]*storage.Post, len(body.Hits.Hits))
	for i, v := range body.Hits.Hits {
		posts[i] = v.Source
	}

	return body.Hits.Total.Value, posts, nil
}