Source to destination Key Field mapping in Elastic Search - elasticsearch

I have a elastic search index with source data coming in the following way:
"_source": {
"email": "smithamber#example.com",
"time": "2022-09-08T13:52:50.347861",
"message": "Pattern thank talk mention. Manage nearly tell beat. Difficult husband feel talk radio however.",
"sIp": "192.168.11.156",
"dIp": "80.254.211.60",
"ts": "2022-09-08T13:52:50"
}
Now I want a way to treat dynamically map #timestamp [destination key] field of ES doc to be time [source key]. For this i am using:
"runtime_mappings": {
"#timestamp": {
"type": "date",
"format": "yyyyMMdd'T'HHmmss.SSSZ",
"script": {
"source": "if (doc[\"time\"].size() == 0) {return} else {return doc[\"time\"].value;}",
"lang": "painless"
}
}
}
However, this does not work. Is there a better way to map source key field to destination key field in elastic search. I am open to static mapping as well if we set once before creating the index for one kind of source data.
I am looking for correct syntax for mapping my field.
Edited:
When I add the query -
{ "query": {
"range": {
"#timestamp": {
"gte": "now-5d",
"lte": "now"
}
}
}
}
I see no hits.
{
"took": 20,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
However, same query on field time gets me all filtered docs.
{
"took": 27,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10000,
"relation": "gte"
},
"max_score": 1.0,
"hits": [
{
"_index": "topic-indexer-xxx",
"_id": "c28sIYMB0xJUJru8c47O",
"_score": 1.0,
"_source": {
"email": "albertthompson#example.com",
"time": "2022-09-07T15:25:33.672016",
"message": "Candidate future staff ever former run. Like quality personal specific trouble cell money move. Available majority memory model thing TV wrong. Summer anyone light key.",
"sIp": "192.168.103.75",
"dIp": "191.27.68.163"
}
},
....
}
For mapping I have also tried dynamic templates; but still no results on query for #timestamp field:
{
"dynamic_templates": [
{
"#timestamp": {
"match": "time",
"mapping": {
"type": "date",
"format": "strict_date_optional_time",
"copy_to": "#timestamp"
}
}
}
]
}
With #paulo's response, I just did a little fine tuning to resolve the issue; The below mapping (as set) works and then I can run range queries on the #timestamp field:
{
"runtime": {
"#timestamp": {
"type": "date",
"script": {
"source": "if (doc['time'].size() != 0){ emit(doc['time'].value.toEpochMilli());}",
"lang": "painless"
}
}
},
"properties": {
"#timestamp": {
"type": "date"
}
}
}

Tldr;
I feel you go mixed up in your painless script.
Please find below an example you should be able to reproduce on your side.
Time is already a date on my side. Elasticsearch was able to detect it automatically.
On another note, using runtime fields while very flexible, may lead to performance issue on the long run.
Maybe you should be looking into ingest pipeline.
Solution
POST /73684302/_doc
{
"email": "smithamber#example.com",
"time": "2022-09-08T13:52:50.347861",
"message": "Pattern thank talk mention. Manage nearly tell beat. Difficult husband feel talk radio however.",
"sIp": "192.168.11.156",
"dIp": "80.254.211.60",
"ts": "2022-09-08T13:52:50"
}
POST /73684302/_doc
{
"email": "smithamber#example.com",
"message": "Pattern thank talk mention. Manage nearly tell beat. Difficult husband feel talk radio however.",
"sIp": "192.168.11.156",
"dIp": "80.254.211.60",
"ts": "2022-09-08T13:52:50"
}
GET /73684302/_search
{
"runtime_mappings": {
"#timestamp": {
"type": "date",
"script": {
"source": """
if (doc["time"].size() != 0){
emit(doc["time"].value.toEpochMilli());
}
""",
"lang": "painless"
}
}
},
"_source": false,
"fields": ["#timestamp"]
}

Related

Why does elasticsearch filter does not give any results whereas using kibana dasboard gives the result?

I am query elastic search using sense. When using range filter on field, I get empty hits, but I am able to get results using kibana dashboard. Why is the filter not working? My query:
GET _search
{
"query": {
"bool": {
"must": [
{"match": {"field_name1": "value1"}},
{"match": {"file_name2": "value2"}}
]
}
},
"filter": { <- not working (no data, but gets data from kibana)
"range": {
"#timestamp": {
"gte": "2017-02-18"
}
}
},
"sort": [
{
"#timestamp": {
"order": "desc",
"ignore_unmapped" : true
}
}
]
}
From kibana dashboard when I add the time it add the time:(from:'2017-02-18T10:19:08.680Z',mode:absolute,to:'2017-02-19T10:19:08.680Z')) and I am able to see results. The dashboard also adds some other stuff like metadata and filter with negate but I think they do the same. Only the time part seem to be different. So why the difference and is my query correct? The sample url:
https://elasticsearch/app/kibana#/discover?
_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2017-02-18T09:23:41.044Z',mode:absolute,to:'2017-02-19T09:23:41.044Z'))
&_a=(columns:!(description,id),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:index-value,key:field_name1,negate:!f,value:value1),query:(match:(field_name2:(query:value2,type:phrase))))),index:index-value,interval:auto,query:(query_string:(analyze_wildcard:!t,query:'*')),sort:!('#timestamp',desc),uiState:(),vis:(aggs:!((params:(field:field_name2,orderBy:'2',size:20),schema:segment,type:terms),(id:'2',schema:metric,type:count)),type:histogram))
&indexPattern=index-value&type=histogram
Thanks.
Sample json response:
{
"took": some_number,
"timed_out": false,
"_shards": {
"total": some_number,
"successful": some_number,
"failed": 0
},
"hits": {
"total": some_number,
"max_score": null,
"hits": [
{
"_index": "index-name",
"_type": "log-1",
"_id": "alphanum",
"_score": null,
"_source": {
"headers": "header-string",
"query_string": "query-string",
"server_variables": "server-variables",
"cookies": "cookies",
"extra_data": "some extra stuff",
"exception_data_obj": {
"stack_trace": "",
"source": "",
"message": "success",
"additional_data": ""
},
"some_id": "211FA1F1-F312-1234-B539-F7AAE23EAA2F",
"level": "Warn",
"description": "Success",
"#timestamp": "2017-01-20T01:33:27.303Z",
"field1": "value1",
"field2": "value2"
"key": {
"key.field1": "key.value1",
"key.field2": "key.value2"
}
"#by": "app-name",
"environment": "env-name"
},
"sort": [
1484876007303
]
},
{}
]
}
}
it's not the same query, in the sense query you asked must query on field1 and field2 but in kibana you didn't

Can I force ES to return dates in epoch_millis format?

I have this field mapping
"time": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
and I'm querying documents with this filter:
"range": {
"time": {
"gt": "1473157500000",
"lte": "1473158700000",
"format": "epoch_millis"
}
this works and returns documents, but the result show the time field in a different format:
"time": "2016-09-06T10:25:23.678",
Is it possible to force queries to be returned in epoch_millis?
The _source always returns the data in the original document.
Ideally I feel it maybe more desirable and flexible to convert the _source data to the desired format for presentation or otherwise on the client end.
However for the above use case you could use fielddata_fields.
fielddata_fields would return fields in the format of how the field-data is actually stored which in case of date field happens to be epoch_millis.
From documentation:
Allows to return the field data representation of a field for each hit
Field data fields can work on fields that are not stored. It’s
important to understand that using the fielddata_fields parameter will
cause the terms for that field to be loaded to memory (cached), which
will result in more memory consumption.
Example:
post <index>/_search
{
"fielddata_fields": [
"time"
]
}
ES 6.5 onwards, we need to use docvalue_fields in this specific structure, as the fielddata_fields has been deprecated. E.g. let's say we ingested a json doc of the following format:
{
"#version": "1",
"#timestamp": "2019-01-29T10:01:19.217Z",
"host": "C02NTJRMG3QD",
"message": "hello world!"
}
Now let's execute the following get query with docvalue_fields:
curl -X GET \
http://localhost:9200/myindex/_search \
-H 'Content-Type: application/json' \
-d '{
"query": {
"match_all": {}
},
"docvalue_fields": [
{
"field": "#timestamp",
"format": "epoch_millis"
}
]
}'
And, we'll get the following response:
{
"took": 15,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1,
"hits": [
{
"_index": "myindex",
"_type": "doc",
"_id": "mY8OmWgBF3ZItz5TVDiL",
"_score": 1,
"_source": {
"#version": "1",
"#timestamp": "2019-01-29T10:01:19.217Z",
"host": "C02NTJRMG3QD",
"message": "hello world!"
},
"fields": {
"#timestamp": [
"1548756079217"
]
}
}
]
}
}

Nested query not working on Elasticsearch 1.7 if mapping with same name exists

I just downgraded my local ES from 2.1.8 to 1.7.5 to match AWS Elasticsearch and now my nested queries aren't working. I have to admit I'm baffled and couldn't find anything helpful online.
I've abbreviated the following for clarity and changed some of the names but otherwise these are real outputs from my local ES. The final nested result correctly returned file documents with the matching package on 2.1 but nothing on 1.7.
Update: I actually have another nested field that is not exhibiting this problem. The difference is the value for that is a single nested object instead of an array. Known issue?
Update #2: Changing the value to a single value made no difference. However, changing the nested property name from package to packages made the problem go away. The only thing I can think of is that I also have a mapping called package, would that cause a problem?
Mapping
"file": {
"dynamic": "strict",
"_all": {
"enabled": false
},
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string",
"index": "not_analyzed"
},
"package": {
"type": "nested",
"dynamic": "strict",
"properties": {
"name": {
"type": "string",
"index": "not_analyzed"
},
"path": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
Document
Search
{ "query": {"term": {"type": "file"}} }
Result
{
"_index": "blah",
"_type": "file",
"_id": "slkdfjsdfjsoijfoisjfisdjf",
"_score": 7.8872123,
"_source": {
"name": "foo",
"type": "file",
"package": [
{
"name": "the_package",
"path": "the_package!path"
}
]
}
}
Term Vectors
localhost:9200/blah/file/slkdfjsdfjsoijfoisjfisdjf/_termvector?pretty=true&fields=package.name
{
"_index": "blah",
"_type": "file",
"_id": "slkdfjsdfjsoijfoisjfisdjf",
"_version": 1,
"found": true,
"took": 1,
"term_vectors": {
"package.name": {
"field_statistics": {
"sum_doc_freq": 1040,
"doc_count": 1040,
"sum_ttf": 1040
},
"terms": {
"the_package": {
"term_freq": 1,
"tokens": [
{
"position": 0,
"start_offset": 0,
"end_offset": 7
}
]
}
}
}
}
}
Nested Query
{
"query": {
"nested":{
"path": "package",
"query": {
"term": {
"package.name": "the_package"
}
}
}
}
}
Result
{
"took": 8,
"timed_out": false,
"_shards": {
"total": 10,
"successful": 10,
"failed": 0
},
"hits": {
"total": 0,
"max_score": null,
"hits": []
}
}
Following update #2 I tried deleting the package mapping and sure enough the nested query now works as expected. I'll update my mappings to avoid this issue.
Nothing in the ES nested object documentation suggests this should be an issue and it has obviously been fixed between 1.7.5 and 2.1.8 so if anyone knows of such documentation or a link to a fixed bug feel free to add. Posting this as an answer in case anyone else hits this.

How to perform an exact match query on an analyzed field in Elasticsearch?

This is probably a very commonly asked question, however the answers I've got so far isn't satisfactory.
Problem:
I have an es index that is composed of nearly 100 fields. Most of the fields are string type and set as analyzed. However, the query can be both partial (match) or exact (more like term). So, if my index contains a string field with value super duper cool pizza, there can be partial query like duper super and will match with the document, however, there can be exact query like cool pizza which should not match the document. On the other hand, Super Duper COOL PIzza again should match with this document.
So far, the partial match part is easy, I used AND operator in a match query. However can't get the other type done.
I have looked into other posts related to this problem and this post contains the closest solution:
Elasticsearch exact matches on analyzed fields
Out of the three solutions, the first one feels very complex as I have a lot of fields and I do not use the REST api, I am creating queries dynamically using QueryBuilders with NativeSearchQueryBuilder from their Java api. Also it generates a lots of possible patterns which I think will cause performance issues.
The second one is a much easier solution but again, I have to maintain a lot more (almost) redundant data and, I don't think using term queries are ever going to solve my problem.
The last one has a problem I think, it will not prevent super duper to be matched with super duper cool pizza which is not the output I want.
So is there any other way I can achieve the goal? I can post some sample mapping if required for clearing the question farther. I am already keeping the source as well (in case that can be used). Please feel free to suggest any improvements as well.
Thanks in advance.
[UPDATE]
Finally, I used multi_field, keeping a raw field for exact queries. When I insert I use some custom modification on data, and during searching, I used the same modification routines on input text. This part is not handled by Elasticsearch. If you want to do that, you have to design appropriate analyzers as well.
Index settings and mapping queries:
PUT test_index
POST test_index/_close
PUT test_index/_settings
{
"index": {
"analysis": {
"analyzer": {
"standard_uppercase": {
"type": "custom",
"char_filter": ["html_strip"],
"tokenizer": "keyword",
"filter": ["uppercase"]
}
}
}
}
}
PUT test_index/doc/_mapping
{
"doc": {
"properties": {
"text_field": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"analyzer": "standard_uppercase"
}
}
}
}
}
}
POST test_index/_open
Inserting some sample data:
POST test_index/doc/_bulk
{"index":{"_id":1}}
{"text_field":"super duper cool pizza"}
{"index":{"_id":2}}
{"text_field":"some other text"}
{"index":{"_id":3}}
{"text_field":"pizza"}
Exact query:
GET test_index/doc/_search
{
"query": {
"bool": {
"must": {
"bool": {
"should": {
"term": {
"text_field.raw": "PIZZA"
}
}
}
}
}
}
}
Response:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1.4054651,
"hits": [
{
"_index": "test_index",
"_type": "doc",
"_id": "3",
"_score": 1.4054651,
"_source": {
"text_field": "pizza"
}
}
]
}
}
Partial query:
GET test_index/doc/_search
{
"query": {
"bool": {
"must": {
"bool": {
"should": {
"match": {
"text_field": {
"query": "pizza",
"operator": "AND",
"type": "boolean"
}
}
}
}
}
}
}
}
Response:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 1,
"hits": [
{
"_index": "test_index",
"_type": "doc",
"_id": "3",
"_score": 1,
"_source": {
"text_field": "pizza"
}
},
{
"_index": "test_index",
"_type": "doc",
"_id": "1",
"_score": 0.5,
"_source": {
"text_field": "super duper cool pizza"
}
}
]
}
}
PS: These are generated queries, that's why there are some redundant blocks, as there would be many other fields concatenated into the queries.
Sad part is, now I need to rewrite the whole mapping again :(
I think this will do what you want (or at least come as close as is possible), using the keyword tokenizer and lowercase token filter:
PUT /test_index
{
"settings": {
"analysis": {
"analyzer": {
"lowercase_analyzer": {
"type": "custom",
"tokenizer": "keyword",
"filter": ["lowercase_token_filter"]
}
},
"filter": {
"lowercase_token_filter": {
"type": "lowercase"
}
}
}
},
"mappings": {
"doc": {
"properties": {
"text_field": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
},
"lowercase": {
"type": "string",
"analyzer": "lowercase_analyzer"
}
}
}
}
}
}
}
I added a couple of docs for testing:
POST /test_index/doc/_bulk
{"index":{"_id":1}}
{"text_field":"super duper cool pizza"}
{"index":{"_id":2}}
{"text_field":"some other text"}
{"index":{"_id":3}}
{"text_field":"pizza"}
Notice we have the outer text_field set to be analyzed by the standard analyzer, then a sub-field raw that's not_analyzed (you may not want this one, I just added it for comparison), and another sub-field lowercase that creates tokens exactly the same as the input text, except that they have been lowercased (but not split on whitespace). So this match query returns what you expected:
POST /test_index/_search
{
"query": {
"match": {
"text_field.lowercase": "Super Duper COOL PIzza"
}
}
}
...
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.30685282,
"hits": [
{
"_index": "test_index",
"_type": "doc",
"_id": "1",
"_score": 0.30685282,
"_source": {
"text_field": "super duper cool pizza"
}
}
]
}
}
Remember that the match query will use the field's analyzer against the search phrase as well, so in this case searching for "super duper cool pizza" would have exactly the same effect as searching for "Super Duper COOL PIzza" (you could still use a term query if you want an exact match).
It's useful to take a look at the terms generated in each field by the three documents, since this is what your search queries will be working against (in this case raw and lowercase have the same tokens, but that's only because all the inputs were lower-case already):
POST /test_index/_search
{
"size": 0,
"aggs": {
"text_field_standard": {
"terms": {
"field": "text_field"
}
},
"text_field_raw": {
"terms": {
"field": "text_field.raw"
}
},
"text_field_lowercase": {
"terms": {
"field": "text_field.lowercase"
}
}
}
}
...{
"took": 26,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 0,
"hits": []
},
"aggregations": {
"text_field_raw": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "pizza",
"doc_count": 1
},
{
"key": "some other text",
"doc_count": 1
},
{
"key": "super duper cool pizza",
"doc_count": 1
}
]
},
"text_field_lowercase": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "pizza",
"doc_count": 1
},
{
"key": "some other text",
"doc_count": 1
},
{
"key": "super duper cool pizza",
"doc_count": 1
}
]
},
"text_field_standard": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "pizza",
"doc_count": 2
},
{
"key": "cool",
"doc_count": 1
},
{
"key": "duper",
"doc_count": 1
},
{
"key": "other",
"doc_count": 1
},
{
"key": "some",
"doc_count": 1
},
{
"key": "super",
"doc_count": 1
},
{
"key": "text",
"doc_count": 1
}
]
}
}
}
Here's the code I used to test this out:
http://sense.qbox.io/gist/cc7564464cec88dd7f9e6d9d7cfccca2f564fde1
If you also want to do partial word matching, I would encourage you to take a look at ngrams. I wrote up an introduction for Qbox here:
https://qbox.io/blog/an-introduction-to-ngrams-in-elasticsearch

ElasticSearch: Attempting to get spelling suggestion on proper names

Before I begin, let me just say that I'm no ElasticSearch expert, but I am currently tasked with tweaking some analyzers to get spelling suggestions working better in a couple of different situations. I've seen examples of people who are doing spelling suggestions on proper names, so I know it must be possible, but I've been at this for a couple days now, and I must be missing something, because ElasticSearch doesn't seem to recognize the name I'm looking for. Can you please help me figure this out? Thanks in advance!
Here's the analyzer I'm using for index as well as search:
"full_text": {
"filter": [
"lowercase",
"asciifolding",
],
"type": "custom",
"tokenizer": "keyword"
},
This should demonstrate that the field is tokenizing into one long keyword, which I want.
{
"query": {
"match": {
"_all": "combine 5"
}
},
"script_fields": {
"terms" : {
"script": "doc[field].values",
"params": {
"field": "my_field"
}
}
}
}
...and it outputs something like this, which shows how the field is being tokenized. Looks good:
"took": 7,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 75,
"max_score": 0.58574116,
"hits": [
{
"_index": "my_index",
"_type": "thing",
"_id": "1",
"_score": 0.58574116,
"fields": {
"terms": [
[
"combine 5"
]
]
}
}
}
}
... but when I do a suggest query, it doesn't suggest the field, even though it's just off by a space.
{
"query": {
"match": {
"_all": "combine 5"
}
},
"suggest": {
"suggest-0": {
"term": {
"field": "_all",
"size": 5
},
"text": "combine5"
}
}
}
Which returns a bunch of documents and this suggestion:
"suggest": {
"suggest-0": [
{
"text": "combine5",
"offset": 0,
"length": 8,
"options": [
{
"text": "combined",
"score": 0.875,
"freq": 15
},
{
"text": "combine",
"score": 0.85714287,
"freq": 17
}
]
}
]
}
Note that if I change the spelling suggestion to work just on the field that contains the text, it does suggest it, but not when I'm using _all. Is there a way to get the words in a specific field to be suggested when suggesting against _all?
I'm not sure this qualifies as exactly the answer I was looking for, but I ended up solving this by adding a field on the document containing the keyword value that I was looking for "combine5", so now it is registered as a word and if I suggest on that field, or _all, the word is suggested. It's also found in queries against _all.

Resources