Elasticsearch:Painlessプログラミングデバッグ


Painlessは痛みがないという意味です.これはElasticsearch専用に設計されています.当初のデザイナーは「Painless」と名付けられ、プログラミング時に痛みがなく、デザイナーが使いやすいという意味だった.これはスクリプトの言語であるため、実際の使用では、これらのプログラミングの方法と使用を見つけるのは難しい.今日のチュートリアルでは、デバッグの方法について説明します.
Debug.Explain
PainlessにはREPLはありません.ある日は嬉しいですが、Elasticsearchに埋め込まれたPainlessスクリプトをデバッグするすべてのプロセスは教えてくれません.スクリプトはデータにアクセスしたり、「コンテキスト」にアクセスしたりすることが重要です.現在、組み込みスクリプトをデバッグする最善の方法は、選択した場所で例外を放出することです.自分の例外を投げ出すことができますが(新しいException('whatever')Painlessの砂箱は、オブジェクトのタイプなどの有用な情報にアクセスすることを阻止します.したがって、Painlessには、例外を引き起こすユーティリティ・メソッドDebug.explainがあります.たとえば、_explainプローブ・スクリプトを使用して使用可能なコンテキストを問い合わせることができます.
例1
私たちはKibanaで次のコマンドを入力しました.
PUT /hockey/_doc/1?refresh
{
  "first": "johnny",
  "last": "gaudreau",
  "goals": [
    9,
    27,
    1
  ],
  "assists": [
    17,
    46,
    0
  ],
  "gp": [
    26,
    82,
    1
  ],
  "time": "2020-08-30"
}

上記のコマンドは、次のmappingを生成します.
GET  hockey/_mapping
{
  "hockey" : {
    "mappings" : {
      "properties" : {
        "assists" : {
          "type" : "long"
        },
        "first" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "goals" : {
          "type" : "long"
        },
        "gp" : {
          "type" : "long"
        },
        "last" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "time" : {
          "type" : "date"
        }
      }
    }
  }
}

上図に示すように、long、text、keyword、dateのいくつかのタイプのデータが表示されます.では、実際のscriptプログラミングでは、これらのデータをどのように操作すればいいかという問題があります.それらのデータ型には、私たちが使用できる方法がありますか?
次のように使用します.explain終点:
POST /hockey/_explain/1
{
  "query": {
    "script": {
      "script": "Debug.explain(doc.goals)"
    }
  }
}

上記のコマンドの結果は次のとおりです.
{
  "error" : {
    "root_cause" : [
      {
        "type" : "script_exception",
        "reason" : "runtime error",
        "painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
        "to_string" : "[1, 9, 27]",
        "java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
        "script_stack" : [
          "Debug.explain(doc.goals)",
          "                 ^---- HERE"
        ],
        "script" : "Debug.explain(doc.goals)",
        "lang" : "painless",
        "position" : {
          "offset" : 17,
          "start" : 0,
          "end" : 24
        }
      }
    ],
    "type" : "script_exception",
    "reason" : "runtime error",
    "painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
    "to_string" : "[1, 9, 27]",
    "java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",
    "script_stack" : [
      "Debug.explain(doc.goals)",
      "                 ^---- HERE"
    ],
    "script" : "Debug.explain(doc.goals)",
    "lang" : "painless",
    "position" : {
      "offset" : 17,
      "start" : 0,
      "end" : 24
    },
    "caused_by" : {
      "type" : "painless_explain_error",
      "reason" : null
    }
  },
  "status" : 400
}

上にexceptionが表示されます.同時にdocも表示されますgoalsはこのようなタイプのデータです.
"painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Longs",
"java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Longs",

次に、リンクを参照してみましょうhttps://www.elastic.co/guide/en/elasticsearch/painless/7.0/painless-api-reference.html.私たちはorgを探しています.elasticsearch.index.fielddata.ScriptDocValues.Longs:次のような説明が見られます.
  • Long get(int)
  • org.joda.time.ReadableDateTime getDate()
  • List getDates()
  • long getValue()
  • List getValues()
  • Inherits methods from  CollectionIterableListObject

  • ここでは、使用可能なAPIインタフェースのいくつかを示します.以上より,このデータはListタイプのデータであることがわかり,統計をとるには以下の方法を用いることができる.
    GET hockey/_search
    {
      "query": {
        "function_score": {
          "script_score": {
            "script": {
              "lang": "painless",
              "source": """
                 int total = 0; 
                 for (int i = 0; i < doc['goals'].getLength(); ++i) { 
                   total += doc['goals'].get(i); 
                 } 
                 return total;
              """
            }
          }
        }
      }
    }

    ここではgetLength()およびget(i)を用いた.これらの方法はいずれもListタイプデータの方法である.シンプルな方法も使用できます.
    GET hockey/_search
    {
      "query": {
        "function_score": {
          "script_score": {
            "script": {
              "lang": "painless",
              "source": """
                 int total = 0; 
                 for (int i = 0; i < doc['goals'].length; ++i) { 
                   total += doc['goals'][i]; 
                 } 
                 return total;
              """
            }
          }
        }
      }
    }

    次の結果が表示されます.
    {
      "took" : 4,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 37.0,
        "hits" : [
          {
            "_index" : "hockey",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 37.0,
            "_source" : {
              "first" : "johnny",
              "last" : "gaudreau",
              "goals" : [
                9,
                27,
                1
              ],
              "assists" : [
                17,
                46,
                0
              ],
              "gp" : [
                26,
                82,
                1
              ],
              "time" : "2020-08-30"
            }
          }
        ]
      }
    }

     
    例2
    次の例では、dateデータ型を例に挙げることができます.Kibanaで次のコマンドを実行します.
    POST /hockey/_explain/1
    {
      "query": {
        "script": {
          "script": "Debug.explain(doc['time'])"
        }
      }
    }

    上記のコマンドは、次のexceptionを放出します.
    {
      "error" : {
        "root_cause" : [
          {
            "type" : "script_exception",
            "reason" : "runtime error",
            "painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Dates",
            "to_string" : "[2020-08-30T00:00:00.000Z]",
            "java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Dates",
            "script_stack" : [
              "Debug.explain(doc['time'])",
              "                 ^---- HERE"
            ],
            "script" : "Debug.explain(doc['time'])",
            "lang" : "painless",
            "position" : {
              "offset" : 17,
              "start" : 0,
              "end" : 26
            }
          }
        ],
        "type" : "script_exception",
        "reason" : "runtime error",
        "painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Dates",
        "to_string" : "[2020-08-30T00:00:00.000Z]",
        "java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Dates",
        "script_stack" : [
          "Debug.explain(doc['time'])",
          "                 ^---- HERE"
        ],
        "script" : "Debug.explain(doc['time'])",
        "lang" : "painless",
        "position" : {
          "offset" : 17,
          "start" : 0,
          "end" : 26
        },
        "caused_by" : {
          "type" : "painless_explain_error",
          "reason" : null
        }
      },
      "status" : 400
    }

    上のexceptionから、この放出された異常が含まれていることがわかります.
    "painless_class" : "org.elasticsearch.index.fielddata.ScriptDocValues.Dates",
    "java_class" : "org.elasticsearch.index.fielddata.ScriptDocValues$Dates",

    これはorgであることを示していますelasticsearch.index.fielddata.ScriptDocValues.Datesタイプのデータです.我々はhttps://www.elastic.co/guide/en/elasticsearch/painless/7.0/painless-api-reference.html、orgを位置決めする.elasticsearch.index.fielddata.ScriptDocValues.Datesでは、次の方法の説明を見ることができます.org.elasticsearch.index.fielddata.ScriptDocValues.Dates
  • org.joda.time.ReadableDateTime get(int)
  • org.joda.time.ReadableDateTime getDate()
  • List getDates()
  • org.joda.time.ReadableDateTime getValue()
  • List getValues()
  • Inherits methods from  CollectionIterableListObject

  • 明らかにorgという名前ですjoda.time.ReadableDateTimeタイプのデータ.上のリンクをクリックすると、このタイプの方法についてもっと見ることができます.org.joda.time.ReadableDateTime
  • int getCenturyOfEra()
  • int getDayOfMonth()
  • int getDayOfWeek()
  • int getDayOfYear()
  • int getEra()
  • int getHourOfDay()
  • int getMillisOfDay()
  • int getMillisOfSecond()
  • int getMinuteOfDay()
  • int getMinuteOfHour()
  • int getMonthOfYear()
  • int getSecondOfDay()
  • int getSecondOfMinute()
  • int getWeekOfWeekyear()
  • int getWeekyear()
  • int getYear()
  • int getYearOfCentury()
  • int getYearOfEra()
  • String toString(String)
  • String toString(String, Locale)
  • Inherits methods from  Comparableorg.joda.time.ReadableInstant

  • 上のリストから、私たちが使用するための非常に豊富な方法を見ました.上記の理解に基づいて、次のスクリプトを使用して検索できます.
    POST /hockey/_search
    {
      "query": {
        "script": {
          "script": """
           doc['time'].value.getYear() > 2000
         
          """
        }
      }
    }

    上記ではgetYear()を使用して年を取得しました.検索結果は次のとおりです.
    {
      "took" : 0,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : "hockey",
            "_type" : "_doc",
            "_id" : "1",
            "_score" : 1.0,
            "_source" : {
              "first" : "johnny",
              "last" : "gaudreau",
              "goals" : [
                9,
                27,
                1
              ],
              "assists" : [
                17,
                46,
                0
              ],
              "gp" : [
                26,
                82,
                1
              ],
              "time" : "2020-08-30"
            }
          }
        ]
      }
    }

    もちろん、検索した年を次のように変更します.
    POST /hockey/_search
    {
      "query": {
        "script": {
          "script": """
           doc['time'].value.getYear() < 2000
         
          """
        }
      }
    }

    ドキュメントの日付が2020-08-30であるため、何も検索できません.
    最も簡潔な方法を直接使用することもできます.
    POST /hockey/_search
    {
      "query": {
        "script": {
          "script": """
           return doc['time'].value.year > 1999
          """
        }
      }
    }

    ここでは,yearを直接属性として用いて取得する.
     
    例3
    私たちも直接ソース操作:
    POST /hockey/_update/1
    {
      "script": "Debug.explain(ctx._source)"
    }

    次の結果が表示されます.
    {
      "error" : {
        "root_cause" : [
          {
            "type" : "illegal_argument_exception",
            "reason" : "failed to execute script"
          }
        ],
        "type" : "illegal_argument_exception",
        "reason" : "failed to execute script",
        "caused_by" : {
          "type" : "script_exception",
          "reason" : "runtime error",
          "painless_class" : "java.util.LinkedHashMap",
          "to_string" : "{first=johnny, last=gaudreau, goals=[9, 27, 1], assists=[17, 46, 0], gp=[26, 82, 1], time=2020-08-30}",
          "java_class" : "java.util.LinkedHashMap",
          "script_stack" : [
            "Debug.explain(ctx._source)",
            "                 ^---- HERE"
          ],
          "script" : "Debug.explain(ctx._source)",
          "lang" : "painless",
          "position" : {
            "offset" : 17,
            "start" : 0,
            "end" : 26
          },
          "caused_by" : {
            "type" : "painless_explain_error",
            "reason" : null
          }
        }
      },
      "status" : 400
    }

    javaという名前が見えますutil.LinkedHashMapタイプのデータ.JavaのAPIドキュメントを直接検索して、対応する方法を使用することができます.