Как писать скрипты
В тех API Elasticsearch, где поддерживается скриптование, синтаксис остается неизменным; вы указываете язык скрипта, предоставляете логику (или исходный код) скрипта и добавляете параметры, которые передаются в скрипт:
"script": {
"lang": "...",
"source" | "id": "...",
"params": { ... }
} -
lang - Указывает язык, на котором написан скрипт. По умолчанию —
painless. -
source,id - Сам скрипт, который вы указываете как
sourceдля встроенного скрипта илиidдля сохранённого скрипта. Используйте API сохранённых скриптов для создания и управления сохранёнными скриптами. -
params - Указывает любые именованные параметры, которые передаются в скрипт как переменные. Используйте параметры вместо жёстко заданных значений, чтобы уменьшить время компиляции.
Напишите свой первый скрипт
Painless — это язык скриптов по умолчанию для Elasticsearch. Он безопасен, производителен и предоставляет естественный синтаксис для любого, у кого есть небольшой опыт программирования.
Скрипт Painless структурирован как одна или несколько инструкций и, необязательно, содержит одну или несколько определённых пользователем функций в начале. Скрипт всегда должен содержать как минимум одну инструкцию.
API Painless execute предоставляет возможность тестирования скрипта с простыми параметрами, определёнными пользователем, и получением результата. Начнём с полного скрипта и рассмотрим его составные части.
Сначала проиндексируем документ с одним полем, чтобы у нас были данные для работы:
resp = client.index(
index="my-index-000001",
id="1",
document={
"my_field": 5
},
)
print(resp) response = client.index(
index: 'my-index-000001',
id: 1,
body: {
my_field: 5
}
)
puts response const response = await client.index({
index: "my-index-000001",
id: 1,
document: {
my_field: 5,
},
});
console.log(response); PUT my-index-000001/_doc/1
{
"my_field": 5
} Затем мы можем создать скрипт, который работает с этим полем, и запустить его в рамках запроса. Следующий запрос использует параметр script_fields API поиска для получения значения скрипта. Здесь происходит много вещей, но мы разберём его компоненты, чтобы понять их по отдельности. Сейчас вам нужно только понять, что этот скрипт принимает my_field и работает с ним.
resp = client.search(
index="my-index-000001",
script_fields={
"my_doubled_field": {
"script": {
"source": "doc['my_field'].value * params['multiplier']",
"params": {
"multiplier": 2
}
}
}
},
)
print(resp) response = client.search(
index: 'my-index-000001',
body: {
script_fields: {
my_doubled_field: {
script: {
source: "doc['my_field'].value * params['multiplier']",
params: {
multiplier: 2
}
}
}
}
}
)
puts response const response = await client.search({
index: "my-index-000001",
script_fields: {
my_doubled_field: {
script: {
source: "doc['my_field'].value * params['multiplier']",
params: {
multiplier: 2,
},
},
},
},
});
console.log(response); GET my-index-000001/_search
{
"script_fields": {
"my_doubled_field": {
"script": {
"source": "doc['my_field'].value * params['multiplier']",
"params": {
"multiplier": 2
}
}
}
}
} |
| |
|
|
script — это стандартный JSON-объект, определяющий скрипты в большинстве API Elasticsearch. Этот объект требует source для определения самого скрипта. Скрипт не указывает язык, поэтому он по умолчанию Painless.
Использование параметров в вашем скрипте
В первый раз, когда Elasticsearch видит новый скрипт, он компилирует его и сохраняет скомпилированную версию в кэше. Компиляция может быть ресурсоёмким процессом. Вместо того, чтобы жёстко задавать значения в вашем скрипте, передавайте их как именованные params.
Например, в предыдущем скрипте мы могли бы просто жёстко задать значения и написать скрипт, который кажется менее сложным. Мы могли бы просто получить первое значение для my_field, а затем умножить его на 2:
"source": "return doc['my_field'].value * 2"
Хотя это работает, это довольно жёсткое решение. Нам нужно изменять исходный код скрипта для изменения множителя, и Elasticsearch должен перекомпилировать скрипт каждый раз, когда изменяется множитель.
Вместо жёсткого кодирования значений используйте именованные params, чтобы сделать скрипты гибкими и также уменьшить время компиляции во время выполнения скрипта. Теперь вы можете изменять параметр multiplier, не перекомпилируя скрипт Elasticsearch.
"source": "doc['my_field'].value * params['multiplier']",
"params": {
"multiplier": 2
} По умолчанию вы можете скомпилировать до 150 скриптов за 5 минут. Для контекстов ingest скорость компиляции скриптов по умолчанию неограничена.
script.context.field.max_compilations_rate=100/10m
Если вы скомпилируете слишком много уникальных скриптов в короткий промежуток времени, Elasticsearch отклонит новые динамические скрипты с ошибкой circuit_breaking_exception.
Укоротите свой скрипт
Используя синтаксические возможности, встроенные в Painless, вы можете сократить объём кода в ваших скриптах и сделать их короче. Вот простой скрипт, который мы можем укоротить:
resp = client.search(
index="my-index-000001",
script_fields={
"my_doubled_field": {
"script": {
"lang": "painless",
"source": "doc['my_field'].value * params.get('multiplier');",
"params": {
"multiplier": 2
}
}
}
},
)
print(resp) response = client.search(
index: 'my-index-000001',
body: {
script_fields: {
my_doubled_field: {
script: {
lang: 'painless',
source: "doc['my_field'].value * params.get('multiplier');",
params: {
multiplier: 2
}
}
}
}
}
)
puts response const response = await client.search({
index: "my-index-000001",
script_fields: {
my_doubled_field: {
script: {
lang: "painless",
source: "doc['my_field'].value * params.get('multiplier');",
params: {
multiplier: 2,
},
},
},
},
});
console.log(response); GET my-index-000001/_search
{
"script_fields": {
"my_doubled_field": {
"script": {
"lang": "painless",
"source": "doc['my_field'].value * params.get('multiplier');",
"params": {
"multiplier": 2
}
}
}
}
} Давайте посмотрим на укороченную версию скрипта, чтобы увидеть, какие улучшения она включает по сравнению с предыдущей итерацией:
resp = client.search(
index="my-index-000001",
script_fields={
"my_doubled_field": {
"script": {
"source": "field('my_field').get(null) * params['multiplier']",
"params": {
"multiplier": 2
}
}
}
},
)
print(resp) const response = await client.search({
index: "my-index-000001",
script_fields: {
my_doubled_field: {
script: {
source: "field('my_field').get(null) * params['multiplier']",
params: {
multiplier: 2,
},
},
},
},
});
console.log(response); GET my-index-000001/_search
{
"script_fields": {
"my_doubled_field": {
"script": {
"source": "field('my_field').get(null) * params['multiplier']",
"params": {
"multiplier": 2
}
}
}
}
} В этой версии скрипта удалены несколько компонентов и значительно упрощён синтаксис:
- Объявление
lang. Поскольку Painless — язык по умолчанию, вам не нужно указывать язык, если вы пишете скрипт Painless. - Ключевое слово
return. Painless автоматически использует последнюю инструкцию в скрипте (если возможно), чтобы сгенерировать возвращаемое значение в контексте скрипта, который требует его. - Метод
get, который заменён скобками[]. Painless использует сокращение специально для типаMap, которое позволяет нам использовать скобки вместо более длинного методаget. - Точка с запятой в конце инструкции
source. Painless не требует точки с запятой для последней инструкции блока. Однако он требует их в других случаях, чтобы избежать неоднозначности.
Используйте этот сокращённый синтаксис везде, где Elasticsearch поддерживает скрипты, например, при создании runtime-полей.
Сохранение и получение скриптов
Вы можете сохранять и получать скрипты из состояния кластера с помощью API сохранённых скриптов. Сохранённые скрипты позволяют ссылаться на общие скрипты для операций, таких как оценка, агрегирование, фильтрация и повторная индексация. Вместо внедрения скриптов в каждый запрос вы можете ссылаться на эти общие операции.
Сохранённые скрипты также могут уменьшить размер запроса. В зависимости от размера скрипта и частоты запросов это может помочь снизить задержку и затраты на передачу данных.
В отличие от обычных скриптов, для сохранённых скриптов требуется указать язык скрипта с помощью параметра lang.
Для создания скрипта используйте API создания или обновления сохранённых скриптов. Например, следующий запрос создаёт сохранённый скрипт с именем calculate-score.
resp = client.put_script(
id="calculate-score",
script={
"lang": "painless",
"source": "Math.log(_score * 2) + params['my_modifier']"
},
)
print(resp) response = client.put_script(
id: 'calculate-score',
body: {
script: {
lang: 'painless',
source: "Math.log(_score * 2) + params['my_modifier']"
}
}
)
puts response const response = await client.putScript({
id: "calculate-score",
script: {
lang: "painless",
source: "Math.log(_score * 2) + params['my_modifier']",
},
});
console.log(response); POST _scripts/calculate-score
{
"script": {
"lang": "painless",
"source": "Math.log(_score * 2) + params['my_modifier']"
}
} Вы можете получить этот скрипт с помощью API получения сохранённого скрипта.
resp = client.get_script(
id="calculate-score",
)
print(resp) response = client.get_script( id: 'calculate-score' ) puts response
const response = await client.getScript({
id: "calculate-score",
});
console.log(response); GET _scripts/calculate-score
Чтобы использовать сохранённый скрипт в запросе, включите скрипт id в объявление script:
resp = client.search(
index="my-index-000001",
query={
"script_score": {
"query": {
"match": {
"message": "some message"
}
},
"script": {
"id": "calculate-score",
"params": {
"my_modifier": 2
}
}
}
},
)
print(resp) response = client.search(
index: 'my-index-000001',
body: {
query: {
script_score: {
query: {
match: {
message: 'some message'
}
},
script: {
id: 'calculate-score',
params: {
my_modifier: 2
}
}
}
}
}
)
puts response const response = await client.search({
index: "my-index-000001",
query: {
script_score: {
query: {
match: {
message: "some message",
},
},
script: {
id: "calculate-score",
params: {
my_modifier: 2,
},
},
},
},
});
console.log(response); GET my-index-000001/_search
{
"query": {
"script_score": {
"query": {
"match": {
"message": "some message"
}
},
"script": {
"id": "calculate-score",
"params": {
"my_modifier": 2
}
}
}
}
} |
|
Чтобы удалить сохранённый скрипт, отправьте запрос удаления сохранённого скрипта.
resp = client.delete_script(
id="calculate-score",
)
print(resp) response = client.delete_script( id: 'calculate-score' ) puts response
const response = await client.deleteScript({
id: "calculate-score",
});
console.log(response); DELETE _scripts/calculate-score
Обновление документов с помощью скриптов
Вы можете использовать API обновления для обновления документов с помощью указанного скрипта. Скрипт может обновлять, удалять или пропускать изменение документа. API обновления также поддерживает передачу частичного документа, который объединяется с существующим документом.
Сначала проиндексируем простой документ:
resp = client.index(
index="my-index-000001",
id="1",
document={
"counter": 1,
"tags": [
"red"
]
},
)
print(resp) response = client.index(
index: 'my-index-000001',
id: 1,
body: {
counter: 1,
tags: [
'red'
]
}
)
puts response const response = await client.index({
index: "my-index-000001",
id: 1,
document: {
counter: 1,
tags: ["red"],
},
});
console.log(response); PUT my-index-000001/_doc/1
{
"counter" : 1,
"tags" : ["red"]
} Для инкремента счётчика можно отправить запрос на обновление с помощью следующего скрипта:
resp = client.update(
index="my-index-000001",
id="1",
script={
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params": {
"count": 4
}
},
)
print(resp) response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: {
source: 'ctx._source.counter += params.count',
lang: 'painless',
params: {
count: 4
}
}
}
)
puts response const response = await client.update({
index: "my-index-000001",
id: 1,
script: {
source: "ctx._source.counter += params.count",
lang: "painless",
params: {
count: 4,
},
},
});
console.log(response); POST my-index-000001/_update/1
{
"script" : {
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params" : {
"count" : 4
}
}
} Аналогично, можно использовать скрипт обновления для добавления тега в список тегов. Поскольку это просто список, тег добавляется, даже если он уже существует:
resp = client.update(
index="my-index-000001",
id="1",
script={
"source": "ctx._source.tags.add(params['tag'])",
"lang": "painless",
"params": {
"tag": "blue"
}
},
)
print(resp) response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: {
source: "ctx._source.tags.add(params['tag'])",
lang: 'painless',
params: {
tag: 'blue'
}
}
}
)
puts response const response = await client.update({
index: "my-index-000001",
id: 1,
script: {
source: "ctx._source.tags.add(params['tag'])",
lang: "painless",
params: {
tag: "blue",
},
},
});
console.log(response); POST my-index-000001/_update/1
{
"script": {
"source": "ctx._source.tags.add(params['tag'])",
"lang": "painless",
"params": {
"tag": "blue"
}
}
} Также можно удалить тег из списка тегов. Метод remove класса Java List доступен в Painless. Он принимает индекс элемента, который нужно удалить. Чтобы избежать возможной ошибки во время выполнения, сначала необходимо убедиться, что тег существует. Если в списке есть дубликаты тега, этот скрипт удаляет только одно вхождение.
resp = client.update(
index="my-index-000001",
id="1",
script={
"source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
"lang": "painless",
"params": {
"tag": "blue"
}
},
)
print(resp) response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: {
source: "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
lang: 'painless',
params: {
tag: 'blue'
}
}
}
)
puts response const response = await client.update({
index: "my-index-000001",
id: 1,
script: {
source:
"if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
lang: "painless",
params: {
tag: "blue",
},
},
});
console.log(response); POST my-index-000001/_update/1
{
"script": {
"source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
"lang": "painless",
"params": {
"tag": "blue"
}
}
} Можно также добавлять и удалять поля из документа. Например, этот скрипт добавляет поле new_field:
resp = client.update(
index="my-index-000001",
id="1",
script="ctx._source.new_field = 'value_of_new_field'",
)
print(resp) response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: "ctx._source.new_field = 'value_of_new_field'"
}
)
puts response const response = await client.update({
index: "my-index-000001",
id: 1,
script: "ctx._source.new_field = 'value_of_new_field'",
});
console.log(response); POST my-index-000001/_update/1
{
"script" : "ctx._source.new_field = 'value_of_new_field'"
} В свою очередь, этот скрипт удаляет поле new_field:
resp = client.update(
index="my-index-000001",
id="1",
script="ctx._source.remove('new_field')",
)
print(resp) response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: "ctx._source.remove('new_field')"
}
)
puts response const response = await client.update({
index: "my-index-000001",
id: 1,
script: "ctx._source.remove('new_field')",
});
console.log(response); POST my-index-000001/_update/1
{
"script" : "ctx._source.remove('new_field')"
} Вместо обновления документа можно изменить операцию, выполняемую в скрипте. Например, этот запрос удаляет документ, если поле tags содержит значение green. В противном случае он ничего не делает (noop):
resp = client.update(
index="my-index-000001",
id="1",
script={
"source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
"lang": "painless",
"params": {
"tag": "green"
}
},
)
print(resp) response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: {
source: "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
lang: 'painless',
params: {
tag: 'green'
}
}
}
)
puts response const response = await client.update({
index: "my-index-000001",
id: 1,
script: {
source:
"if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
lang: "painless",
params: {
tag: "green",
},
},
});
console.log(response); POST my-index-000001/_update/1
{
"script": {
"source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
"lang": "painless",
"params": {
"tag": "green"
}
}
}
© 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/modules-scripting-using.html