スクリプトの書き方

Elasticsearch API でスクリプトがサポートされている場所では、構文は同じパターンに従います。スクリプトの言語を指定し、スクリプトのロジック(またはソース)を提供し、スクリプトに渡されるパラメータを追加します。

Js

  1. "script": {
  2. "lang": "...",
  3. "source" | "id": "...",
  4. "params": { ... }
  5. }
  • lang
  • スクリプトが書かれている言語を指定します。デフォルトは painless です。
  • source, id
  • スクリプト自体で、インラインスクリプトの場合は source、保存されたスクリプトの場合は id として指定します。保存されたスクリプト API を使用して、保存されたスクリプトを作成および管理します。
  • params
  • スクリプトに変数として渡される任意の名前付きパラメータを指定します。パラメータを使用して、ハードコーディングされた値を減らし、コンパイル時間を短縮します。

最初のスクリプトを書く

Painless は Elasticsearch のデフォルトのスクリプト言語です。安全で、高性能で、少しのコーディング経験がある人にとって自然な構文を提供します。

Painless スクリプトは、1 つ以上のステートメントで構成され、オプションで最初に 1 つ以上のユーザー定義関数を持つことができます。スクリプトには常に少なくとも 1 つのステートメントが必要です。

Painless execute API は、シンプルなユーザー定義パラメータでスクリプトをテストし、結果を受け取る機能を提供します。完全なスクリプトから始めて、その構成要素を確認しましょう。

まず、作業するデータを持つために、単一のフィールドを持つドキュメントをインデックスします:

Python

  1. resp = client.index(
  2. index="my-index-000001",
  3. id="1",
  4. document={
  5. "my_field": 5
  6. },
  7. )
  8. print(resp)

Ruby

  1. response = client.index(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. my_field: 5
  6. }
  7. )
  8. puts response

Js

  1. const response = await client.index({
  2. index: "my-index-000001",
  3. id: 1,
  4. document: {
  5. my_field: 5,
  6. },
  7. });
  8. console.log(response);

Console

  1. PUT my-index-000001/_doc/1
  2. {
  3. "my_field": 5
  4. }

このフィールドで動作するスクリプトを構築し、そのスクリプトをクエリの一部として評価します。次のクエリは、検索 API の script_fields パラメータを使用してスクリプトの評価を取得します。ここでは多くのことが起こっていますが、個々のコンポーネントを理解するために分解します。今のところ、このスクリプトは my_field を取り込み、それに対して操作を行うことを理解していれば十分です。

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. script_fields={
  4. "my_doubled_field": {
  5. "script": {
  6. "source": "doc['my_field'].value * params['multiplier']",
  7. "params": {
  8. "multiplier": 2
  9. }
  10. }
  11. }
  12. },
  13. )
  14. print(resp)

Ruby

  1. response = client.search(
  2. index: 'my-index-000001',
  3. body: {
  4. script_fields: {
  5. my_doubled_field: {
  6. script: {
  7. source: "doc['my_field'].value * params['multiplier']",
  8. params: {
  9. multiplier: 2
  10. }
  11. }
  12. }
  13. }
  14. }
  15. )
  16. puts response

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. script_fields: {
  4. my_doubled_field: {
  5. script: {
  6. source: "doc['my_field'].value * params['multiplier']",
  7. params: {
  8. multiplier: 2,
  9. },
  10. },
  11. },
  12. },
  13. });
  14. console.log(response);

Console

  1. GET my-index-000001/_search
  2. {
  3. "script_fields": {
  4. "my_doubled_field": {
  5. "script": {
  6. "source": "doc['my_field'].value * params['multiplier']",
  7. "params": {
  8. "multiplier": 2
  9. }
  10. }
  11. }
  12. }
  13. }
script オブジェクト
script ソース

script は、Elasticsearch のほとんどの API でスクリプトを定義する標準の JSON オブジェクトです。このオブジェクトは、スクリプト自体を定義するために source を必要とします。スクリプトは言語を指定しないため、デフォルトで Painless になります。

スクリプトでパラメータを使用する

Elasticsearch が新しいスクリプトを初めて見ると、スクリプトをコンパイルし、コンパイルされたバージョンをキャッシュに保存します。コンパイルは重いプロセスになる可能性があります。スクリプト内にハードコーディングされた値を使用するのではなく、名前付き params として渡してください。

たとえば、前のスクリプトでは、ハードコーディングされた値を使用して、見た目上は複雑さの少ないスクリプトを書くことができました。my_field の最初の値を取得し、それを 2 で掛け算することができます:

Painless

  1. "source": "return doc['my_field'].value * 2"

動作はしますが、この解決策は非常に柔軟性がありません。乗数を変更するためにスクリプトソースを修正する必要があり、乗数が変更されるたびに Elasticsearch はスクリプトを再コンパイルする必要があります。

値をハードコーディングするのではなく、名前付き params を使用してスクリプトを柔軟にし、スクリプトが実行されるときのコンパイル時間を短縮します。これにより、Elasticsearch がスクリプトを再コンパイルすることなく、multiplier パラメータを変更できるようになります。

Painless

  1. "source": "doc['my_field'].value * params['multiplier']",
  2. "params": {
  3. "multiplier": 2
  4. }

デフォルトでは、5 分ごとに最大 150 のスクリプトをコンパイルできます。インジェストコンテキストの場合、デフォルトのスクリプトコンパイルレートは無制限です。

Js

  1. script.context.field.max_compilations_rate=100/10m

短時間にあまりにも多くのユニークなスクリプトをコンパイルすると、Elasticsearch は新しい動的スクリプトを circuit_breaking_exception エラーで拒否します。

スクリプトを短くする

Painless に固有の構文能力を使用して、スクリプトの冗長性を減らし、短くすることができます。以下は、短くできるシンプルなスクリプトです:

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. script_fields={
  4. "my_doubled_field": {
  5. "script": {
  6. "lang": "painless",
  7. "source": "doc['my_field'].value * params.get('multiplier');",
  8. "params": {
  9. "multiplier": 2
  10. }
  11. }
  12. }
  13. },
  14. )
  15. print(resp)

Ruby

  1. response = client.search(
  2. index: 'my-index-000001',
  3. body: {
  4. script_fields: {
  5. my_doubled_field: {
  6. script: {
  7. lang: 'painless',
  8. source: "doc['my_field'].value * params.get('multiplier');",
  9. params: {
  10. multiplier: 2
  11. }
  12. }
  13. }
  14. }
  15. }
  16. )
  17. puts response

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. script_fields: {
  4. my_doubled_field: {
  5. script: {
  6. lang: "painless",
  7. source: "doc['my_field'].value * params.get('multiplier');",
  8. params: {
  9. multiplier: 2,
  10. },
  11. },
  12. },
  13. },
  14. });
  15. console.log(response);

Console

  1. GET my-index-000001/_search
  2. {
  3. "script_fields": {
  4. "my_doubled_field": {
  5. "script": {
  6. "lang": "painless",
  7. "source": "doc['my_field'].value * params.get('multiplier');",
  8. "params": {
  9. "multiplier": 2
  10. }
  11. }
  12. }
  13. }
  14. }

スクリプトの短縮版を見て、前のバージョンに対してどのような改善が含まれているかを確認しましょう:

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. script_fields={
  4. "my_doubled_field": {
  5. "script": {
  6. "source": "field('my_field').get(null) * params['multiplier']",
  7. "params": {
  8. "multiplier": 2
  9. }
  10. }
  11. }
  12. },
  13. )
  14. print(resp)

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. script_fields: {
  4. my_doubled_field: {
  5. script: {
  6. source: "field('my_field').get(null) * params['multiplier']",
  7. params: {
  8. multiplier: 2,
  9. },
  10. },
  11. },
  12. },
  13. });
  14. console.log(response);

Console

  1. GET my-index-000001/_search
  2. {
  3. "script_fields": {
  4. "my_doubled_field": {
  5. "script": {
  6. "source": "field('my_field').get(null) * params['multiplier']",
  7. "params": {
  8. "multiplier": 2
  9. }
  10. }
  11. }
  12. }
  13. }

このバージョンのスクリプトは、いくつかのコンポーネントを削除し、構文を大幅に簡素化しています:

  • lang 宣言。Painless がデフォルトの言語であるため、Painless スクリプトを書く場合は言語を指定する必要はありません。
  • return キーワード。Painless は、スクリプトの最終ステートメントを自動的に使用して、必要なスクリプトコンテキストで戻り値を生成します。
  • get メソッドは、ブラケット [] に置き換えられます。Painless は、Map タイプに特化したショートカットを使用して、長い get メソッドの代わりにブラケットを使用できるようにします。
  • source ステートメントの最後のセミコロン。Painless は、ブロックの最終ステートメントにセミコロンを必要としません。ただし、他のケースでは曖昧さを取り除くために必要です。

Elasticsearch がスクリプトをサポートしている場所では、この省略された構文を使用してください。たとえば、ランタイムフィールド を作成する際などです。

スクリプトを保存および取得する

クラスタ状態からスクリプトを保存および取得するには、保存されたスクリプト API を使用できます。保存されたスクリプトは、コンパイル時間を短縮し、検索を高速化します。

通常のスクリプトとは異なり、保存されたスクリプトは lang パラメータを使用してスクリプト言語を指定する必要があります。

スクリプトを作成するには、create stored script API を使用します。たとえば、次のリクエストは calculate-score という名前の保存されたスクリプトを作成します。

Python

  1. resp = client.put_script(
  2. id="calculate-score",
  3. script={
  4. "lang": "painless",
  5. "source": "Math.log(_score * 2) + params['my_modifier']"
  6. },
  7. )
  8. print(resp)

Ruby

  1. response = client.put_script(
  2. id: 'calculate-score',
  3. body: {
  4. script: {
  5. lang: 'painless',
  6. source: "Math.log(_score * 2) + params['my_modifier']"
  7. }
  8. }
  9. )
  10. puts response

Js

  1. const response = await client.putScript({
  2. id: "calculate-score",
  3. script: {
  4. lang: "painless",
  5. source: "Math.log(_score * 2) + params['my_modifier']",
  6. },
  7. });
  8. console.log(response);

Console

  1. POST _scripts/calculate-score
  2. {
  3. "script": {
  4. "lang": "painless",
  5. "source": "Math.log(_score * 2) + params['my_modifier']"
  6. }
  7. }

保存されたスクリプト API を使用して、そのスクリプトを取得できます。

Python

  1. resp = client.get_script(
  2. id="calculate-score",
  3. )
  4. print(resp)

Ruby

  1. response = client.get_script(
  2. id: 'calculate-score'
  3. )
  4. puts response

Js

  1. const response = await client.getScript({
  2. id: "calculate-score",
  3. });
  4. console.log(response);

Console

  1. GET _scripts/calculate-score

クエリで保存されたスクリプトを使用するには、idscript 宣言に含めます:

Python

  1. resp = client.search(
  2. index="my-index-000001",
  3. query={
  4. "script_score": {
  5. "query": {
  6. "match": {
  7. "message": "some message"
  8. }
  9. },
  10. "script": {
  11. "id": "calculate-score",
  12. "params": {
  13. "my_modifier": 2
  14. }
  15. }
  16. }
  17. },
  18. )
  19. print(resp)

Ruby

  1. response = client.search(
  2. index: 'my-index-000001',
  3. body: {
  4. query: {
  5. script_score: {
  6. query: {
  7. match: {
  8. message: 'some message'
  9. }
  10. },
  11. script: {
  12. id: 'calculate-score',
  13. params: {
  14. my_modifier: 2
  15. }
  16. }
  17. }
  18. }
  19. }
  20. )
  21. puts response

Js

  1. const response = await client.search({
  2. index: "my-index-000001",
  3. query: {
  4. script_score: {
  5. query: {
  6. match: {
  7. message: "some message",
  8. },
  9. },
  10. script: {
  11. id: "calculate-score",
  12. params: {
  13. my_modifier: 2,
  14. },
  15. },
  16. },
  17. },
  18. });
  19. console.log(response);

Console

  1. GET my-index-000001/_search
  2. {
  3. "query": {
  4. "script_score": {
  5. "query": {
  6. "match": {
  7. "message": "some message"
  8. }
  9. },
  10. "script": {
  11. "id": "calculate-score",
  12. "params": {
  13. "my_modifier": 2
  14. }
  15. }
  16. }
  17. }
  18. }
id の保存されたスクリプト

保存されたスクリプトを削除するには、delete stored script API リクエストを送信します。

Python

  1. resp = client.delete_script(
  2. id="calculate-score",
  3. )
  4. print(resp)

Ruby

  1. response = client.delete_script(
  2. id: 'calculate-score'
  3. )
  4. puts response

Js

  1. const response = await client.deleteScript({
  2. id: "calculate-score",
  3. });
  4. console.log(response);

Console

  1. DELETE _scripts/calculate-score

スクリプトでドキュメントを更新する

update API を使用して、指定されたスクリプトでドキュメントを更新できます。スクリプトは、ドキュメントを更新、削除、または変更をスキップできます。update API は、既存のドキュメントにマージされる部分的なドキュメントを渡すこともサポートしています。

まず、シンプルなドキュメントをインデックスしましょう:

Python

  1. resp = client.index(
  2. index="my-index-000001",
  3. id="1",
  4. document={
  5. "counter": 1,
  6. "tags": [
  7. "red"
  8. ]
  9. },
  10. )
  11. print(resp)

Ruby

  1. response = client.index(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. counter: 1,
  6. tags: [
  7. 'red'
  8. ]
  9. }
  10. )
  11. puts response

Js

  1. const response = await client.index({
  2. index: "my-index-000001",
  3. id: 1,
  4. document: {
  5. counter: 1,
  6. tags: ["red"],
  7. },
  8. });
  9. console.log(response);

Console

  1. PUT my-index-000001/_doc/1
  2. {
  3. "counter" : 1,
  4. "tags" : ["red"]
  5. }

カウンターをインクリメントするには、次のスクリプトを使用して更新リクエストを送信できます:

Python

  1. resp = client.update(
  2. index="my-index-000001",
  3. id="1",
  4. script={
  5. "source": "ctx._source.counter += params.count",
  6. "lang": "painless",
  7. "params": {
  8. "count": 4
  9. }
  10. },
  11. )
  12. print(resp)

Ruby

  1. response = client.update(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. script: {
  6. source: 'ctx._source.counter += params.count',
  7. lang: 'painless',
  8. params: {
  9. count: 4
  10. }
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.update({
  2. index: "my-index-000001",
  3. id: 1,
  4. script: {
  5. source: "ctx._source.counter += params.count",
  6. lang: "painless",
  7. params: {
  8. count: 4,
  9. },
  10. },
  11. });
  12. console.log(response);

Console

  1. POST my-index-000001/_update/1
  2. {
  3. "script" : {
  4. "source": "ctx._source.counter += params.count",
  5. "lang": "painless",
  6. "params" : {
  7. "count" : 4
  8. }
  9. }
  10. }

同様に、更新スクリプトを使用してタグをタグのリストに追加できます。これは単なるリストなので、タグが存在していても追加されます:

Python

  1. resp = client.update(
  2. index="my-index-000001",
  3. id="1",
  4. script={
  5. "source": "ctx._source.tags.add(params['tag'])",
  6. "lang": "painless",
  7. "params": {
  8. "tag": "blue"
  9. }
  10. },
  11. )
  12. print(resp)

Ruby

  1. response = client.update(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. script: {
  6. source: "ctx._source.tags.add(params['tag'])",
  7. lang: 'painless',
  8. params: {
  9. tag: 'blue'
  10. }
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.update({
  2. index: "my-index-000001",
  3. id: 1,
  4. script: {
  5. source: "ctx._source.tags.add(params['tag'])",
  6. lang: "painless",
  7. params: {
  8. tag: "blue",
  9. },
  10. },
  11. });
  12. console.log(response);

Console

  1. POST my-index-000001/_update/1
  2. {
  3. "script": {
  4. "source": "ctx._source.tags.add(params['tag'])",
  5. "lang": "painless",
  6. "params": {
  7. "tag": "blue"
  8. }
  9. }
  10. }

タグのリストからタグを削除することもできます。Java Listremove メソッドは Painless で利用可能です。削除したい要素のインデックスを取ります。ランタイムエラーを避けるために、まずタグが存在することを確認する必要があります。リストにタグの重複が含まれている場合、このスクリプトは 1 回の出現を削除するだけです。

Python

  1. resp = client.update(
  2. index="my-index-000001",
  3. id="1",
  4. script={
  5. "source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
  6. "lang": "painless",
  7. "params": {
  8. "tag": "blue"
  9. }
  10. },
  11. )
  12. print(resp)

Ruby

  1. response = client.update(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. script: {
  6. source: "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
  7. lang: 'painless',
  8. params: {
  9. tag: 'blue'
  10. }
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.update({
  2. index: "my-index-000001",
  3. id: 1,
  4. script: {
  5. source:
  6. "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
  7. lang: "painless",
  8. params: {
  9. tag: "blue",
  10. },
  11. },
  12. });
  13. console.log(response);

Console

  1. POST my-index-000001/_update/1
  2. {
  3. "script": {
  4. "source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }",
  5. "lang": "painless",
  6. "params": {
  7. "tag": "blue"
  8. }
  9. }
  10. }

ドキュメントからフィールドを追加および削除することもできます。たとえば、このスクリプトはフィールド new_field を追加します:

Python

  1. resp = client.update(
  2. index="my-index-000001",
  3. id="1",
  4. script="ctx._source.new_field = 'value_of_new_field'",
  5. )
  6. print(resp)

Ruby

  1. response = client.update(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. script: "ctx._source.new_field = 'value_of_new_field'"
  6. }
  7. )
  8. puts response

Js

  1. const response = await client.update({
  2. index: "my-index-000001",
  3. id: 1,
  4. script: "ctx._source.new_field = 'value_of_new_field'",
  5. });
  6. console.log(response);

Console

  1. POST my-index-000001/_update/1
  2. {
  3. "script" : "ctx._source.new_field = 'value_of_new_field'"
  4. }

逆に、このスクリプトはフィールド new_field を削除します:

Python

  1. resp = client.update(
  2. index="my-index-000001",
  3. id="1",
  4. script="ctx._source.remove('new_field')",
  5. )
  6. print(resp)

Ruby

  1. response = client.update(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. script: "ctx._source.remove('new_field')"
  6. }
  7. )
  8. puts response

Js

  1. const response = await client.update({
  2. index: "my-index-000001",
  3. id: 1,
  4. script: "ctx._source.remove('new_field')",
  5. });
  6. console.log(response);

Console

  1. POST my-index-000001/_update/1
  2. {
  3. "script" : "ctx._source.remove('new_field')"
  4. }

ドキュメントを更新する代わりに、スクリプト内で実行される操作を変更することもできます。たとえば、このリクエストは tags フィールドが green を含む場合にドキュメントを削除します。そうでない場合は何もしません(noop):

Python

  1. resp = client.update(
  2. index="my-index-000001",
  3. id="1",
  4. script={
  5. "source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
  6. "lang": "painless",
  7. "params": {
  8. "tag": "green"
  9. }
  10. },
  11. )
  12. print(resp)

Ruby

  1. response = client.update(
  2. index: 'my-index-000001',
  3. id: 1,
  4. body: {
  5. script: {
  6. source: "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
  7. lang: 'painless',
  8. params: {
  9. tag: 'green'
  10. }
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.update({
  2. index: "my-index-000001",
  3. id: 1,
  4. script: {
  5. source:
  6. "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
  7. lang: "painless",
  8. params: {
  9. tag: "green",
  10. },
  11. },
  12. });
  13. console.log(response);

Console

  1. POST my-index-000001/_update/1
  2. {
  3. "script": {
  4. "source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }",
  5. "lang": "painless",
  6. "params": {
  7. "tag": "green"
  8. }
  9. }
  10. }