subquery
正確なフィルタや条件は不明であるものの、短いクエリを作成してフィルタや条件を設定できる場合には、subquery によってクエリを絞り込んで条件を評価できます。subquery は 1 つのクエリを使用して結果を別のクエリに返し、そのクエリで検索されたメッセージのセットを絞り込むか評価します。この方法の方が結合より速い場合もあります。結合では、大規模なデータ セットを統合してから結果を検索して結論を形成します。何らかの処理によってデータの範囲を狭めることができるのであれば、subquery を構築できます。
subquery では、親クエリにクエリの本体が含まれ、子クエリには親クエリのフィルタリングに必要な結果のみが含まれます。
- 子クエリ。フィルタリングを行います。最初に実行され、親クエリへの中間入力を生成します。親クエリとは異なる時間範囲を指定できます。
- 親クエリ。子クエリ (1 つまたは複数) に依存して実行を完了します。
構文
subquery は、クエリの検索式 (最初のパイプ (|) の前) で、where および if operator と一緒に使用できます。
検索式の構文
Parent query
[subquery [from=(<fromTime>)] [to=(<toTime>)] : <child query>
| compose <field1>[, <field2>, ...] [maxresults=<int>] [keywords]
]
Rest of parent query
where operator の構文:
Parent query
| where [subquery [from=(<fromTime>)] [to=(<toTime>)] : <child query>
| compose <field1>[, <field2>, ...] [maxresults=<int>]
]
Rest of parent query
where operator の構文では 否定 !
オプションを使用できます (| where ![<subquery>]
)。
if operator の構文:
Parent query
| if ( [subquery [from=(<fromTime>)] [to=(<toTime>)] : <child query>
| compose <field1>[, <field2>, ...] [maxresults=<int>]
]
, <value_if_true>, <value_if_false> ) as <field>
Rest of parent query
if operator の構文では 否定 !
オプションを使用できます (| if !([<subquery>],1,0) as <field>
)。
必須の引数
compose
は、親クエリに返される子クエリの出力を制御します。
- 親クエリに返される子クエリのフィールド。親クエリには複数のフィールドを返すことができます。
- 値が返される形式。指定フィールドの結果は、表形式から標準 DNF 形式に変換されます。
たとえば、subquery が以下の結果を生成した場合:
_sourcehost | _sourcecatagory | clientip |
---|---|---|
prod-search-1 | stream | 1.1.1.1 |
prod-remix-1 | remix | 10.10.10.10 |
次のように単一の出力に変換されます。
(( _sourcehost="prod-search-1" AND _sourcecatagory=”stream” AND clientip=”1.1.1.1”) OR (_sourcehost=”prod-remix-1” AND _sourcecatagory=”remix” AND clientip=”10.10.10.10”))
- 結果の列間は
AND
、行間はOR
で連結されます。 - 各行は
( )
で囲まれ、結果全体は別の( )
で囲まれます。
省略可能な引数
from=(<fromTime>) to=(<toTime>)
子クエリのsubquery
には親クエリとは異なる時間範囲を指定できます。デフォルトでは、子クエリは親クエリと同じ時間範囲で実行されます。相対時間と絶対時間のどちらでも指定できます。例とサポートされるフォーマットについては、「異なる時間範囲の subquery」を参照してください。
maxresults=<int>
子クエリから親クエリに返される結果の数を制限できます。パフォーマンスを高めるため、デフォルトでは返される結果を最大 2,500 件に設定してありますが、10,000 件まで増やすことができます。
keywords
keyword 句を指定しないと、結果のフィールド名はキーと値のペアとして親に返されるため、クエリが機能するためには親クエリにもこれらのフィールドが存在する必要があります。
keywords
を指定すると、キーと値のペアのみから値が返され、キーがフィールド名になります。たとえば、subquery が以下の結果を生成した場合:
_sourcehost | _sourcecatagory | clientip |
---|---|---|
prod-search-1 | stream | 1.1.1.1 |
prod-remix-1 | remix | 10.10.10.10 |
次のように単一の出力に変換されます。
((”prod-search-1” AND ”stream” AND ”1.1.1.1”) OR (”prod-remix-1” AND ”remix” AND ”10.10.10.10”))
結果にはキーと値のペアからの値のみが含まれ、キー (フィールド名) は返されません。これは、親クエリの既存の (すでに parse された) フィールドではなくリテラル値と一致させたい場合に便利です。
制限事項
- 子クエリから返される一意の結果 (行) 数の上限は 10,000 件で、これらの結果を返すためのメモリの上限は 100MB です。次のエラーメッセージが表示された場合
Subquery reached the maximum memory limit. Some records will be truncated.
子クエリの最大結果数またはメモリ量の制限に達したことを意味します。 - [Log Search (ログ検索)] ビューには、子クエリのヒストグラムは表示されません。表示されるヒストグラムは、親クエリのみを反映しています。
- API から返される結果は、すべて最終出力からの結果です。子クエリからの結果は返されません。
- 受信時間はサポートされません。
- 以下の場所では、subquery はサポートされません。
- Scheduled View
- FER
- ライブ ダッシュボード
- リアルタイム アラート
クエリの例
ショッピング Web サイトで最もアクティブなユーザの購買行動をトラッキングしたいとします。この場合は以下の手順で subquery
を使用することで、希望する結果を得ることができます。
- 特定のユーザがチェックアウトした商品と購入した商品を返すクエリ (親クエリ) を作成します。
- Web サイトで最もアクティブなユーザをトラッキングするクエリ (子クエリ) を作成します。
- subquery を使用して子クエリから親クエリに user_id または user_ip を渡すことで、1 つのクエリでワークフローが完了するようにします。
ステップ 1: 親クエリの作成
ここで作成する親クエリは、特定のユーザがチェックアウトした商品と購入した商品に関する統計情報を提供します。この例では、IP でユーザをトラッキングし、次のクエリを使用してカスタム アプリケーション ログを処理します。
_sourceCategory=reinvent/travel/checkout 243.63.233.30
| json field=_raw "funcName"
| where funcname in ("process_cart","charge")
| if (funcname = "process_cart" , "Checkout", "Purchased") as funcname
| count by funcname
subquery を使用することで、強調表示されている IP アドレスを、子クエリからこの親クエリに検索式のキーワードとして渡すことができます。
ステップ 2: 子クエリの作成
ここで作成する子クエリは、最もアクティブなユーザを特定し、次のように Web サーバ ログの IP アドレスを使用してトラッキングします。
_sourceCategory=reinvent/travel/nginx
| count by src_ip
| topk(1,_count)
このクエリの結果には、親クエリに渡す IP アドレス (243.63.233.30) が含まれています。
ステップ 3: subquery の作成
2 つのクエリを 1 つの subquery として組み合わせて、親クエリで子クエリの結果を利用できるようにします。subquery はいくつかの方法で作成できます。
- 子クエリからの IP アドレスが (最初のパイプ (|) の前にある) 検索式のキーワードとして使用されるように
keywords
を含めます。 - 結果がキーと値のペアの表形式で返されるように
keywords
引数を除外します。返されるフィールド (キー) は、親クエリの結果に存在する必要があります。 - subquery は
where
またはif
operator と一緒に使用します。where
句で IP アドレスをフィルタリングするのであれば、IP アドレスの代わりにフィルタ式を動的に生成する subquery を使用できます。- IP アドレスを
if
条件として使用する場合、if 条件が true または false のどちらになるかに従って値を新しいフィールドに割り当てることができます。
キーワード
キーワードを使用することで、親クエリからの IP アドレス (243.63.233.30) を子クエリに置き換えることができます。
_sourceCategory=reinvent/travel/checkout
[subquery:_sourceCategory=reinvent/travel/nginx
| count by src_ip
| topk(1,_count)
| compose src_ip keywords
]
| json field=_raw "funcName"
| where funcname in ("process_cart","charge")
| if (funcname = "process_cart" , "Checkout", "Purchased") as funcname
| count by funcname
ここでは IP アドレスをキーワードとして渡したいだけなので、compose
で scr_ip
フィールドと keywords
引数を指定しています。
キーワードを使用しない
親クエリのフィールドに IP アドレスが含まれている場合も (例: src_ip=243.63.233.30
、src_ip
はフィールド名)、親クエリで子クエリに置き換えることができます。
_sourceCategory=reinvent/travel/checkout
[subquery:_sourceCategory=reinvent/travel/nginx
| count by src_ip
| topk(1,_count)
| compose src_ip
]
| json field=_raw "funcName"
| where funcname in ("process_cart","charge")
| if (funcname = "process_cart" , "Checkout", "Purchased") as funcname
| count by funcname
where
フィルタ条件を使用するには、親クエリが子クエリと同じフィールド名を返す必要があります。クエリ内でフィールドを手動で parse するか、またはログ メタデータを利用します。
親クエリが同じフィールド名 (この例では src_ip
) を認識するようになったら、親クエリ内に where フィルタ式として子クエリを配置します。構文のセクションで説明したように、keywords
は where オペレーションではサポートされません。
_sourceCategory=reinvent/travel/checkout
| json field=_raw "source_ip" as src_ip
| json field=_raw "funcName"
| where funcname in ("process_cart","charge")
| where [subquery:_sourceCategory=reinvent/travel/nginx
| count by src_ip
| topk(1,_count)| compose src_ip]
| if (funcname = "process_cart" , "Checkout", "Purchased") as funcname
| count by funcname
if
subquery では有効な条件ステートメント (例: A=B) を返すことができるため、if オペレーションで subquery を使用できます。条件が true または false として評価されるためには、親クエリが子クエリと同じフィールド名を返す必要があります。クエリ内でフィールドを手動で parse するか、またはログ メタデータを利用します。
親クエリが同じフィールド名 (この例では src_ip
) を認識するようになったら、親クエリ内に if 条件として子クエリを配置します。構文のセクションで説明したように、keywords
は if オペレーションではサポートされません。
次の例では、boolean
というフィールドを作成しています。このフィールドは、最もアクティブなユーザの IP アドレスが見つかったかどうかによって true または false を返します。この subquery は (src_ip="243.63.233.30")
を返し、if operator がその IP アドレスを親クエリから取得したログと照合します。この例では、src_ip が親クエリからのログと一致すれば、true を返します。
_sourceCategory=reinvent/travel/checkout
| json field=_raw "source_ip" as src_ip
| json field=_raw "funcName"
| where funcname in ("process_cart","charge")
| if ( [subquery:_sourceCategory=reinvent/travel/nginx
| count by src_ip
| topk(1,_count)| compose src_ip], true, false) as boolean
| if (funcname = "process_cart" , "Checkout", "Purchased") as funcname
| count by funcname
異なる時間範囲の subquery
デフォルトでは、子クエリは親クエリと同じ時間範囲で実行されますが、カスタマイズ可能です。相対時間と絶対時間のどちらでも指定できます。
下記の例のように from
と to
の両方を使用して、時間範囲を明示的に指定してください。強調されている角括弧 [ ]
のみが必要で、残りは省略可能な引数です。
Parent query
[subquery [from=(<fromTime>)] [to=(<toTime>)] : <child query>
| compose <field1>[, <field2>, ...] [maxresults=<int>] [keywords]
]
Rest of parent query
相対時間範囲を指定するには、from
引数のみを指定します。例については、次のセクションを参照してください。
Time range usage examples
相対
[subquery from=(-15m):error
| count by _sourcehost
| topk(1, _count)
| compose _sourceHost]
| count by _sourceHost
絶対
[subquery from=(2018/07/08 23:13:36) to=(2018/07/09 23:13:36):error
| count by _sourcehost
| topk(1, _count)
| compose _sourceHost]
| count by _sourceHost
サポートされる時間形式
相対
- s - 秒
- m - 分
- h - 時
- d - 日
- w - 週
エポック
01/01/1970 00:00:00.000 UTC からのミリ秒数で指定するタイムスタンプ
絶対時間
ISO 8601 形式をサポートします。
- yyyy-MM-dd HH:mm:ss.SSS
- yyyy-MM-dd HH:mm:ss
- yyyy-MM-dd HH:mm
- yyyy-MM-dd
- MM-dd HH:mm:ss.SSS
- MM-dd HH:mm:ss
- MM-dd HH:mm
- MM-dd
- yyyy/MM/dd HH:mm:ss.SSS
- yyyy/MM/dd HH:mm:ss
- yyyy/MM/dd HH:mm
- yyyy/MM/dd
- MM/dd HH:mm:ss.SSS
- MM/dd HH:mm:ss
- MM/dd HH:mm
- MM/dd
例
subquery によるアラートの改善
一般に、アラートを構築する場合には、複雑な計算を実行して、何か悪いことが起きたときにだけアラートが発動することを保証しなければなりません。これらの複雑な計算は、アラートの発動にのみ必要であり、アラートから返される実際の結果では必要ありません。
subquery を使用しなければ、この詳細レベルでのアラートは不可能でしたが、subquery によって簡単に実現できるようになりました。たとえば、インデックスのデプロイ時の問題が発生するたびにアラートを発動したいとします。まず、リトライ回数が _count > 3
になった後でも特定のインデックスがデプロイされていないかどうかを見ます。もし該当のインデックスがあれば、subquery はそのインデックスに関する有益な情報 (デプロイ先のホストなど) を送信します。
_sourceCategory=search "error while retrying to deploy index"
| parse \",name=*-*\" as cus, index | where [subquery: _sourceCategory=search "error while retrying to deploy index" !info
| parse ",indexName='*-*'" as cus, index | count by index // Ignore cases where retry might have happened.
| where _count > 3
| compose by index]
| count by _sourceHost, cus, index
subquery による悪意のあるアクティビティのチェック
次の検索によってセキュリティ アナリストは、Amazon GuardDuty や CrowdStrike Threat フィードによってフラグが立てられた悪意のある IP アドレスに関連したログをトラッキングできます。この subquery は、脅威と見なした IP アドレスを src_ip
に格納して親クエリに返します。親クエリは src_ip フィールドが存在することを期待するため、keywords オプションは使用していません。結果には、subquery から脅威と見なされて返された src_ip
値を含む、ウェブログ sourceCategory からのログが含まれます。
_sourceCategory=weblogs
[subquery:_sourceCategory="Labs/SecDemo/guardduty" "EC2 Instance" "communicating on an unusual server port 22"
| json field=_raw "service.action.networkConnectionAction.remoteIpDetails" as remoteIpDetails
| json field=_raw "service.action.networkConnectionAction.connectionDirection" as connectionDirection
| where connectionDirection = "OUTBOUND"
| json field=remoteipdetails "ipAddressV4" as src_ip
| lookup type, actor, raw, threatlevel from sumo://threat/cs on src_ip=threat
| where threatlevel = "high"
| compose src_ip]
save と lookup を使用する場合の子クエリからのデータの参照
異なる Source からのデータを相関させたり、子クエリからのデータを compose で渡すことなくさらに集計したりする場合は、結果を制限するクエリの範囲が適用されるため、save および lookup operator を使用して、親クエリで必要なデータを取得できます。
このクエリは、特定のセッションを識別して、異なるデータ ソースからのステータス メッセージに相関させます。
_sourceCategory=katta
[subquery:(_sourceCategory=stream explainJSONPlan.ETT) error
| where !(statusmessage="Finished successfully" or statusmessage="Query canceled" or isNull(statusMessage))
| count by sessionId, statusMessage
| fields -_count
| save /explainPlan/neededSessions
| compose sessionId keywords]
| parse "[sessionId=*]" as sessionId
| lookup statusMessage from /explainPlan/neededSessions on sessionid=sessionid
このクエリは、トランザクション エラーを特定して、別のデータ ソースからの発送情報に相関させます。
[subquery: _source=sourceA and ("Transaction Error")
| parse "message=* )" as MsgTxt
| parse "transaction *\\\"" as trans_id
| save /transactions/errors
| compose trans_id keywords
]
_source=sourceB
| parse "name: SHIPPER_ID\n value: *\n}" as shipper_id
| parse "transaction *\\\"" as trans_id
| lookup MsgTxt from /transactions/errors on trans_id=trans_id
| count by shipper_id
ベスト プラクティス
subquery をよりスムーズに使用するためのヒントとテクニックを紹介します。
- フィルタ句内ではなく検索式 (最初のパイプ (|) の前) に子クエリを指定した方が、クエリのパフォーマンスは高くなります。以下の例にこの点を示します。まず、最初のパイプの前に subquery を使用することで、17 秒で実行を完了しています。
where 句で subquery を使用した場合では、実行に 29 秒もかかっています。
- 子クエリを使用してフィルタ句を構築する場合は、クエリの後方ではなく、検索式の近くにフィルタ句を配置することでパフォーマンスを高めることができます。右側のようなクエリが理想的です。
効率が悪い | 効率が良い |
|
|
- 子クエリは、最初に個別のタブで実行してください。compose operator をクエリの最後に追加して、返される結果をチェックしてください。クエリに満足したら、subquery にコピーします。この事前テストにより、間違った結果を生成するクエリが作成されてしまう可能性を抑えることができます。次のスクリーンショットは、compose operator を使用して別のタブで子クエリを構築する方法を示しています。
- subquery で生成されるレコードが多すぎる場合は、クエリの時間範囲を短くしてください。
- subquery が 10000 件を超える結果を返すか、またはメモリ上限の 100MB を超えると、次のエラー メッセージが表示されます。
Subquery reached the maximum memory limit. Some records will be truncated.
この場合は、子クエリの範囲を狭くするか、または maxResults で結果数の制限を減らしてください。