Spec-Zone.ru › Elasticsearch 7
›Руководство по Elasticsearch [7.17] ›REST API ›API поиска

API профилирования

API профилирования — это инструмент отладки, добавляющий существенную нагрузку на выполнение запроса поиска.

Предоставляет подробную информацию о временных затратах на выполнение отдельных компонентов запроса поиска.

Описание

API профилирования предоставляет пользователю представление о том, как запросы поиска выполняются на низком уровне, чтобы понять, почему некоторые запросы медленные, и предпринять шаги для их улучшения. Обратите внимание, что API профилирования, в частности, не измеряет задержки сети, время ожидания запросов в очередях или время слияния ответов фрагментов на узле координатора.

Вывод API профилирования очень подробный, особенно для сложных запросов, выполняемых на многих фрагментах. Рекомендуется использовать форматирование ответа для лучшего понимания вывода.

Примеры

Любой запрос _search может быть профилирован путем добавления параметра верхнего уровня profile:

GET /my-index-000001/_search
{
  "profile": true,
  "query" : {
    "match" : { "message" : "GET /search" }
  }
}

Установка параметра верхнего уровня profile в значение true включит профилирование для поиска.

API возвращает следующий результат:

{
  "took": 25,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 5,
      "relation": "eq"
    },
    "max_score": 0.17402273,
    "hits": [...] 
  },
  "profile": {
    "shards": [
      {
        "id": "[2aE02wS1R8q_QFnYu6vDVQ][my-index-000001][0]",
        "searches": [
          {
            "query": [
              {
                "type": "BooleanQuery",
                "description": "message:get message:search",
                "time_in_nanos" : 11972972,
                "breakdown" : {
                  "set_min_competitive_score_count": 0,
                  "match_count": 5,
                  "shallow_advance_count": 0,
                  "set_min_competitive_score": 0,
                  "next_doc": 39022,
                  "match": 4456,
                  "next_doc_count": 5,
                  "score_count": 5,
                  "compute_max_score_count": 0,
                  "compute_max_score": 0,
                  "advance": 84525,
                  "advance_count": 1,
                  "score": 37779,
                  "build_scorer_count": 2,
                  "create_weight": 4694895,
                  "shallow_advance": 0,
                  "create_weight_count": 1,
                  "build_scorer": 7112295
                },
                "children": [
                  {
                    "type": "TermQuery",
                    "description": "message:get",
                    "time_in_nanos": 3801935,
                    "breakdown": {
                      "set_min_competitive_score_count": 0,
                      "match_count": 0,
                      "shallow_advance_count": 3,
                      "set_min_competitive_score": 0,
                      "next_doc": 0,
                      "match": 0,
                      "next_doc_count": 0,
                      "score_count": 5,
                      "compute_max_score_count": 3,
                      "compute_max_score": 32487,
                      "advance": 5749,
                      "advance_count": 6,
                      "score": 16219,
                      "build_scorer_count": 3,
                      "create_weight": 2382719,
                      "shallow_advance": 9754,
                      "create_weight_count": 1,
                      "build_scorer": 1355007
                    }
                  },
                  {
                    "type": "TermQuery",
                    "description": "message:search",
                    "time_in_nanos": 205654,
                    "breakdown": {
                      "set_min_competitive_score_count": 0,
                      "match_count": 0,
                      "shallow_advance_count": 3,
                      "set_min_competitive_score": 0,
                      "next_doc": 0,
                      "match": 0,
                      "next_doc_count": 0,
                      "score_count": 5,
                      "compute_max_score_count": 3,
                      "compute_max_score": 6678,
                      "advance": 12733,
                      "advance_count": 6,
                      "score": 6627,
                      "build_scorer_count": 3,
                      "create_weight": 130951,
                      "shallow_advance": 2512,
                      "create_weight_count": 1,
                      "build_scorer": 46153
                    }
                  }
                ]
              }
            ],
            "rewrite_time": 451233,
            "collector": [
              {
                "name": "SimpleTopScoreDocCollector",
                "reason": "search_top_hits",
                "time_in_nanos": 775274
              }
            ]
          }
        ],
        "aggregations": [],
        "fetch": {
          "type": "fetch",
          "description": "",
          "time_in_nanos": 660555,
          "breakdown": {
            "next_reader": 7292,
            "next_reader_count": 1,
            "load_stored_fields": 299325,
            "load_stored_fields_count": 5
          },
          "debug": {
            "stored_fields": ["_id", "_routing", "_source"]
          },
          "children": [
            {
              "type": "FetchSourcePhase",
              "description": "",
              "time_in_nanos": 20443,
              "breakdown": {
                "next_reader": 745,
                "next_reader_count": 1,
                "process": 19698,
                "process_count": 5
              },
              "debug": {
                "fast_path": 5
              }
            }
          ]
        }
      }
    ]
  }
}

Результаты поиска возвращаются, но здесь опущены для краткости.

Даже для простого запроса ответ относительно сложный. Давайте разберем его по частям, прежде чем переходить к более сложным примерам.

Общая структура ответа профилирования следующая:

{
   "profile": {
        "shards": [
           {
              "id": "[2aE02wS1R8q_QFnYu6vDVQ][my-index-000001][0]",  
              "searches": [
                 {
                    "query": [...],             
                    "rewrite_time": 51443,      
                    "collector": [...]          
                 }
              ],
              "aggregations": [...],            
              "fetch": {...}                    
           }
        ]
     }
}

Для каждого фрагмента, участвовавшего в ответе, возвращается профиль, идентифицируемый уникальным идентификатором.

Временные метки запроса и другая отладочная информация.

Кумулятивное время переписывания.

Имена и временные метки вызова каждого коллектора.

Временные метки агрегации, счетчики вызовов и отладочная информация.

Временные метки извлечения и отладочная информация.

Поскольку запрос поиска может быть выполнен на одном или нескольких фрагментах индекса, а поиск может охватывать один или несколько индексов, элементом верхнего уровня в ответе профилирования является массив объектов shard. Каждый объект фрагмента перечисляет его id, который однозначно идентифицирует фрагмент. Формат идентификатора — [nodeID][indexName][shardID].

Сам профиль может состоять из одного или нескольких «поисков», где поиск — это запрос, выполненный на базовом индексе Lucene. Большинство запросов поиска, отправленных пользователем, выполняют только один search на индексе Lucene. Но иногда выполняется несколько поисков, например, при включении глобальной агрегации (которая должна выполнить вторичный запрос «match_all» для глобального контекста).

Внутри каждого объекта search будет два массива профильной информации: массив query и массив collector. Наряду с объектом search расположен объект aggregations, содержащий информацию профилирования для агрегаций. В будущем могут быть добавлены другие разделы, такие как suggest, highlight и т.д.

Также будет метрика rewrite, показывающая общее время переписывания запроса (в наносекундах).

Как и другие API статистики, API профилирования поддерживает вывод в удобочитаемом формате. Это можно включить, добавив ?human=true в строку запроса. В этом случае вывод содержит дополнительное поле time, содержащее округлённую, удобочитаемую информацию о временных метках (например, "time": "391,9ms", "time": "123.3micros").

Профилирование запросов

Подробности, предоставляемые API профилирования, напрямую отображают имена и концепции классов Lucene, что означает, что для полного интерпретирования результатов требуются довольно глубокие знания Lucene. Эта страница пытается дать краткий курс по тому, как Lucene выполняет запросы, чтобы вы могли использовать API профилирования для успешной диагностики и отладки запросов, но это лишь обзор. Для полного понимания, пожалуйста, обратитесь к документации Lucene и, в некоторых местах, к коду.

При этом, полное понимание зачастую не требуется для исправления медленного запроса. Обычно достаточно увидеть, что определенный компонент запроса медленный, а не обязательно понимать, почему фаза advance этого запроса является причиной, например.

query Раздел

Раздел query содержит подробные временные метки дерева запроса, выполняемого Lucene на конкретном фрагменте. Общая структура этого дерева запроса будет напоминать ваш исходный запрос Elasticsearch, но может быть немного (или иногда очень) отличаться. Она также будет использовать аналогичные, но не всегда идентичные наименования. Используя наш предыдущий пример запроса match, давайте проанализируем раздел query:

"query": [
    {
       "type": "BooleanQuery",
       "description": "message:get message:search",
       "time_in_nanos": "11972972",
       "breakdown": {...},               
       "children": [
          {
             "type": "TermQuery",
             "description": "message:get",
             "time_in_nanos": "3801935",
             "breakdown": {...}
          },
          {
             "type": "TermQuery",
             "description": "message:search",
             "time_in_nanos": "205654",
             "breakdown": {...}
          }
       ]
    }
]

Подробные временные метки опушены для простоты.

На основе структуры профиля мы видим, что наш запрос match был переписан Lucene в BooleanQuery с двумя операндами (оба содержат TermQuery). Поле type отображает имя класса Lucene и часто соответствует эквивалентному имени в Elasticsearch. Поле description отображает текст объяснения Lucene для запроса и предназначено для дифференциации частей вашего запроса (например, оба message:get и message:search являются TermQuery и в противном случае выглядели бы одинаково).

Поле time_in_nanos показывает, что выполнение всего BooleanQuery заняло ~11,9 мс. Записанное время включает в себя время всех дочерних элементов.

Поле breakdown предоставит подробную статистику о том, как распределилось время; мы рассмотрим это чуть позже. Наконец, массив children перечисляет любые подзапросы, которые могут присутствовать. Поскольку мы искали два значения ("get search"), наш BooleanQuery содержит два дочерних TermQueries. Они имеют идентичную информацию (тип, время, разбиение и т. д.). Дочерние элементы могут иметь свои собственные дочерние элементы.

Разбиение по времени

Компонент breakdown перечисляет подробные статистические данные о временных метках низкого уровня выполнения Lucene:

"breakdown": {
  "set_min_competitive_score_count": 0,
  "match_count": 5,
  "shallow_advance_count": 0,
  "set_min_competitive_score": 0,
  "next_doc": 39022,
  "match": 4456,
  "next_doc_count": 5,
  "score_count": 5,
  "compute_max_score_count": 0,
  "compute_max_score": 0,
  "advance": 84525,
  "advance_count": 1,
  "score": 37779,
  "build_scorer_count": 2,
  "create_weight": 4694895,
  "shallow_advance": 0,
  "create_weight_count": 1,
  "build_scorer": 7112295
}

Временные метки указаны в наносекундах реального времени и никак не нормализованы. Все оговорки по поводу общего time_in_nanos относятся и к этому разделу. Цель разбиения — дать вам представление о том, A) какой механизм в Lucene фактически потребляет время, и B) величине различий во времени между различными компонентами. Как и общее время, разбиение по времени включает время всех дочерних элементов.

Значение статистических данных:

Все параметры:

create_weight

Запрос в Lucene должен иметь возможность повторного использования во множестве IndexSearchers (представьте себе движок, который выполняет поиск против конкретного индекса Lucene). Это ставит Lucene в трудную ситуацию, поскольку многие запросы должны накапливать временное состояние/статистику, связанную с индексом, с которым они используются, но контракт Query требует, чтобы он был неизменным.

Чтобы обойти это, Lucene просит каждый запрос сгенерировать объект Weight, который действует как временный объект контекста для хранения состояния, связанного с этой конкретной парой (IndexSearcher, Query). Метрика weight показывает, сколько времени занимает этот процесс

build_scorer

Этот параметр показывает, сколько времени занимает создание Scorer для запроса. Scorer — это механизм, который итерируется по соответствующим документам и генерирует оценку для каждого документа (например, насколько хорошо "foo" соответствует документу?). Обратите внимание, что это регистрирует время, необходимое для создания объекта Scorer, а не фактического подсчета баллов по документам. У разных запросов есть более быстрая или более медленная инициализация Scorer в зависимости от оптимизаций, сложности и т. д.

Это также может показывать временные метки, связанные с кэшированием, если оно включено и/или применимо для запроса

next_doc

Метод Lucene next_doc возвращает идентификатор документа следующего документа, соответствующего запросу. Эта статистика показывает время, необходимое для определения того, какой документ является следующим соответствием, процесс, который значительно варьируется в зависимости от природы запроса. Next_doc — это специализированная форма advance(), которая удобнее для многих запросов в Lucene. Она эквивалентна advance(docId() + 1)

advance

advance — это "нижележащая" версия next_doc: она выполняет ту же задачу поиска следующего соответствующего документа, но требует от вызывающего запроса выполнения дополнительных задач, таких как идентификация и пропуск пропусков и т. д. Однако не все запросы могут использовать next_doc, поэтому advance также отслеживается для этих запросов.

Конъюнкции (например, must операнды в Boolean) — типичные потребители advance

match

Некоторые запросы, такие как запросы по фразе, сопоставляют документы с помощью "двухэтапного" процесса. Сначала документ "приблизительно" сопоставляется, и если он приблизительно соответствует, он проверяется во второй раз с помощью более строгого (и дорогостоящего) процесса. Проверка на втором этапе — то, что измеряет статистика match.

Например, запрос по фразе сначала проверяет документ приблизительно, гарантируя, что все термины в фразе присутствуют в документе. Если все термины присутствуют, он выполняет проверку второго этапа, чтобы убедиться, что термины упорядочены для формирования фразы, что относительно дороже, чем просто проверка наличия терминов.

Поскольку этот двухэтапный процесс используется только небольшим количеством запросов, статистика match часто равна нулю

score

Это записывает время, необходимое для оценки конкретного документа с помощью его Scorer

*_count

Записывает количество вызовов конкретного метода. Например, "next_doc_count": 2, означает, что метод nextDoc() был вызван для двух разных документов. Это может использоваться для оценки селективности запросов путем сравнения подсчетов между различными компонентами запроса.

collectors Раздел

Раздел Collectors ответа отображает подробные сведения о выполнении на высоком уровне. Lucene работает, определяя «Collector», который отвечает за координацию обхода, оценки и сбора соответствующих документов. Collectors также позволяют одному запросу записывать результаты агрегации, выполнять несвязанные «глобальные» запросы, выполнять фильтры после запроса и т. д.

Рассмотрим предыдущий пример:

"collector": [
  {
    "name": "SimpleTopScoreDocCollector",
    "reason": "search_top_hits",
    "time_in_nanos": 775274
  }
]

Мы видим единственный коллектор с именем SimpleTopScoreDocCollector, заключенный в CancellableCollector. SimpleTopScoreDocCollector — это стандартный «оценивающий и сортирующий» Collector, используемый Elasticsearch. Поле reason пытается предоставить простое описание класса на естественном языке. time_in_nanos аналогичен времени в дереве запросов: время, прошедшее с учётом всех дочерних элементов. Аналогично, children перечисляет все подколлекторы. CancellableCollector, который оборачивает SimpleTopScoreDocCollector, используется Elasticsearch для определения, был ли текущий поиск отменён, и остановки сбора документов, как только это произойдёт.

Следует отметить, что время работы коллекторов независимо от времени запроса. Они вычисляются, комбинируются и нормализуются независимо! Из-за характера выполнения Lucene невозможно «слить» время работы коллекторов в раздел запроса, поэтому они отображаются в отдельных разделах.

Для справки, различные причины работы коллектора:

search_sorted

Коллектор, который оценивает и сортирует документы. Это наиболее распространённый коллектор, который будет виден в большинстве простых поисков

search_count

Коллектор, который только подсчитывает количество документов, соответствующих запросу, но не извлекает исходные данные. Это видно, когда указан параметр size: 0

search_terminate_after_count

Коллектор, который завершает выполнение поиска после того, как найдено n соответствующих документов. Это видно, когда указан параметр запроса terminate_after_count

search_min_score

Коллектор, который возвращает только соответствующие документы, которые имеют оценку выше n. Это видно, когда указан параметр верхнего уровня min_score.

search_multi

Коллектор, который оборачивает несколько других коллекторов. Это видно, когда сочетания поиска, агрегаций, глобальных агрегаций и постфильтров объединены в одном поиске.

search_timeout

Коллектор, который останавливает выполнение после истечения заданного промежутка времени. Это видно, когда указан параметр верхнего уровня timeout.

aggregation

Коллектор, который Elasticsearch использует для выполнения агрегаций по области действия запроса. Один коллектор aggregation используется для сбора документов для всех агрегаций, поэтому в имени будет отображаться список агрегаций.

global_aggregation

Коллектор, который выполняет агрегацию по глобальной области действия запроса, а не по указанной области действия. Поскольку глобальная область действия отличается от области действия выполняемого запроса, она должна выполнить свой собственный запрос match_all (который вы увидите в разделе запроса), чтобы собрать весь набор данных

rewrite Раздел

Все запросы в Lucene проходят процесс «переписывания». Запрос (и его подзапросы) могут быть переписаны один или несколько раз, и процесс продолжается до тех пор, пока запрос не перестанет изменяться. Этот процесс позволяет Lucene выполнять оптимизации, такие как удаление избыточных пунктов, замена одного запроса более эффективным путём выполнения и т. д. Например, Boolean → Boolean → TermQuery может быть переписан в TermQuery, поскольку все Boolean в этом случае излишни.

Процесс переписывания сложный и трудно отображаемый, так как запросы могут сильно меняться. Вместо отображения промежуточных результатов отображается общее время переписывания (в наносекундах). Это значение является кумулятивным и содержит общее время переписывания всех запросов.

Более сложный пример

Для демонстрации несколько более сложного запроса и связанных результатов мы можем проанализировать следующий запрос:

GET /my-index-000001/_search
{
  "profile": true,
  "query": {
    "term": {
      "user.id": {
        "value": "elkbee"
      }
    }
  },
  "aggs": {
    "my_scoped_agg": {
      "terms": {
        "field": "http.response.status_code"
      }
    },
    "my_global_agg": {
      "global": {},
      "aggs": {
        "my_level_agg": {
          "terms": {
            "field": "http.response.status_code"
          }
        }
      }
    }
  },
  "post_filter": {
    "match": {
      "message": "search"
    }
  }
}

В этом примере:

  • Запрос
  • Агрегация в рамках области видимости
  • Глобальная агрегация
  • Постфильтр

API возвращает следующий результат:

{
  ...
  "profile": {
    "shards": [
      {
        "id": "[P6-vulHtQRWuD4YnubWb7A][my-index-000001][0]",
        "searches": [
          {
            "query": [
              {
                "type": "TermQuery",
                "description": "message:search",
                "time_in_nanos": 141618,
                "breakdown": {
                  "set_min_competitive_score_count": 0,
                  "match_count": 0,
                  "shallow_advance_count": 0,
                  "set_min_competitive_score": 0,
                  "next_doc": 0,
                  "match": 0,
                  "next_doc_count": 0,
                  "score_count": 0,
                  "compute_max_score_count": 0,
                  "compute_max_score": 0,
                  "advance": 3942,
                  "advance_count": 4,
                  "score": 0,
                  "build_scorer_count": 2,
                  "create_weight": 38380,
                  "shallow_advance": 0,
                  "create_weight_count": 1,
                  "build_scorer": 99296
                }
              },
              {
                "type": "TermQuery",
                "description": "user.id:elkbee",
                "time_in_nanos": 163081,
                "breakdown": {
                  "set_min_competitive_score_count": 0,
                  "match_count": 0,
                  "shallow_advance_count": 0,
                  "set_min_competitive_score": 0,
                  "next_doc": 2447,
                  "match": 0,
                  "next_doc_count": 4,
                  "score_count": 4,
                  "compute_max_score_count": 0,
                  "compute_max_score": 0,
                  "advance": 3552,
                  "advance_count": 1,
                  "score": 5027,
                  "build_scorer_count": 2,
                  "create_weight": 107840,
                  "shallow_advance": 0,
                  "create_weight_count": 1,
                  "build_scorer": 44215
                }
              }
            ],
            "rewrite_time": 4769,
            "collector": [
              {
                "name": "MultiCollector",
                "reason": "search_multi",
                "time_in_nanos": 1945072,
                "children": [
                  {
                    "name": "FilteredCollector",
                    "reason": "search_post_filter",
                    "time_in_nanos": 500850,
                    "children": [
                      {
                        "name": "SimpleTopScoreDocCollector",
                        "reason": "search_top_hits",
                        "time_in_nanos": 22577
                      }
                    ]
                  },
                  {
                    "name": "MultiBucketCollector: [[my_scoped_agg, my_global_agg]]",
                    "reason": "aggregation",
                    "time_in_nanos": 867617
                  }
                ]
              }
            ]
          }
        ],
        "aggregations": [...], 
        "fetch": {...}
      }
    ]
  }
}

Часть "aggregations" опущена, поскольку она будет рассмотрена в следующем разделе.

Как вы можете видеть, вывод значительно более подробный, чем раньше. Представлены все основные части запроса:

  1. Первый TermQuery (user.id:elkbee) представляет основной запрос term.
  2. Второй TermQuery (message:search) представляет запрос post_filter.

Дерево коллекторов достаточно простое, демонстрируя, как один CancellableCollector оборачивает MultiCollector, который также оборачивает FilteredCollector для выполнения постфильтра (и в свою очередь оборачивает обычный оценивающий SimpleCollector), BucketCollector для выполнения всех агрегаций в рамках области видимости.

Понимание вывода MultiTermQuery

Необходимо сделать особое замечание относительно запросов класса MultiTermQuery. Это включает запросы с подстановкой, регулярными выражениями и с fuzzy-сопоставлением. Эти запросы генерируют очень подробные ответы и не сильно структурированы.

В сущности, эти запросы переписываются для каждого сегмента. Если представить себе запрос с подстановкой b*, он технически может соответствовать любому токену, начинающемуся с буквы «b». Было бы невозможно перечислить все возможные комбинации, поэтому Lucene переписывает запрос в контексте оцениваемого сегмента, например, один сегмент может содержать токены [bar, baz], поэтому запрос переписывается в сочетание BooleanQuery «bar» и «baz». Другой сегмент может содержать только токен [bakery], поэтому запрос переписывается в одиночный TermQuery для «bakery».

Из-за этой динамической переписывки для каждого сегмента структура дерева искажается и больше не соответствует чистой «родословной», показывающей, как один запрос переписывается в другой. На данный момент мы можем только извиниться и предложить свернуть детали для дочерних элементов этого запроса, если это слишком запутанно. К счастью, все статистические данные по времени правильные, просто не физическая структура ответа, поэтому достаточно проанализировать верхний MultiTermQuery и игнорировать его дочерние элементы, если подробности слишком сложны для интерпретации.

Надеемся, что это будет исправлено в будущих версиях, но это сложная проблема, которую предстоит решить.

Профилирование агрегаций

aggregations Раздел

Раздел aggregations содержит подробные временные данные дерева агрегаций, выполняемых конкретным фрагментом. Общая структура этого дерева агрегаций будет напоминать ваш исходный запрос Elasticsearch. Давайте выполним предыдущий запрос ещё раз и посмотрим на профиль агрегаций:

GET /my-index-000001/_search
{
  "profile": true,
  "query": {
    "term": {
      "user.id": {
        "value": "elkbee"
      }
    }
  },
  "aggs": {
    "my_scoped_agg": {
      "terms": {
        "field": "http.response.status_code"
      }
    },
    "my_global_agg": {
      "global": {},
      "aggs": {
        "my_level_agg": {
          "terms": {
            "field": "http.response.status_code"
          }
        }
      }
    }
  },
  "post_filter": {
    "match": {
      "message": "search"
    }
  }
}

Это даёт следующий вывод профиля агрегаций:

{
  "profile": {
    "shards": [
      {
        "aggregations": [
          {
            "type": "NumericTermsAggregator",
            "description": "my_scoped_agg",
            "time_in_nanos": 79294,
            "breakdown": {
              "reduce": 0,
              "build_aggregation": 30885,
              "build_aggregation_count": 1,
              "initialize": 2623,
              "initialize_count": 1,
              "reduce_count": 0,
              "collect": 45786,
              "collect_count": 4,
              "build_leaf_collector": 18211,
              "build_leaf_collector_count": 1,
              "post_collection": 929,
              "post_collection_count": 1
            },
            "debug": {
              "total_buckets": 1,
              "result_strategy": "long_terms",
              "built_buckets": 1
            }
          },
          {
            "type": "GlobalAggregator",
            "description": "my_global_agg",
            "time_in_nanos": 104325,
            "breakdown": {
              "reduce": 0,
              "build_aggregation": 22470,
              "build_aggregation_count": 1,
              "initialize": 12454,
              "initialize_count": 1,
              "reduce_count": 0,
              "collect": 69401,
              "collect_count": 4,
              "build_leaf_collector": 8150,
              "build_leaf_collector_count": 1,
              "post_collection": 1584,
              "post_collection_count": 1
            },
            "debug": {
              "built_buckets": 1
            },
            "children": [
              {
                "type": "NumericTermsAggregator",
                "description": "my_level_agg",
                "time_in_nanos": 76876,
                "breakdown": {
                  "reduce": 0,
                  "build_aggregation": 13824,
                  "build_aggregation_count": 1,
                  "initialize": 1441,
                  "initialize_count": 1,
                  "reduce_count": 0,
                  "collect": 61611,
                  "collect_count": 4,
                  "build_leaf_collector": 5564,
                  "build_leaf_collector_count": 1,
                  "post_collection": 471,
                  "post_collection_count": 1
                },
                "debug": {
                  "total_buckets": 1,
                  "result_strategy": "long_terms",
                  "built_buckets": 1
                }
              }
            ]
          }
        ]
      }
    ]
  }
}

Из структуры профиля видно, что my_scoped_agg выполняется как NumericTermsAggregator (потому что поле, по которому он выполняет агрегацию, http.response.status_code, является числовым). На том же уровне мы видим GlobalAggregator, который происходит от my_global_agg. Эта агрегация имеет дочерний NumericTermsAggregator, который происходит от второй агрегации по термину http.response.status_code.

Поле time_in_nanos отображает время выполнения каждой агрегации и включает время всех дочерних элементов. Хотя общее время полезно, поле breakdown предоставит подробную статистику о том, как расходуется время.

Некоторые агрегации могут возвращать экспертные debug сведения, которые описывают особенности базового выполнения агрегации, которые полезны для разработчиков, но которые, по нашему мнению, не должны быть полезны в других случаях. Они могут сильно различаться в зависимости от версии, агрегаций и стратегий выполнения агрегаций.

Детализация временных затрат

Компонент breakdown предоставляет подробную статистику о низкоуровневом выполнении:

"breakdown": {
  "reduce": 0,
  "build_aggregation": 30885,
  "build_aggregation_count": 1,
  "initialize": 2623,
  "initialize_count": 1,
  "reduce_count": 0,
  "collect": 45786,
  "collect_count": 4,
  "build_leaf_collector": 18211,
  "build_leaf_collector_count": 1
}

Каждая свойство в компоненте breakdown соответствует внутреннему методу агрегирования. Например, свойство build_leaf_collector измеряет наносекунды, потраченные на выполнение метода getLeafCollector() агрегирования. Свойства, оканчивающиеся на _count, записывают количество вызовов данного метода. Например, "collect_count": 2 означает, что агрегирование вызвало метод collect() для двух различных документов. Свойство reduce зарезервировано для будущего использования и всегда возвращает 0.

Временные затраты указаны в наносекундах реального времени и никак не нормированы. Здесь применимы все оговорки, касающиеся общих временных затрат time. Цель детализации — дать представление о том, какие компоненты Elasticsearch фактически занимают время и о величине различий во времени между различными компонентами. Как и общее время, детализация включает в себя время всех дочерних компонентов.

Профилирование Fetch

У всех фрагментов, из которых извлекаются документы, в профиле будет раздел fetch. Давайте выполним небольшой поиск и посмотрим на профиль извлечения:

GET /my-index-000001/_search?filter_path=profile.shards.fetch
{
  "profile": true,
  "query": {
    "term": {
      "user.id": {
        "value": "elkbee"
      }
    }
  }
}

И вот профиль извлечения:

{
  "profile": {
    "shards": [
      {
        "fetch": {
          "type": "fetch",
          "description": "",
          "time_in_nanos": 660555,
          "breakdown": {
            "next_reader": 7292,
            "next_reader_count": 1,
            "load_stored_fields": 299325,
            "load_stored_fields_count": 5
          },
          "debug": {
            "stored_fields": ["_id", "_routing", "_source"]
          },
          "children": [
            {
              "type": "FetchSourcePhase",
              "description": "",
              "time_in_nanos": 20443,
              "breakdown": {
                "next_reader": 745,
                "next_reader_count": 1,
                "process": 19698,
                "process_count": 5
              },
              "debug": {
                "fast_path": 4
              }
            }
          ]
        }
      }
    ]
  }
}

Поскольку это отладочная информация о том, как Elasticsearch выполняет извлечение, она может меняться от запроса к запросу и от версии к версии. Даже исправления могут изменить вывод здесь. Именно это отсутствие согласованности делает его полезным для отладки.

В любом случае! time_in_nanos измеряет общее время фазы извлечения. breakdown подсчитывает и измеряет время подготовки каждого сегмента в next_reader и время загрузки хранимых полей в load_stored_fields. Раздел Debug содержит различную информацию, не связанную с измерением времени, в частности, stored_fields перечисляет хранимые поля, которые извлечение должно загрузить. Если это пустой список, то извлечение полностью пропустит загрузку хранимых полей.

Раздел children перечисляет подфазы, выполняющие фактическую работу по извлечению, а breakdown содержит подсчеты и временные затраты на подготовку каждого сегмента в next_reader и извлечение каждого документа в process.

Мы стараемся загрузить все необходимые для извлечения хранимые поля сразу. Это, как правило, делает фазу _source несколькими микросекундами на каждый элемент. В этом случае реальная стоимость фазы _source скрыта в компоненте load_stored_fields детализации. Возможно полностью пропустить загрузку хранимых полей, установив "_source": false, "stored_fields": ["_none_"].

Учет при профилировании

Как и любой профилировщик, API профиля вносит неотъемлемую задержку в выполнение поиска. Действие инструментирования вызовов низкоуровневых методов, таких как collect, advance и next_doc, может быть достаточно дорогим, так как эти методы вызываются в узких циклах. Поэтому профилирование не должно включаться по умолчанию в производственных условиях и не должно сравниваться с временем выполнения запросов без профилирования. Профилирование — это просто инструмент диагностики.

Также есть случаи, когда специальные оптимизации Lucene отключены, так как они не подходят для профилирования. Это может привести к тому, что некоторые запросы покажут более высокие относительные времена, чем их не-профилированные аналоги, но в целом это не должно иметь существенного влияния по сравнению с другими компонентами в профилируемом запросе.

Ограничения

  • В настоящее время профилирование не измеряет сетевые затраты.
  • Профилирование также не учитывает время ожидания в очереди, объединение ответов фрагментов на узле координации или дополнительную работу, такую как построение глобальных порядков (внутренняя структура данных, используемая для ускорения поиска).
  • В настоящее время статистика профилирования недоступна для предложений, выделения, dfs_query_then_fetch.
  • В настоящее время недоступно профилирование фазы сокращения агрегирования.
  • Профилировщик инструментирует внутренние компоненты, которые могут изменяться от версии к версии. Результирующий JSON следует считать в основном нестабильным, особенно вещи в разделе debug.

© 2023-2025 Elasticsearch
As of September 2024, Elasticsearch is available under a choice of three licenses: the Server Side Public License (SSPL), the Elastic License, or the AGPLv3 (OSI approved).
Elasticsearch and the Elasticsearch logo are trademarks of Elasticsearch B.V., registered in the U.S. and in other countries.
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-profile.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API