В последнее время условия запросов в реальных проектах становятся все более сложными, и фильтрация MySQL больше не поддерживает их. Мы планируем заменить все поисковые фильтры на запросы es. Учитывая эту ситуацию, в этой статье исследуются преимущества и недостатки четырех запросов: from-size, search_after, прокрутки API, search_after (PIT).
использовать from
+ size
Перевернуть страницу
from
Не указано, значение по умолчанию 0, определяет, что нужно пропустить hits
номер, по умолчанию 0。size
Не указано, значение по умолчанию 10. Определите, что необходимо вернуть hits
Максимальное количество.Программа используется проста и имеет большое количество страниц, которые нужно перелистывать. from больше или size В особенно крупных случаях возникнут серьезные проблемы с перелистыванием страниц. ES Одна страница по умолчанию Запросмаксимальный пределmax_result_window
до 10000 。
Причина проблемы с глубоким переворачиванием страниц: ES Он использует распределенную архитектуру. При хранении данных они распределяются по разным местам. shard
середина. При запросе, если from Если значение слишком велико, начальная точка подкачки будет слишком глубокой. каждый shard При запросе, from Все данные и запросы из предыдущих локаций size Общее количество возвращается вcoordinator
。дляcoordinator
Давайте поговорим,Значительно приведет к увеличению скорости использования памяти и ЦП.,Особенно в сценариях с высоким уровнем параллелизма.,Приводит к снижению производительности или отказу узла.
например ЕС делиться 4 индивидуальныйshard
,И каждыйиндивидуальныйshard
нет копии。если Пагинация Размер 10, хочу занять 11 место содержимое страницы. тогда соответствующий from = 100,size = 10。
Процесс запроса ES:
shard
общее местосуществовать Данные загружаются в память исортировать,Тогда возьми первые 110,вернуться вcoordinator
。shard
Всеосуществлять Вышеописанная операция。coordinator
Воля 110 * 4 = 440 Отсортируйте данные, а затем возьмите 10 Данные возвращаются.Можно обнаружить, что если положение from слишком глубоко, неизбежно возникнут следующие проблемы:
coordinator
Значение слишком велико,Актуальная потребность 10частей данных,но датьcoordinator
440 часть данныхcoordinator
需要处理Каждыйиндивидуальныйshard
перед возвращением 11 страниц результатов. Но нужно только первое 11 содержание страницы, но для предыдущего 44 Содержимое страницы сортируется, тратится память и cpu ресурсы.max_result_window
настраивать,Неограниченное перелистывание страниц невозможно.func SearchTaskSampleByQuery(ctx context.Context, query *elastic.BoolQuery, page *common_base.PageReq) (int64, []es.TaskSample, error) {
var taskSampleList []es.TaskSample
esClient, err := olivere7.Get(config.EsNameSrv)
if err != nil {
return 0, taskSampleList, constant.Errorf(constant.CodeErrEsSearchFail, "SearchTakSample get es client error: %w", err)
}
search := esClient.Search().Index(имя индекса)
search.Query(elastic.NewBoolQuery().Must(query))
// сортировать
search.Sort("id", false)
// Пагинация
if page != nil {
from := page.PageSize * (page.PageNum - 1)
search.From(str.StringToInt(fmt.Sprint(from)))
search.Size(str.StringToInt(fmt.Sprint(page.PageSize)))
}
search.TrackTotalHits(true)
// осуществлять
esResult, err := search.Pretty(true).Do(ctx)
if err != nil {
return 0, taskSampleList, constant.Errorf(constant.CodeErrEsSearchFail, "SearchTaskSampleByQuery Do error: %w", err)
}
// преобразование данных запроса es
return parseEsSearchTaskSampleResult(ctx, esResult)
}
Большие пакеты запросов к документам могут выполняться эффективно. Запросы курсора берут данные моментального снимка в определенный момент времени. Любые изменения индекса после инициализации запроса им игнорируются. Эта функция достигается за счет сохранения старых файлов данных, в результате чего получается то же индексированное представление, что и при его инициализации.
Как использовать эту пагинацию,Не для данных запроса в реальном времени,но дляЗапрос большого количества данных (даже всех данных) одновременно)。
специфическийиспользоватьметод:
scrollId
и кэшировать все результаты поиска, соответствующие условиям поиска. Обратите внимание, что это только кэшируется doc_id
, не все данные документа действительно кэшируются, данные извлекаются из существования fetch
этап завершен.scrollId
иscroll
esСделай этот снимок(search контекст) эффективное время кэшированных результатов 。Получение ES разделено на два этапа: запрос и выборка. Этап запроса более эффективен и запрашивает только те идентификаторы документов, которые соответствуют условиям. Фаза выборки выполняет глобальную сортировку на координирующем узле на основе результатов каждого сегмента, а затем, наконец, вычисляет результаты.
Запрос прокрутки ищет только те, которые соответствуют условиям doc_id
(Официально рекомендуется doc_id
Сортировка, потому что сам кэш doc_id
, если сортировка по другим полям увеличит объем запроса), отсортируйте их и сохраните в поиске контекст, но он не извлекает все данные. Вместо этого он считывает документы размера каждый раз при прокрутке и возвращает следующий документ, прочитанный в этот раз, и статус контекста, чтобы сообщить, какой документ из какого фрагмента необходимо начать чтение в следующий раз.
scroll_id
и исторические снимки, а также должны обеспечить scroll_id
время жизни, что является огромной нагрузкой на сервер.// ScrollTaskSample прокрутить автономный запрос
func ScrollTaskSample(ctx context.Context, query *elastic.BoolQuery, scrollId, scrollTime string) (int64, []es.Sample, error) {
var sampleList []es.Sample
esClient, err := olivere7.Get(config.EsNameSrv)
if err != nil {
return 0, sampleList, constant.Errorf(constant.CodeErrEsSearchFail, "SearchSampleByQuery get es client error: %w", err)
}
search := esClient.Scroll().Index(SampleIndex)
search.Query(elastic.NewBoolQuery().Must(query))
// сортировать
search.Sort("id", false)
search.Size(1000)
// хиты версии 7.0
search.TrackTotalHits(true)
// При первом вызове передайте пустую строку ScrollId, scrollTime 5s, получать esResult.ScrollId
// Для последующих вызовов введите esResult.ScrollId, 5m, Пока длина массива попаданий не станет 0
search.ScrollId(scrollId)
// время моментального снимка
search.Scroll(scrollTime)
// осуществлять
esResult, err := search.Pretty(true).Do(ctx)
if err != nil {
return 0, sampleList, constant.Errorf(constant.CodeErrEsSearchFail, "SearchSampleByQuery Do error: %w", err)
}
return parseEsSearchSampleResult(ctx, esResult)
}
search_after Запрос: Использовать Последнюю часть запроса данные для вашего следующего запроса. Потому что данные на каждой странице зависят от последней части предыдущей страницы. данных, поэтому запросы на переход к странице не могут быть выполнены.
специфическийиспользоватьметод:
sort
массив отсортированных значенийsort
Значение сортировки используется для ввода параметров для захвата данных на следующей странице.например ЕС делиться 4 индивидуальныйshard
,И каждыйиндивидуальныйshard
нет копии。если Пагинация Размер 10, хочу занять 11 место содержимое страницы. Соответствующий from = 100,size = 10.
Процесс запроса ES:
shard
в соответствии сsortкурсор,Возьмите 10 образцов, соответствующих условиям.,вернуться вcoordinator
.shard
Всеосуществлять Вышеописанная операция。coordinator
Воля 10 * 4 = 40 Отсортируйте данные, а затем возьмите 10 Данные возвращаются.scroll_id
,Снимок подтверждения не требуется,Таким образом, можно избежать потребления больших объемов ресурсов.// SearchAfterTaskSample Запрос курсора Пагинация
func SearchAfterTaskSample(ctx context.Context, query \*elastic.BoolQuery, sortFlag \*[]interface{}) (int64, []es.Sample, error) {
var sampleList []es.Sample
esClient, err := olivere7.Get(config.EsNameSrv)
if err != nil {
return 0, sampleList, constant.Errorf(constant.CodeErrEsSearchFail, "SearchSampleByQuery get es client error: %w", err)
}
search := esClient.Search()
search.Query(elastic.NewBoolQuery().Must(query))
// сортировать
search.Sort("id", false)
if sortFlag != nil {
// search\_after
search.SearchAfter(sortFlag...)
}
// хиты версии 7.0
search.TrackTotalHits(true)
// осуществлять
esResult, err := search.Pretty(true).Do(ctx)
if err != nil {
return 0, sampleList, constant.Errorf(constant.CodeErrEsSearchFail, "SearchSampleByQuery Do error: %w", err)
}
// Курсор следующей страницы sortFlag := &esResult.Hits.Hitslen(esResult.Hits.Hits)-1.Sort
return parseEsSearchSampleResult(ctx, esResult)
}
существовать После 7.10 версия, официальная ES Метод прокрутки для глубокой пагинации больше не рекомендуется, но рекомендуется использовать с PIT. search_after наводить справки.
PIT можно рассматривать как упрощенное представление, в котором хранится состояние индексных данных. Создайте момент времени (PIT), чтобы гарантировать, что индексный статус определенной точки события сохраняется во время процесса поиска. При использовании PIT все последующие запросы в search_after основаны на представлении PIT, что может эффективно обеспечить согласованность данных.
Эффект от запроса Пагинация точно такой же, как и от Прокрутки.,но средний Запрос Повышение эффективности30%。Цитируемый текст: Elasticsearch Scroll API против поиска после с PIT
По сравнению с прокруткой также была оптимизирована память и упрощен процесс es-запроса:
Видно, что частота повторного использования кеша уpit выше, чем у прокрутки: например, если поступает 1 миллион запросов на прокрутку, в памяти необходимо кэшировать 1 миллион результатов запросов на прокрутку. Однако ссылки на сегментную память в кэше ямы на самом деле могут повторно использоваться во многих запросах, и само собой разумеется, что потребление памяти будет ниже.
Он подходит для таких сценариев, как получение больших пакетных данных, обработка данных не в реальном времени, миграция данных или изменение индексов.
Запрос не может отражать характер данных в реальном времени. Созданный исторический снимок данных не будет отражать изменения данных в снимке.
// CreatePit Создать момент времени
func CreatePit(ctx context.Context, indexName, aliveTime string) (string, error) {
esClient, err := olivere7.Get(config.EsNameSrv)
if err != nil {
return "", constant.Errorf(constant.CodeErrEsSearchFail, "get es client error: %w", err)
}
openRsp, err := esClient.OpenPointInTime(indexName).KeepAlive(aliveTime).Pretty(true).Do(ctx)
if err != nil {
return "", err
}
return openRsp.Id, nil
}
// ClosePit время закрытия
func ClosePit(ctx context.Context, pit string) (bool, error) {
esClient, err := olivere7.Get(config.EsNameSrv)
if err != nil {
return false, constant.Errorf(constant.CodeErrEsSearchFail, "get es client error: %w", err)
}
closeResp, err := esClient.ClosePointInTime(pit).Pretty(true).Do(ctx)
if err != nil {
return false, err
}
return closeResp.Succeeded, nil
}
// SearchAfter Курсорный запрос
func SearchAfter(ctx context.Context, query *elastic.BoolQuery, sortFlag []interface{},
pit, aliveTime string) (*elastic.SearchResult, error) {
esClient, err := olivere7.Get(config.EsNameSrv)
if err != nil {
return nil, constant.Errorf(constant.CodeErrEsSearchFail, "get es client error: %w", err)
}
search := esClient.Search()
// Каждая настройка размера тяги
search.Size(10)
search.Query(elastic.NewBoolQuery().Must(query))
// сортировать
search.Sort("_shard_doc", true)
// доставка в определенный момент
if len(pit) > 0 {
pointTime := &elastic.PointInTime{
Id: pit,
KeepAlive: aliveTime,
}
search.PointInTime(pointTime)
}
// Прохождение курсора
if len(sortFlag) != 0 {
// search_after
search.SearchAfter(sortFlag...)
}
// осуществлять
esResult, err := search.Pretty(true).Do(ctx)
if err != nil {
return nil, constant.Errorf(constant.CodeErrEsSearchFail, "SearchAfter Do error: %w", err.Error())
}
return esResult, nil
}
Ссылки: