スクリプトの書き方
Elasticsearch API でスクリプトがサポートされている場所では、構文は同じパターンに従います。スクリプトの言語を指定し、スクリプトのロジック(またはソース)を提供し、スクリプトに渡されるパラメータを追加します。
Js
"script": {
"lang": "...",
"source" | "id": "...",
"params": { ... }
}
lang
- スクリプトが書かれている言語を指定します。デフォルトは
painless
です。 source
,id
- スクリプト自体で、インラインスクリプトの場合は
source
、保存されたスクリプトの場合はid
として指定します。保存されたスクリプト API を使用して、保存されたスクリプトを作成および管理します。 params
- スクリプトに変数として渡される任意の名前付きパラメータを指定します。パラメータを使用して、ハードコーディングされた値を減らし、コンパイル時間を短縮します。
最初のスクリプトを書く
Painless は Elasticsearch のデフォルトのスクリプト言語です。安全で、高性能で、少しのコーディング経験がある人にとって自然な構文を提供します。
Painless スクリプトは、1 つ以上のステートメントで構成され、オプションで最初に 1 つ以上のユーザー定義関数を持つことができます。スクリプトには常に少なくとも 1 つのステートメントが必要です。
Painless execute API は、シンプルなユーザー定義パラメータでスクリプトをテストし、結果を受け取る機能を提供します。完全なスクリプトから始めて、その構成要素を確認しましょう。
まず、作業するデータを持つために、単一のフィールドを持つドキュメントをインデックスします:
Python
resp = client.index(
index="my-index-000001",
id="1",
document={
"my_field": 5
},
)
print(resp)
Ruby
response = client.index(
index: 'my-index-000001',
id: 1,
body: {
my_field: 5
}
)
puts response
Js
const response = await client.index({
index: "my-index-000001",
id: 1,
document: {
my_field: 5,
},
});
console.log(response);
Console
PUT my-index-000001/_doc/1
{
"my_field": 5
}
このフィールドで動作するスクリプトを構築し、そのスクリプトをクエリの一部として評価します。次のクエリは、検索 API の script_fields
パラメータを使用してスクリプトの評価を取得します。ここでは多くのことが起こっていますが、個々のコンポーネントを理解するために分解します。今のところ、このスクリプトは my_field
を取り込み、それに対して操作を行うことを理解していれば十分です。
Python
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)
Ruby
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
Js
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);
Console
GET my-index-000001/_search
{
"script_fields": {
"my_doubled_field": {
"script": {
"source": "doc['my_field'].value * params['multiplier']",
"params": {
"multiplier": 2
}
}
}
}
}
script オブジェクト |
|
script ソース |
script
は、Elasticsearch のほとんどの API でスクリプトを定義する標準の JSON オブジェクトです。このオブジェクトは、スクリプト自体を定義するために source
を必要とします。スクリプトは言語を指定しないため、デフォルトで Painless になります。
スクリプトでパラメータを使用する
Elasticsearch が新しいスクリプトを初めて見ると、スクリプトをコンパイルし、コンパイルされたバージョンをキャッシュに保存します。コンパイルは重いプロセスになる可能性があります。スクリプト内にハードコーディングされた値を使用するのではなく、名前付き params
として渡してください。
たとえば、前のスクリプトでは、ハードコーディングされた値を使用して、見た目上は複雑さの少ないスクリプトを書くことができました。my_field
の最初の値を取得し、それを 2
で掛け算することができます:
Painless
"source": "return doc['my_field'].value * 2"
動作はしますが、この解決策は非常に柔軟性がありません。乗数を変更するためにスクリプトソースを修正する必要があり、乗数が変更されるたびに Elasticsearch はスクリプトを再コンパイルする必要があります。
値をハードコーディングするのではなく、名前付き params
を使用してスクリプトを柔軟にし、スクリプトが実行されるときのコンパイル時間を短縮します。これにより、Elasticsearch がスクリプトを再コンパイルすることなく、multiplier
パラメータを変更できるようになります。
Painless
"source": "doc['my_field'].value * params['multiplier']",
"params": {
"multiplier": 2
}
デフォルトでは、5 分ごとに最大 150 のスクリプトをコンパイルできます。インジェストコンテキストの場合、デフォルトのスクリプトコンパイルレートは無制限です。
Js
script.context.field.max_compilations_rate=100/10m
短時間にあまりにも多くのユニークなスクリプトをコンパイルすると、Elasticsearch は新しい動的スクリプトを circuit_breaking_exception
エラーで拒否します。
スクリプトを短くする
Painless に固有の構文能力を使用して、スクリプトの冗長性を減らし、短くすることができます。以下は、短くできるシンプルなスクリプトです:
Python
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)
Ruby
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
Js
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);
Console
GET my-index-000001/_search
{
"script_fields": {
"my_doubled_field": {
"script": {
"lang": "painless",
"source": "doc['my_field'].value * params.get('multiplier');",
"params": {
"multiplier": 2
}
}
}
}
}
スクリプトの短縮版を見て、前のバージョンに対してどのような改善が含まれているかを確認しましょう:
Python
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)
Js
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);
Console
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 がスクリプトをサポートしている場所では、この省略された構文を使用してください。たとえば、ランタイムフィールド を作成する際などです。
スクリプトを保存および取得する
クラスタ状態からスクリプトを保存および取得するには、保存されたスクリプト API を使用できます。保存されたスクリプトは、コンパイル時間を短縮し、検索を高速化します。
通常のスクリプトとは異なり、保存されたスクリプトは lang
パラメータを使用してスクリプト言語を指定する必要があります。
スクリプトを作成するには、create stored script API を使用します。たとえば、次のリクエストは calculate-score
という名前の保存されたスクリプトを作成します。
Python
resp = client.put_script(
id="calculate-score",
script={
"lang": "painless",
"source": "Math.log(_score * 2) + params['my_modifier']"
},
)
print(resp)
Ruby
response = client.put_script(
id: 'calculate-score',
body: {
script: {
lang: 'painless',
source: "Math.log(_score * 2) + params['my_modifier']"
}
}
)
puts response
Js
const response = await client.putScript({
id: "calculate-score",
script: {
lang: "painless",
source: "Math.log(_score * 2) + params['my_modifier']",
},
});
console.log(response);
Console
POST _scripts/calculate-score
{
"script": {
"lang": "painless",
"source": "Math.log(_score * 2) + params['my_modifier']"
}
}
保存されたスクリプト API を使用して、そのスクリプトを取得できます。
Python
resp = client.get_script(
id="calculate-score",
)
print(resp)
Ruby
response = client.get_script(
id: 'calculate-score'
)
puts response
Js
const response = await client.getScript({
id: "calculate-score",
});
console.log(response);
Console
GET _scripts/calculate-score
クエリで保存されたスクリプトを使用するには、id
を script
宣言に含めます:
Python
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)
Ruby
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
Js
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);
Console
GET my-index-000001/_search
{
"query": {
"script_score": {
"query": {
"match": {
"message": "some message"
}
},
"script": {
"id": "calculate-score",
"params": {
"my_modifier": 2
}
}
}
}
}
id の保存されたスクリプト |
保存されたスクリプトを削除するには、delete stored script API リクエストを送信します。
Python
resp = client.delete_script(
id="calculate-score",
)
print(resp)
Ruby
response = client.delete_script(
id: 'calculate-score'
)
puts response
Js
const response = await client.deleteScript({
id: "calculate-score",
});
console.log(response);
Console
DELETE _scripts/calculate-score
スクリプトでドキュメントを更新する
update API を使用して、指定されたスクリプトでドキュメントを更新できます。スクリプトは、ドキュメントを更新、削除、または変更をスキップできます。update API は、既存のドキュメントにマージされる部分的なドキュメントを渡すこともサポートしています。
まず、シンプルなドキュメントをインデックスしましょう:
Python
resp = client.index(
index="my-index-000001",
id="1",
document={
"counter": 1,
"tags": [
"red"
]
},
)
print(resp)
Ruby
response = client.index(
index: 'my-index-000001',
id: 1,
body: {
counter: 1,
tags: [
'red'
]
}
)
puts response
Js
const response = await client.index({
index: "my-index-000001",
id: 1,
document: {
counter: 1,
tags: ["red"],
},
});
console.log(response);
Console
PUT my-index-000001/_doc/1
{
"counter" : 1,
"tags" : ["red"]
}
カウンターをインクリメントするには、次のスクリプトを使用して更新リクエストを送信できます:
Python
resp = client.update(
index="my-index-000001",
id="1",
script={
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params": {
"count": 4
}
},
)
print(resp)
Ruby
response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: {
source: 'ctx._source.counter += params.count',
lang: 'painless',
params: {
count: 4
}
}
}
)
puts response
Js
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);
Console
POST my-index-000001/_update/1
{
"script" : {
"source": "ctx._source.counter += params.count",
"lang": "painless",
"params" : {
"count" : 4
}
}
}
同様に、更新スクリプトを使用してタグをタグのリストに追加できます。これは単なるリストなので、タグが存在していても追加されます:
Python
resp = client.update(
index="my-index-000001",
id="1",
script={
"source": "ctx._source.tags.add(params['tag'])",
"lang": "painless",
"params": {
"tag": "blue"
}
},
)
print(resp)
Ruby
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
Js
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);
Console
POST my-index-000001/_update/1
{
"script": {
"source": "ctx._source.tags.add(params['tag'])",
"lang": "painless",
"params": {
"tag": "blue"
}
}
}
タグのリストからタグを削除することもできます。Java List
の remove
メソッドは Painless で利用可能です。削除したい要素のインデックスを取ります。ランタイムエラーを避けるために、まずタグが存在することを確認する必要があります。リストにタグの重複が含まれている場合、このスクリプトは 1 回の出現を削除するだけです。
Python
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)
Ruby
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
Js
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);
Console
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
を追加します:
Python
resp = client.update(
index="my-index-000001",
id="1",
script="ctx._source.new_field = 'value_of_new_field'",
)
print(resp)
Ruby
response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: "ctx._source.new_field = 'value_of_new_field'"
}
)
puts response
Js
const response = await client.update({
index: "my-index-000001",
id: 1,
script: "ctx._source.new_field = 'value_of_new_field'",
});
console.log(response);
Console
POST my-index-000001/_update/1
{
"script" : "ctx._source.new_field = 'value_of_new_field'"
}
逆に、このスクリプトはフィールド new_field
を削除します:
Python
resp = client.update(
index="my-index-000001",
id="1",
script="ctx._source.remove('new_field')",
)
print(resp)
Ruby
response = client.update(
index: 'my-index-000001',
id: 1,
body: {
script: "ctx._source.remove('new_field')"
}
)
puts response
Js
const response = await client.update({
index: "my-index-000001",
id: 1,
script: "ctx._source.remove('new_field')",
});
console.log(response);
Console
POST my-index-000001/_update/1
{
"script" : "ctx._source.remove('new_field')"
}
ドキュメントを更新する代わりに、スクリプト内で実行される操作を変更することもできます。たとえば、このリクエストは tags
フィールドが green
を含む場合にドキュメントを削除します。そうでない場合は何もしません(noop
):
Python
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)
Ruby
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
Js
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);
Console
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"
}
}
}