変換のためのPainlessの例
scripted_metric
集約を使用する例は、Elasticsearch Serverlessではサポートされていません。
これらの例は、変換におけるPainlessの使用方法を示しています。Painlessスクリプト言語の詳細については、Painlessガイドを参照してください。
- スクリプト化されたメトリック集約を使用してトップヒットを取得する
- 集約を使用して時間の特徴を取得する
- バケットスクリプトを使用して期間を取得する
- スクリプト化されたメトリック集約を使用してHTTPレスポンスをカウントする
- スクリプト化されたメトリック集約を使用してインデックスを比較する
以下の例のコンテキストは変換のユースケースですが、以下のスニペットのPainlessスクリプトは他のElasticsearch検索集約でも使用できます。
- 以下のすべての例はスクリプトを使用しており、フィールドがスクリプトによって作成されるとき、変換は出力フィールドのマッピングを推測できません。変換はこれらのフィールドの宛先インデックスにマッピングを作成しないため、動的にマッピングされます。明示的なマッピングが必要な場合は、変換を開始する前に宛先インデックスを作成してください。
スクリプト化されたメトリック集約を使用してトップヒットを取得する
このスニペットは、最新のドキュメント、つまり最新のタイムスタンプを持つドキュメントを見つける方法を示しています。技術的な観点からは、スクリプト化されたメトリック集約を使用して変換内でトップヒットの機能を実現するのに役立ちます。これはメトリック出力を提供します。
この例では、scripted_metric
集約を使用していますが、これはElasticsearch Serverlessではサポートされていません。
Js
"aggregations": {
"latest_doc": {
"scripted_metric": {
"init_script": "state.timestamp_latest = 0L; state.last_doc = ''",
"map_script": """
def current_date = doc['@timestamp'].getValue().toInstant().toEpochMilli();
if (current_date > state.timestamp_latest)
{state.timestamp_latest = current_date;
state.last_doc = new HashMap(params['_source']);}
""",
"combine_script": "return state",
"reduce_script": """
def last_doc = '';
def timestamp_latest = 0L;
for (s in states) {if (s.timestamp_latest > (timestamp_latest))
{timestamp_latest = s.timestamp_latest; last_doc = s.last_doc;}}
return last_doc
"""
}
}
}
init_script は、state オブジェクト内に長い型のtimestamp_latest と文字列型のlast_doc を作成します。 |
|
map_script は、ドキュメントのタイムスタンプに基づいてcurrent_date を定義し、current_date とstate.timestamp_latest を比較し、最終的にstate.last_doc をシャードから返します。new HashMap(...) を使用すると、ソースドキュメントをコピーします。これは、1つのフェーズから次のフェーズに完全なソースオブジェクトを渡したいときに重要です。 |
|
combine_script は、各シャードからstate を返します。 |
|
reduce_script は、各シャードから返されたs.timestamp_latest の値を反復処理し、最新のタイムスタンプを持つドキュメント(last_doc )を返します。レスポンスでは、トップヒット(つまり、latest_doc )はlatest_doc フィールドの下にネストされています。 |
スクリプトの詳細な説明については、スクリプトの範囲を確認してください。
最後の値を同様の方法で取得できます:
Js
"aggregations": {
"latest_value": {
"scripted_metric": {
"init_script": "state.timestamp_latest = 0L; state.last_value = ''",
"map_script": """
def current_date = doc['@timestamp'].getValue().toInstant().toEpochMilli();
if (current_date > state.timestamp_latest)
{state.timestamp_latest = current_date;
state.last_value = params['_source']['value'];}
""",
"combine_script": "return state",
"reduce_script": """
def last_value = '';
def timestamp_latest = 0L;
for (s in states) {if (s.timestamp_latest > (timestamp_latest))
{timestamp_latest = s.timestamp_latest; last_value = s.last_value;}}
return last_value
"""
}
}
}
保存されたスクリプトを使用して最新の値を取得する
最新の値を取得するために、保存されたスクリプトの力を使用することもできます。保存されたスクリプトはコンパイル時間を短縮し、検索を高速化し、更新可能です。
- 1. 保存されたスクリプトを作成します:
Js
POST _scripts/last-value-map-init
{
"script": {
"lang": "painless",
"source": """
state.timestamp_latest = 0L; state.last_value = ''
"""
}
}
POST _scripts/last-value-map
{
"script": {
"lang": "painless",
"source": """
def current_date = doc['@timestamp'].getValue().toInstant().toEpochMilli();
if (current_date > state.timestamp_latest)
{state.timestamp_latest = current_date;
state.last_value = doc[params['key']].value;}
"""
}
}
POST _scripts/last-value-combine
{
"script": {
"lang": "painless",
"source": """
return state
"""
}
}
POST _scripts/last-value-reduce
{
"script": {
"lang": "painless",
"source": """
def last_value = '';
def timestamp_latest = 0L;
for (s in states) {if (s.timestamp_latest > (timestamp_latest))
{timestamp_latest = s.timestamp_latest; last_value = s.last_value;}}
return last_value
"""
}
}
- 2. スクリプト化されたメトリック集約で保存されたスクリプトを使用します。
Js
"aggregations":{
"latest_value":{
"scripted_metric":{
"init_script":{
"id":"last-value-map-init"
},
"map_script":{
"id":"last-value-map",
"params":{
"key":"field_with_last_value"
}
},
"combine_script":{
"id":"last-value-combine"
},
"reduce_script":{
"id":"last-value-reduce"
}
パラメータfield_with_last_value は、最新の値を取得したい任意のフィールドに設定できます。 |
集約を使用して時間の特徴を取得する
このスニペットは、変換内でPainlessを使用して時間に基づく特徴を抽出する方法を示しています。このスニペットは、@timestamp
がdate
型フィールドとして定義されているインデックスを使用します。
Js
"aggregations": {
"avg_hour_of_day": {
"avg":{
"script": {
"source": """
ZonedDateTime date = doc['@timestamp'].value;
return date.getHour();
"""
}
}
},
"avg_month_of_year": {
"avg":{
"script": {
"source": """
ZonedDateTime date = doc['@timestamp'].value;
return date.getMonthValue();
"""
}
}
},
...
}
集約の名前。 | |
日の時間を返すPainlessスクリプトを含みます。 | |
ドキュメントのタイムスタンプに基づいてdate を設定します。 |
|
date から時間値を返します。 |
|
集約の名前。 | |
年の月を返すPainlessスクリプトを含みます。 | |
ドキュメントのタイムスタンプに基づいてdate を設定します。 |
|
date から月値を返します。 |
バケットスクリプトを使用して期間を取得する
この例は、バケットスクリプトを使用して、クライアントIPからデータログのセッションの期間を取得する方法を示しています。この例では、Kibanaのサンプルウェブログデータセットを使用しています。
Python
resp = client.transform.put_transform(
transform_id="data_log",
source={
"index": "kibana_sample_data_logs"
},
dest={
"index": "data-logs-by-client"
},
pivot={
"group_by": {
"machine.os": {
"terms": {
"field": "machine.os.keyword"
}
},
"machine.ip": {
"terms": {
"field": "clientip"
}
}
},
"aggregations": {
"time_frame.lte": {
"max": {
"field": "timestamp"
}
},
"time_frame.gte": {
"min": {
"field": "timestamp"
}
},
"time_length": {
"bucket_script": {
"buckets_path": {
"min": "time_frame.gte.value",
"max": "time_frame.lte.value"
},
"script": "params.max - params.min"
}
}
}
},
)
print(resp)
Js
const response = await client.transform.putTransform({
transform_id: "data_log",
source: {
index: "kibana_sample_data_logs",
},
dest: {
index: "data-logs-by-client",
},
pivot: {
group_by: {
"machine.os": {
terms: {
field: "machine.os.keyword",
},
},
"machine.ip": {
terms: {
field: "clientip",
},
},
},
aggregations: {
"time_frame.lte": {
max: {
field: "timestamp",
},
},
"time_frame.gte": {
min: {
field: "timestamp",
},
},
time_length: {
bucket_script: {
buckets_path: {
min: "time_frame.gte.value",
max: "time_frame.lte.value",
},
script: "params.max - params.min",
},
},
},
},
});
console.log(response);
Console
PUT _transform/data_log
{
"source": {
"index": "kibana_sample_data_logs"
},
"dest": {
"index": "data-logs-by-client"
},
"pivot": {
"group_by": {
"machine.os": {"terms": {"field": "machine.os.keyword"}},
"machine.ip": {"terms": {"field": "clientip"}}
},
"aggregations": {
"time_frame.lte": {
"max": {
"field": "timestamp"
}
},
"time_frame.gte": {
"min": {
"field": "timestamp"
}
},
"time_length": {
"bucket_script": {
"buckets_path": {
"min": "time_frame.gte.value",
"max": "time_frame.lte.value"
},
"script": "params.max - params.min"
}
}
}
}
}
セッションの長さを定義するために、バケットスクリプトを使用します。 | |
バケットパスは、スクリプト変数のマップと、それらの変数に使用するバケットへの関連パスです。この特定のケースでは、min とmax がtime_frame.gte.value とtime_frame.lte.value にマッピングされた変数です。 |
|
最後に、スクリプトはセッションの開始日から終了日を引き算し、セッションの期間を得ます。 |
スクリプト化されたメトリック集約を使用してHTTPレスポンスをカウントする
ウェブログデータセット内の異なるHTTPレスポンスタイプをスクリプト化されたメトリック集約を使用してカウントできます。フィルター集約を使用しても同様の機能を実現できます。詳細については、疑わしいクライアントIPの発見の例を確認してください。
以下の例では、HTTPレスポンスコードがドキュメントのresponse
フィールドにキーワードとして保存されていると仮定しています。
この例では、scripted_metric
集約を使用していますが、これはElasticsearch Serverlessではサポートされていません。
Js
"aggregations": {
"responses.counts": {
"scripted_metric": {
"init_script": "state.responses = ['error':0L,'success':0L,'other':0L]",
"map_script": """
def code = doc['response.keyword'].value;
if (code.startsWith('5') || code.startsWith('4')) {<br> state.responses.error += 1 ;<br> } else if(code.startsWith('2')) {<br> state.responses.success += 1;<br> } else {<br> state.responses.other += 1;<br> }<br> """,<br> "combine_script": "state.responses",<br> "reduce_script": """<br> def counts = ['error': 0L, 'success': 0L, 'other': 0L];<br> for (responses in states) {<br> counts.error += responses['error'];<br> counts.success += responses['success'];<br> counts.other += responses['other'];<br> }<br> return counts;<br> """<br> }<br> },<br> ...<br>}<br>``````<br><br>
| | |
| --- | --- |
| | すべての集約を含む変換の`````aggregations`````オブジェクト。 |
| | `````scripted_metric`````集約のオブジェクト。 |
| | この`````scripted_metric`````は、ウェブログデータに対して特定のHTTPレスポンスタイプ(エラー、成功、その他)をカウントする分散操作を実行します。 |
| | `````init_script`````は、`````state`````オブジェクト内に`````responses`````配列を作成し、長いデータ型の3つのプロパティ(`````error`````、`````success`````、`````other`````)を持ちます。 |
| | `````map_script`````は、ドキュメントの`````response.keyword`````値に基づいて`````code`````を定義し、レスポンスの最初の数字に基づいてエラー、成功、その他のレスポンスをカウントします。 |
| | `````combine_script`````は、各シャードから`````state.responses`````を返します。 |
| | `````reduce_script`````は、`````counts`````、`````error`````、`````success`````プロパティを持つ`````other`````配列を作成し、各シャードから返された`````responses`````の値を反復処理し、異なるレスポンスタイプを`````counts`````オブジェクトの適切なプロパティに割り当てます。エラー応答はエラーカウントに、成功応答は成功カウントに、その他の応答はその他のカウントに割り当てられます。最終的に、レスポンスカウントを持つ`````counts`````配列を返します。 |
## スクリプト化されたメトリック集約を使用してインデックスを比較する
この例は、スクリプト化されたメトリック集約を使用した変換によって2つのインデックスの内容を比較する方法を示しています。
この例では、`````scripted_metric`````集約を使用していますが、これはElasticsearch Serverlessではサポートされていません。
#### Python
``````python
resp = client.transform.preview_transform(
id="index_compare",
source={
"index": [
"index1",
"index2"
],
"query": {
"match_all": {}
}
},
dest={
"index": "compare"
},
pivot={
"group_by": {
"unique-id": {
"terms": {
"field": "<unique-id-field>"
}
}
},
"aggregations": {
"compare": {
"scripted_metric": {
"map_script": "state.doc = new HashMap(params['_source'])",
"combine_script": "return state",
"reduce_script": " \n if (states.size() != 2) {\n return \"count_mismatch\"\n }\n if (states.get(0).equals(states.get(1))) {\n return \"match\"\n } else {\n return \"mismatch\"\n }\n "
}
}
}
},
)
print(resp)
Js
const response = await client.transform.previewTransform({
id: "index_compare",
source: {
index: ["index1", "index2"],
query: {
match_all: {},
},
},
dest: {
index: "compare",
},
pivot: {
group_by: {
"unique-id": {
terms: {
field: "<unique-id-field>",
},
},
},
aggregations: {
compare: {
scripted_metric: {
map_script: "state.doc = new HashMap(params['_source'])",
combine_script: "return state",
reduce_script:
' \n if (states.size() != 2) {\n return "count_mismatch"\n }\n if (states.get(0).equals(states.get(1))) {\n return "match"\n } else {\n return "mismatch"\n }\n ',
},
},
},
},
});
console.log(response);
Console
POST _transform/_preview
{
"id" : "index_compare",
"source" : {
"index" : [
"index1",
"index2"
],
"query" : {
"match_all" : { }
}
},
"dest" : {
"index" : "compare"
},
"pivot" : {
"group_by" : {
"unique-id" : {
"terms" : {
"field" : "<unique-id-field>"
}
}
},
"aggregations" : {
"compare" : {
"scripted_metric" : {
"map_script" : "state.doc = new HashMap(params['_source'])",
"combine_script" : "return state",
"reduce_script" : """
if (states.size() != 2) {
return "count_mismatch"
}
if (states.get(0).equals(states.get(1))) {
return "match"
} else {
return "mismatch"
}
"""
}
}
}
}
}
source オブジェクトで参照されるインデックスが互いに比較されます。 |
|
dest インデックスには比較の結果が含まれます。 |
|
group_by フィールドは、各ドキュメントの一意の識別子である必要があります。 |
|
scripted_metric 集約のオブジェクト。 |
|
map_script は、状態オブジェクト内でdoc を定義します。new HashMap(...) を使用すると、ソースドキュメントをコピーします。これは、1つのフェーズから次のフェーズに完全なソースオブジェクトを渡したいときに重要です。 |
|
combine_script は、各シャードからstate を返します。 |
|
reduce_script は、インデックスのサイズが等しいかどうかを確認します。等しくない場合は、count_mismatch を報告します。その後、2つのインデックスのすべての値を反復処理し、比較します。値が等しい場合はmatch を返し、そうでない場合はmismatch を返します。 |
スクリプト化されたメトリック集約を使用してウェブセッションの詳細を取得する
この例は、単一のトランザクションから複数の特徴を導出する方法を示しています。データからの例のソースドキュメントを見てみましょう:
ソースドキュメント
Js
{
"_index":"apache-sessions",
"_type":"_doc",
"_id":"KvzSeGoB4bgw0KGbE3wP",
"_score":1.0,
"_source":{
"@timestamp":1484053499256,
"apache":{
"access":{
"sessionid":"571604f2b2b0c7b346dc685eeb0e2306774a63c2",
"url":"http://www.leroymerlin.fr/v3/search/search.do?keyword=Carrelage%20salle%20de%20bain",
"path":"/v3/search/search.do",
"query":"keyword=Carrelage%20salle%20de%20bain",
"referrer":"http://www.leroymerlin.fr/v3/p/produits/carrelage-parquet-sol-souple/carrelage-sol-et-mur/decor-listel-et-accessoires-carrelage-mural-l1308217717?resultOffset=0&resultLimit=51&resultListShape=MOSAIC&priceStyle=SALEUNIT_PRICE",
"user_agent":{
"original":"Mobile Safari 10.0 Mac OS X (iPad) Apple Inc.",
"os_name":"Mac OS X (iPad)"
},
"remote_ip":"0337b1fa-5ed4-af81-9ef4-0ec53be0f45d",
"geoip":{
"country_iso_code":"FR",
"location":{
"lat":48.86,
"lon":2.35
}
},
"response_code":200,
"method":"GET"
}
}
}
}
...
この例では、`````scripted_metric`````集約を使用していますが、これはElasticsearch Serverlessではサポートされていません。
#### Js
``````js
POST _transform/_preview
{
"source": {
"index": "apache-sessions"
},
"pivot": {
"group_by": {
"sessionid": {
"terms": {
"field": "apache.access.sessionid"
}
}
},
"aggregations": {
"distinct_paths": {
"cardinality": {
"field": "apache.access.path"
}
},
"num_pages_viewed": {
"value_count": {
"field": "apache.access.url"
}
},
"session_details": {
"scripted_metric": {
"init_script": "state.docs = []",
"map_script": """
Map span = [
'@timestamp':doc['@timestamp'].value,
'url':doc['apache.access.url'].value,
'referrer':doc['apache.access.referrer'].value
];
state.docs.add(span)
""",
"combine_script": "return state.docs;",
"reduce_script": """
def all_docs = [];
for (s in states) {
for (span in s) {
all_docs.add(span);
}
}
all_docs.sort((HashMap o1, HashMap o2)->o1['@timestamp'].toEpochMilli().compareTo(o2['@timestamp'].toEpochMilli()));
def size = all_docs.size();
def min_time = all_docs[0]['@timestamp'];
def max_time = all_docs[size-1]['@timestamp'];
def duration = max_time.toEpochMilli() - min_time.toEpochMilli();
def entry_page = all_docs[0]['url'];
def exit_path = all_docs[size-1]['url'];
def first_referrer = all_docs[0]['referrer'];
def ret = new HashMap();
ret['first_time'] = min_time;
ret['last_time'] = max_time;
ret['duration'] = duration;
ret['entry_page'] = entry_page;
ret['exit_path'] = exit_path;
ret['first_referrer'] = first_referrer;
return ret;
"""
}
}
}
}
}
`
データはsessionid でグループ化されます。 |
|
集約は、セッション中に表示されたページのパスの数をカウントし、列挙します。 | |
init_script は、state オブジェクト内に配列型のdoc を作成します。 |
|
map_script は、ドキュメントの対応する値に基づいて、タイムスタンプ、URL、およびリファラー値を持つspan 配列を定義し、span 配列の値をdoc オブジェクトに追加します。 |
|
combine_script は、各シャードからstate.docs を返します。 |
|
reduce_script は、ドキュメントフィールドに基づいてmin_time 、max_time 、duration などのさまざまなオブジェクトを定義し、ret オブジェクトを宣言し、new HashMap () を使用してソースドキュメントをコピーします。次に、スクリプトはfirst_time 、last_time 、duration およびret オブジェクト内の他のフィールドを、前に定義された対応するオブジェクトに基づいて定義し、最終的にret を返します。 |
API呼び出しの結果は、同様のレスポンスになります:
Js
{
"num_pages_viewed" : 2.0,
"session_details" : {
"duration" : 100300001,
"first_referrer" : "https://www.bing.com/",
"entry_page" : "http://www.leroymerlin.fr/v3/p/produits/materiaux-menuiserie/porte-coulissante-porte-interieure-escalier-et-rambarde/barriere-de-securite-l1308218463",
"first_time" : "2017-01-10T21:22:52.982Z",
"last_time" : "2017-01-10T21:25:04.356Z",
"exit_path" : "http://www.leroymerlin.fr/v3/p/produits/materiaux-menuiserie/porte-coulissante-porte-interieure-escalier-et-rambarde/barriere-de-securite-l1308218463?__result-wrapper?pageTemplate=Famille%2FMat%C3%A9riaux+et+menuiserie&resultOffset=0&resultLimit=50&resultListShape=PLAIN&nomenclatureId=17942&priceStyle=SALEUNIT_PRICE&fcr=1&*4294718806=4294718806&*14072=14072&*4294718593=4294718593&*17942=17942"
},
"distinct_paths" : 1.0,
"sessionid" : "000046f8154a80fd89849369c984b8cc9d795814"
},
{
"num_pages_viewed" : 10.0,
"session_details" : {
"duration" : 343100405,
"first_referrer" : "https://www.google.fr/",
"entry_page" : "http://www.leroymerlin.fr/",
"first_time" : "2017-01-10T16:57:39.937Z",
"last_time" : "2017-01-10T17:03:23.049Z",
"exit_path" : "http://www.leroymerlin.fr/v3/p/produits/porte-de-douche-coulissante-adena-e168578"
},
"distinct_paths" : 8.0,
"sessionid" : "000087e825da1d87a332b8f15fa76116c7467da6"
}
...