Агрегация терминов (для достижения иерархической огранки) низкая производительность запросов

Я индексирую имена метрик в эластичном поиске. Имена метрик имеют форму foo.bar.baz.aux. Вот индекс, который я использую.

{
    "index": {
        "analysis": {
            "analyzer": {
                "prefix-test-analyzer": {
                    "filter": "dotted",
                    "tokenizer": "prefix-test-tokenizer",
                    "type": "custom"
                }
            },
            "filter": {
                "dotted": {
                    "patterns": [
                        "([^.]+)"
                    ],
                    "type": "pattern_capture"
                }
            },
            "tokenizer": {
                "prefix-test-tokenizer": {
                    "delimiter": ".",
                    "type": "path_hierarchy"
                }
            }
        }
    }
}

{
    "metrics": {
        "_routing": {
            "required": true
        },
        "properties": {
            "tenantId": {
                "type": "string",
                "index": "not_analyzed"
            },
            "unit": {
                "type": "string",
                "index": "not_analyzed"
            },
            "metric_name": {
                "index_analyzer": "prefix-test-analyzer",
                "search_analyzer": "keyword",
                "type": "string"
            }
        }
    }
}

Приведенный выше индекс создает следующие термины для имени метрики foo.bar.baz

foo
bar
baz
foo.bar
foo.bar.baz

Если у меня есть куча метрик, как показано ниже

a.b.c.d.e
a.b.c.d
a.b.m.n
x.y.z

Мне нужно написать запрос, чтобы получить токены n-го уровня. В приведенном выше примере

for level = 0, I should get [a, x] 
for level = 1, with 'a' as first token I should get [b]
               with 'x' as first token I should get [y]  
for level = 2, with 'a.b' as first token I should get [c, m]

Я не мог придумать никакого другого способа, кроме как написать агрегацию терминов. Чтобы выяснить токены уровня 2 a.b, вот запрос, который я придумал.

time curl -XGET http://localhost:9200/metrics_alias/metrics/_search\?pretty\&routing\=12345 -d '{
      "size": 0,
      "query": {
        "term": {
            "tenantId": "12345"
        }
      },
      "aggs": {
          "metric_name_tokens": {
              "terms": {
                  "field" : "metric_name",
                  "include": "a[.]b[.][^.]*",
                  "execution_hint": "map",
                  "size": 0
              }
          }
      }
  }'

Это приведет к следующим ведрам. Я анализирую вывод и беру оттуда [c, m].

"buckets" : [ {
     "key" : "a.b.c",
     "doc_count" : 2
   }, {
     "key" : "a.b.m",
     "doc_count" : 1
 } ]

Все идет нормально. Запрос отлично работает для большинства арендаторов (обратите внимание на запрос tenantId term выше). Для некоторых арендаторов с большими объемами данных (около 1 миллиона) производительность очень низкая. Я предполагаю, что агрегация всех терминов требует времени.

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


person Chandra    schedule 14.07.2016    source источник
comment
Мне непонятно, что вам нужно. Вам нужны счетчики? Или вам нужны c и m? Или вам нужны документы, содержащие: a.b.c.d.e - a.b.c.d - a.b.m.n?   -  person Jettro Coenradie    schedule 14.07.2016
comment
Мне просто нужно с и м.   -  person Chandra    schedule 14.07.2016
comment
@JettroCoenradie Мне просто нужны c и m. В основном, что когда-либо является следующим уровнем возможных токенов для данного префикса.   -  person Chandra    schedule 14.07.2016
comment
Как выглядит кластер? (сколько узлов, сколько оперативной памяти, сколько ядер процессора, каков размер кучи) metrics_alias сколько индексов он затрагивает? Насколько они велики? Когда вы запускаете тяжелый запрос (с миллионами метрик), как используется ЦП и куча для узлов?   -  person Andrei Stefan    schedule 18.07.2016
comment
@AndreiStefan Это кластер из 3 узлов с 125 ГБ ОЗУ и 40 ядрами. ES работает с размером кучи 31G. metric_alias касается только 2 индексов (один больше не обновляется). Около 20 ГБ данных. Я не вижу ничего другого в использовании ЦП и кучи при выполнении запроса.   -  person Chandra    schedule 18.07.2016
comment
Я бы предложил также отразить фильтр на уровне агрегации в части query. Таким образом, для сопоставления a.b. используйте это как запрос: ` запрос: { bool: { must: [ {term: {tenantId: 123 } }, { prefix: { metric_name: { value: a.b. } } } ] } }` и сохраните тот же раздел aggs. Проверьте это и дайте мне знать, как это происходит. Запустите его несколько раз, а не один раз.   -  person Andrei Stefan    schedule 19.07.2016
comment
Кроме того, еще одна проблема с запросом — "size": 0 для части агрегации. Вам действительно нужна агрегация, чтобы вернуть все ведра, независимо от того, сколько их. Можете ли вы проверить запрос, например, с помощью "size": 100?   -  person Andrei Stefan    schedule 19.07.2016
comment
Кто бы ни проголосовал за вопрос, могу я узнать причину этого?   -  person Chandra    schedule 19.07.2016
comment
@AndreiStefan Изменение размера до 100 не дало большого эффекта. Общее время запроса осталось прежним.   -  person Chandra    schedule 19.07.2016
comment
@AndreiStefan Добавление фильтра регулярных выражений, отражающего фильтр на уровне агрегации, оказало большое влияние. Время выполнения запроса, который я тестировал, сократилось с 23 до 11 секунд. Но производительность по-прежнему недостаточно хороша.   -  person Chandra    schedule 19.07.2016
comment
@AndreiStefan Добавление фильтра регулярного выражения, который отражает фильтр на уровне агрегации, действительно помогает, когда мы запрашиваем токены более высокого уровня. Например: следующие уровни abcd. Но для более низких уровней, таких как a.b, это лучше, но все же не так хорошо, как хотелось бы.   -  person Chandra    schedule 19.07.2016
comment
Рад, что один из них изменил ситуацию. Теперь скажите мне, каков размер на диске этих двух индексов. Только первички 20 Гб или первички + реплики 20 Гб? Сколько основных и сколько реплик имеют два индекса?   -  person Andrei Stefan    schedule 19.07.2016
comment
Кроме того, удалите "execution_hint": "map" и дайте Elasticsearch использовать значения по умолчанию.   -  person Andrei Stefan    schedule 19.07.2016
comment
Запрос, который выполняется за 11 секунд, теперь занимает около 50 секунд, если я удалю execute_hint: map   -  person Chandra    schedule 19.07.2016
comment
Размер 20 ГБ, который я дал вам ранее, был праймериз. Общий размер составляет около 60 ГБ. metric_alias указывает на два индекса. Общий размер одного индекса составляет 15 ГБ, а другого индекса — 45 ГБ данных. Каждый индекс имеет 5 основных сегментов и 10 сегментов реплик.   -  person Chandra    schedule 19.07.2016
comment
The query which runs in 11 secs now took around 50 secs if I remove "execution_hint": "map"... хм... а вы, если у вас все еще execution_hint не включено, и вы запускаете запрос несколько раз (скажем, 10 раз), он все равно возвращается через 50 секунд?   -  person Andrei Stefan    schedule 19.07.2016
comment
Запуск запроса после удаления execute_hint 10 раз, и это время выполнения (округлено) 59,51,23,55,47,23,43,48,1мин,1мин.   -  person Chandra    schedule 19.07.2016
comment
Понятно. Вставьте его обратно :-). Затем выполните запрос несколько раз. После 5-6 запусков запустите его еще раз и будьте готовы получить список hot_threads из кластера: curl -XGET "http://localhost:9200/_nodes/hot_threads?threads=5000". Итак, во время выполнения запроса (11 секунд) запустите предыдущую hot_threads команду дважды с интервалом 2-3 секунды и сохраните каждый запуск в отдельном текстовом файле. Используйте gist, чтобы поделиться ими.   -  person Andrei Stefan    schedule 19.07.2016
comment
@AndreiStefan Вот так. gist.github.com/ChandraAddala/d70904b90027217db4192e34b70dde85   -  person Chandra    schedule 19.07.2016
comment
Кстати, я предложил использовать фильтр prefix, а не regexp. Я чувствую, что prefix быстрее, а также делает то же самое (с точки зрения фильтрации), что и регулярное выражение. Пожалуйста, попробуйте использовать в качестве фильтра prefix, а не regexp.   -  person Andrei Stefan    schedule 19.07.2016
comment
И да, поисковая часть проводит большую часть времени в регулярном выражении внутри агрегации. Как было предложено ранее, попробуйте использовать prefix в части фильтра и посмотрите, что получится. И, как вы понимаете, регулярное выражение в агрегации только потому, что я не вижу другого способа фильтровать термины.   -  person Andrei Stefan    schedule 19.07.2016
comment
На самом деле регулярное выражение работало бы лучше для меня, так как кто-то мог бы выполнить поиск, например, a.b.*.d, чтобы найти следующий токен. Также я заметил, что регулярное выражение выполняется быстрее, чем префиксный запрос. Использование префиксного запроса занимает в среднем около 12-13 секунд, а использование регулярного выражения - в среднем 11-12 секунд.   -  person Chandra    schedule 19.07.2016
comment
Если я запускаю префиксный запрос несколько раз, также выполняется около 11-12 секунд.   -  person Chandra    schedule 19.07.2016
comment
Единственное, что я мог придумать, это уменьшить давление во время поиска, переместив его во время индексации. Что я подразумеваю под этим: во время индексации в вашем собственном приложении или любом другом методе индексации, который вы используете, разделите текст для индексации программно (а не ES) и индексируйте каждый элемент в иерархии в отдельном поле. Например, a.b в field2, a.b.c в field3 и так далее. Это для того же документа. Затем во время поиска вы просматриваете определенные поля в зависимости от искомого текста. Однако вся эта идея требует дополнительной работы вне ES.   -  person Andrei Stefan    schedule 19.07.2016


Ответы (1)


Некоторые предложения:

  • «отзеркалить» фильтр на уровне агрегации и в части запроса. Итак, для сопоставления a.b. используйте следующий запрос и сохраните тот же раздел ggs:
"bool": {
  "must": [
    {
      "term": {
        "tenantId": 123
      }
    },
    {
      "prefix": {
        "metric_name": {
          "value": "a.b."
        }
      }
    }
  ]
}

или даже использовать regexp с тем же регулярным выражением, что и в части агрегации. Таким образом, агрегатам придется оценивать меньше сегментов, поскольку документов, достигающих агрегатной части, будет меньше. Вы упомянули, что regexp работает лучше для вас, я изначально предполагал, что prefix будет работать лучше.

  • измените "size": 0 с агрегатов на "size": 100. После тестирования вы упомянули, что это не имеет никакого значения
  • удалите "execution_hint": "map" и дайте Elasticsearch использовать значения по умолчанию. После тестирования вы упомянули, что execution_hint по умолчанию работает намного хуже.
  • единственное, что я мог придумать, это уменьшить давление во время поиска, переместив его во время индексации. Что я подразумеваю под этим: во время индексации в вашем собственном приложении или любом другом методе индексации, который вы используете, разделите текст для индексации программно (а не ES) и индексируйте каждый элемент в иерархии в отдельном поле. Например, a.b в field2, a.b.c в field3 и так далее. Это для того же документа. Затем во время поиска вы просматриваете определенные поля в зависимости от искомого текста. Однако вся эта идея требует дополнительной работы вне ES.

Из всех приведенных выше предложений наибольшее влияние оказало первое: время ответа на запросы улучшилось с 23 до 11 секунд.

person Andrei Stefan    schedule 19.07.2016
comment
В соответствии с вашими предложениями для строк a.b.c.d.e, a.b.x.y я буду индексировать индекс [a], [a.b], [ab.c], [a.b.c.d], [a.b.c.d.e], [ab.x], [ab.x.y] отдельно, а затем как мне искать, чтобы получить все токены, следующие за префиксом a.b? - person Chandra; 20.07.2016
comment
Для документа у вас будет следующая структура: {"tenantId":123,"metric_name1":["a","x"],"metric_name2":["a.b","x.y"],"metric_name3":["a.b.c","a.b.m","x.y.z"],"metric_name4":["a.b.c.d","a.b.m.n"],"metric_name5":["a.b.c.d.e"]}. Все поля metric_name* будут использовать анализатор keyword или что-то еще, что не разделит его. Сам запрос (поскольку вы хотите, чтобы ваши пользователи могли искать a.b.*.d, а не только prefix запросов) должен иметь вид "query_string": { "default_field": "metric_name*", "query": "a.b.*.d" }. - person Andrei Stefan; 20.07.2016
comment
Агрегации будут примерно такими, хотя "aggs": { "metric_name_tokens": { "terms": { "field": "metric_name3", "size": 0 } } }. Таким образом, в основном для поиска a.b.*.d вас интересует только то, что идет после a.b.. И вы будете использовать поле metric_name3 (с тремя уровнями иерархии). Мне было бы любопытно посмотреть, какую производительность вы получите от запроса query_string. Агрегатная часть будет намного легче. - person Andrei Stefan; 20.07.2016
comment
При написании предыдущего комментария у меня возникла еще одна идея: если query_string выше не обеспечивает лучшую производительность по сравнению с regexp, которую вы используете сейчас, вы можете сохранить оба подхода в одном индексе. Это означает, что вы используете metric_name со своим собственным анализатором (как вы это делаете сейчас), но также вы добавляете поля metric_name*, которые я предложил выше, и вы будете использовать только metric_name* в агрегатах (как я показал выше). - person Andrei Stefan; 20.07.2016
comment
Когда я сказал a.b.*.d. Я имел в виду с префиксом как a.b.*.d. Для входных строк [a.b.x.d.e, a.b.y.d.f] с префиксом a.b.*.d следующим уровнем токенов будет [e. ф]. Но я считаю, что идея, которую вы предложили, подходит и для этого случая. - person Chandra; 21.07.2016