-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security Solution][Alerts] improves performance of new terms multi fields #145167
Changes from 19 commits
e918ed0
33bd49e
f3bf6ac
4700752
3545a6f
7921c50
5308301
5d53cfa
8d50f10
65f0e30
f28d144
c74b919
1b0ce67
2087dc6
29c026b
02e2948
5b95dd9
7516076
981896b
dbb71e3
3b26fa6
da52b8d
705984d
26cb0b8
f2be791
8f10528
a153d77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ import { | |
getNewTermsRuntimeMappings, | ||
getAggregationField, | ||
decodeMatchedValues, | ||
createFieldValuesMap, | ||
} from './utils'; | ||
import { | ||
addToSearchAfterReturn, | ||
|
@@ -193,6 +194,7 @@ export const createNewTermsAlertType = ( | |
} | ||
const bucketsForField = searchResultWithAggs.aggregations.new_terms.buckets; | ||
const includeValues = transformBucketsToValues(params.newTermsFields, bucketsForField); | ||
const fieldsValuesMap = createFieldValuesMap(params.newTermsFields, bucketsForField); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: could make this something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
// PHASE 2: Take the page of results from Phase 1 and determine if each term exists in the history window. | ||
// The aggregation filters out buckets for terms that exist prior to `tuple.from`, so the buckets in the | ||
// response correspond to each new term. | ||
|
@@ -209,7 +211,7 @@ export const createNewTermsAlertType = ( | |
}), | ||
runtimeMappings: { | ||
...runtimeMappings, | ||
...getNewTermsRuntimeMappings(params.newTermsFields), | ||
...getNewTermsRuntimeMappings(params.newTermsFields, fieldsValuesMap), | ||
}, | ||
searchAfterSortIds: undefined, | ||
index: inputIndex, | ||
|
@@ -255,7 +257,7 @@ export const createNewTermsAlertType = ( | |
}), | ||
runtimeMappings: { | ||
...runtimeMappings, | ||
...getNewTermsRuntimeMappings(params.newTermsFields), | ||
...getNewTermsRuntimeMappings(params.newTermsFields, fieldsValuesMap), | ||
}, | ||
searchAfterSortIds: undefined, | ||
index: inputIndex, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,8 +80,43 @@ export const transformBucketsToValues = ( | |
); | ||
}; | ||
|
||
/** | ||
* transforms arrays of new terms fields and its values in object | ||
* [new_terms_field]: { [value1]: true, [value1]: true } | ||
* It's needed to have constant time complexity of accessing whether value is present in new terms | ||
* It will be passed to Painless script used in runtime field | ||
*/ | ||
export const createFieldValuesMap = ( | ||
newTermsFields: string[], | ||
buckets: estypes.AggregationsCompositeBucket[] | ||
) => { | ||
if (newTermsFields.length === 1) { | ||
return undefined; | ||
} | ||
|
||
const valuesMap = newTermsFields.reduce<Record<string, Record<string, boolean>>>( | ||
(acc, field) => ({ ...acc, [field]: {} }), | ||
{} | ||
); | ||
|
||
buckets | ||
.map((bucket) => bucket.key) | ||
.forEach((bucket) => { | ||
Object.entries(bucket).forEach(([key, value]) => { | ||
if (value == null) { | ||
return; | ||
} | ||
const strValue = typeof value !== 'string' ? value.toString() : value; | ||
valuesMap[key][strValue] = true; | ||
}); | ||
}); | ||
|
||
return valuesMap; | ||
}; | ||
|
||
export const getNewTermsRuntimeMappings = ( | ||
newTermsFields: string[] | ||
newTermsFields: string[], | ||
values?: Record<string, Record<string, boolean>> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good idea, done |
||
): undefined | { [AGG_FIELD_NAME]: estypes.MappingRuntimeField } => { | ||
// if new terms include only one field we don't use runtime mappings and don't stich fields buckets together | ||
if (newTermsFields.length <= 1) { | ||
|
@@ -92,7 +127,7 @@ export const getNewTermsRuntimeMappings = ( | |
[AGG_FIELD_NAME]: { | ||
type: 'keyword', | ||
script: { | ||
params: { fields: newTermsFields }, | ||
params: { fields: newTermsFields, values }, | ||
source: ` | ||
def stack = new Stack(); | ||
// ES has limit in 100 values for runtime field, after this query will fail | ||
|
@@ -110,9 +145,14 @@ export const getNewTermsRuntimeMappings = ( | |
emit(line); | ||
emitLimit = emitLimit - 1; | ||
} else { | ||
for (field in doc[params['fields'][index]]) { | ||
def fieldName = params['fields'][index]; | ||
for (field in doc[fieldName]) { | ||
def fieldStr = String.valueOf(field); | ||
if (!params['values'][fieldName].containsKey(fieldStr)) { | ||
continue; | ||
} | ||
def delimiter = index === 0 ? '' : '${DELIMITER}'; | ||
def nextLine = line + delimiter + String.valueOf(field).encodeBase64(); | ||
def nextLine = line + delimiter + fieldStr.encodeBase64(); | ||
|
||
stack.add([index + 1, nextLine]) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This limitation could still be hit, right? It's just less likely now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated README with recent findings