Spec-Zone.ru › Elasticsearch 8
›Elasticsearch Guide [8.17] ›Агрегации ›Агрегации метрик

Агрегация метрик на основе скриптов

Агрегация метрик, которая выполняется с помощью скриптов для получения метрического результата.

scripted_metric недоступна в Elastic Cloud Serverless.

Использование скриптов может привести к замедлению скорости поиска. См. Скрипты, кэширование и скорость поиска. При использовании агрегации метрик на основе скриптов её промежуточное состояние сериализуется в байтовый массив в оперативной памяти для передачи на другие узлы во время процесса агрегации. Вследствие этого сложная агрегация метрик на основе скриптов также может столкнуться с ограничением в 2 ГБ, наложенным на массивы Java.

Пример:

resp = client.search(
    index="ledger",
    size="0",
    query={
        "match_all": {}
    },
    aggs={
        "profit": {
            "scripted_metric": {
                "init_script": "state.transactions = []",
                "map_script": "state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)",
                "combine_script": "double profit = 0; for (t in state.transactions) { profit += t } return profit",
                "reduce_script": "double profit = 0; for (a in states) { profit += a } return profit"
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'ledger',
  size: 0,
  body: {
    query: {
      match_all: {}
    },
    aggregations: {
      profit: {
        scripted_metric: {
          init_script: 'state.transactions = []',
          map_script: "state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)",
          combine_script: 'double profit = 0; for (t in state.transactions) { profit += t } return profit',
          reduce_script: 'double profit = 0; for (a in states) { profit += a } return profit'
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "ledger",
  size: 0,
  query: {
    match_all: {},
  },
  aggs: {
    profit: {
      scripted_metric: {
        init_script: "state.transactions = []",
        map_script:
          "state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)",
        combine_script:
          "double profit = 0; for (t in state.transactions) { profit += t } return profit",
        reduce_script:
          "double profit = 0; for (a in states) { profit += a } return profit",
      },
    },
  },
});
console.log(response);
POST ledger/_search?size=0
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "profit": {
      "scripted_metric": {
        "init_script": "state.transactions = []", 
        "map_script": "state.transactions.add(doc.type.value == 'sale' ? doc.amount.value : -1 * doc.amount.value)",
        "combine_script": "double profit = 0; for (t in state.transactions) { profit += t } return profit",
        "reduce_script": "double profit = 0; for (a in states) { profit += a } return profit"
      }
    }
  }
}

init_script — это необязательный параметр, все остальные скрипты обязательны.

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

Результат выполнения вышеуказанной агрегации:

{
  "took": 218,
  ...
  "aggregations": {
    "profit": {
      "value": 240.0
    }
  }
}

Вышеприведённый пример также можно задать с помощью сохранённых скриптов следующим образом:

resp = client.search(
    index="ledger",
    size="0",
    aggs={
        "profit": {
            "scripted_metric": {
                "init_script": {
                    "id": "my_init_script"
                },
                "map_script": {
                    "id": "my_map_script"
                },
                "combine_script": {
                    "id": "my_combine_script"
                },
                "params": {
                    "field": "amount"
                },
                "reduce_script": {
                    "id": "my_reduce_script"
                }
            }
        }
    },
)
print(resp)
response = client.search(
  index: 'ledger',
  size: 0,
  body: {
    aggregations: {
      profit: {
        scripted_metric: {
          init_script: {
            id: 'my_init_script'
          },
          map_script: {
            id: 'my_map_script'
          },
          combine_script: {
            id: 'my_combine_script'
          },
          params: {
            field: 'amount'
          },
          reduce_script: {
            id: 'my_reduce_script'
          }
        }
      }
    }
  }
)
puts response
const response = await client.search({
  index: "ledger",
  size: 0,
  aggs: {
    profit: {
      scripted_metric: {
        init_script: {
          id: "my_init_script",
        },
        map_script: {
          id: "my_map_script",
        },
        combine_script: {
          id: "my_combine_script",
        },
        params: {
          field: "amount",
        },
        reduce_script: {
          id: "my_reduce_script",
        },
      },
    },
  },
});
console.log(response);
POST ledger/_search?size=0
{
  "aggs": {
    "profit": {
      "scripted_metric": {
        "init_script": {
          "id": "my_init_script"
        },
        "map_script": {
          "id": "my_map_script"
        },
        "combine_script": {
          "id": "my_combine_script"
        },
        "params": {
          "field": "amount"           
        },
        "reduce_script": {
          "id": "my_reduce_script"
        }
      }
    }
  }
}

Параметры скрипта для init, map и combine скриптов должны быть указаны в глобальном объекте params, чтобы они могли быть разделены между скриптами.

Дополнительные сведения о настройке скриптов см. в документации по скриптам.

Разрешенные типы возвращаемых значений

Хотя любой допустимый объект скрипта может использоваться в одном скрипте, скрипты должны возвращать или сохранять в объекте state только следующие типы:

  • примитивные типы
  • Строка
  • Карта (содержащая только ключи и значения перечисленных здесь типов)
  • Массив (содержащий элементы только перечисленных здесь типов)

Область действия скриптов

Агрегация метрик на основе скриптов использует скрипты на 4 этапах своего выполнения:

init_script

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

В приведённом выше примере init_script создаёт массив transactions в объекте state.

map_script

Выполняется один раз на каждый собранный документ. Это обязательный скрипт.

В приведённом выше примере map_script проверяет значение поля типа. Если значение равно sale, значение поля amount добавляется в массив transactions. Если значение поля типа не равно sale, отрицательное значение поля amount добавляется в transactions.

combine_script

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

В приведённом выше примере combine_script перебирает все хранимые транзакции, суммирует значения в переменной profit и, наконец, возвращает profit.

reduce_script

Выполняется один раз на узле координатора после того, как все фрагменты вернули свои результаты. Это обязательный скрипт. Скрипту предоставляется доступ к переменной states, которая представляет собой массив результатов выполнения combine_script на каждом фрагменте.

В приведённом выше примере reduce_script перебирает массив profit, возвращаемый каждым фрагментом, суммирует значения, прежде чем вернуть окончательную объединённую прибыль, которая будет возвращена в ответе агрегации.

Пример работы

Представьте ситуацию, когда вы индексируете следующие документы в индекс с 2 фрагментами:

resp = client.bulk(
    index="transactions",
    refresh=True,
    operations=[
        {
            "index": {
                "_id": 1
            }
        },
        {
            "type": "sale",
            "amount": 80
        },
        {
            "index": {
                "_id": 2
            }
        },
        {
            "type": "cost",
            "amount": 10
        },
        {
            "index": {
                "_id": 3
            }
        },
        {
            "type": "cost",
            "amount": 30
        },
        {
            "index": {
                "_id": 4
            }
        },
        {
            "type": "sale",
            "amount": 130
        }
    ],
)
print(resp)
response = client.bulk(
  index: 'transactions',
  refresh: true,
  body: [
    {
      index: {
        _id: 1
      }
    },
    {
      type: 'sale',
      amount: 80
    },
    {
      index: {
        _id: 2
      }
    },
    {
      type: 'cost',
      amount: 10
    },
    {
      index: {
        _id: 3
      }
    },
    {
      type: 'cost',
      amount: 30
    },
    {
      index: {
        _id: 4
      }
    },
    {
      type: 'sale',
      amount: 130
    }
  ]
)
puts response
const response = await client.bulk({
  index: "transactions",
  refresh: "true",
  operations: [
    {
      index: {
        _id: 1,
      },
    },
    {
      type: "sale",
      amount: 80,
    },
    {
      index: {
        _id: 2,
      },
    },
    {
      type: "cost",
      amount: 10,
    },
    {
      index: {
        _id: 3,
      },
    },
    {
      type: "cost",
      amount: 30,
    },
    {
      index: {
        _id: 4,
      },
    },
    {
      type: "sale",
      amount: 130,
    },
  ],
});
console.log(response);
PUT /transactions/_bulk?refresh
{"index":{"_id":1}}
{"type": "sale","amount": 80}
{"index":{"_id":2}}
{"type": "cost","amount": 10}
{"index":{"_id":3}}
{"type": "cost","amount": 30}
{"index":{"_id":4}}
{"type": "sale","amount": 130}

Допустим, документы 1 и 3 попадают на фрагмент A, а документы 2 и 4 — на фрагмент B. Ниже приведён подробный разбор результата агрегации на каждом этапе приведённого выше примера.

Перед init_script

state инициализируется как новый пустой объект.

"state" : {}

После init_script

Это выполняется один раз на каждом фрагменте перед сбором любых документов, и поэтому у нас будет копия на каждом фрагменте:

Фрагмент A
"state" : {
    "transactions" : []
}
Фрагмент B
"state" : {
    "transactions" : []
}

После map_script

Каждый фрагмент собирает свои документы и выполняет map_script для каждого собранного документа:

Фрагмент A
"state" : {
    "transactions" : [ 80, -30 ]
}
Фрагмент B
"state" : {
    "transactions" : [ -10, 130 ]
}

После combine_script

combine_script выполняется на каждом фрагменте после завершения сбора документов и сводит все транзакции к одной сумме прибыли для каждого фрагмента (суммируя значения в массиве transactions), которая передаётся узлу координатора:

Фрагмент A
50
Фрагмент B
120

После reduce_script

reduce_script получает массив states, содержащий результат combine_script для каждого фрагмента:

"states" : [
    50,
    120
]

Он сводит ответы фрагментов к окончательной общей сумме прибыли (суммируя значения) и возвращает её как результат агрегации, чтобы получить ответ:

{
  ...

  "aggregations": {
    "profit": {
      "value": 170
    }
  }
}

Другие параметры

params

Необязательно. Объект, содержимое которого будет передано в качестве переменных в init_script, map_script и combine_script. Это может быть полезно для управления поведением агрегации и для сохранения состояния между скриптами. Если это не указано, по умолчанию эквивалентно предоставлению:

"params" : {}

Пустые корзины

Если родительская корзина агрегации метрик на основе скриптов не собирает никаких документов, из фрагмента будет возвращён пустой ответ агрегации со значением null. В этом случае переменная reduce_script будет содержать null в качестве ответа от этого фрагмента. reduce_script должны, следовательно, ожидать и обрабатывать ответы null от фрагментов.

© 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/8.17/search-aggregations-metrics-scripted-metric-aggregation.html

Spec-Zone.ru

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