通过在另一个索引

时间:2016-10-25 21:13:06

标签: elasticsearch time-series

数据

所以我有大量不同类型的时间序列数据。目前我已经选择将每种类型的数据放入他们自己的索引中,因为除4个字段外,所有数据都非常不同。此外,数据以不同的速率进行采样,并且不保证在同一个亚秒级窗口中具有共同的时间戳,因此将它们全部融合到一个大型文档中也不是一项简单的任务。

目标

我试图查看是否可以在Elasticsearch中完全解决的一个常见用例是根据从另一个索引的查询返回的时间窗口返回一个索引的聚合结果。图示地:

What i'd like to accomplish

这就是我想要完成的。

一些注意事项

对于"条件的足够小的信号转换"数据,我可以使用日期直方图和顶部命中子聚合的某种组合,但是当我有10,000或者#100,000;"条件&#时,这很快就会崩溃34 ;.此外,这只是一个" case",我有100套类似的情况,我希望从中得到总体最小值/最大值。

比较基本上属于我认为的兄弟级别的文档或索引,所以似乎没有任何明显的父母 - >儿童关系足够灵活从长远来看,至少与数据目前的结构有关。

感觉应该有一个优雅的解决方案,而不是强力建立Elasticsearch之外的日期范围与一个查询的结果,并将100个时间范围提供给另一个查询。

通过文档查看,感觉Elasticsearch脚本和一些流水线聚合的某种组合将成为我想要的,但没有明确的解决方案向我跳出来。我真的可以在社区的正确方向上使用一些指针。

感谢。

1 个答案:

答案 0 :(得分:3)

我找到了一个解决这个问题的“解决方案”。没有任何答案甚至评论,但我会发布我的解决方案,以防其他人出现寻找这样的事情。我确信有很多改进和优化的机会,如果我发现这样的解决方案(可能通过脚本聚合),我会回来更新我的解决方案。

它可能不是最佳解决方案,但它适用于我。关键是要利用top_hitsserial_diffbucket_selector聚合器。

“解决方案”

def time_edges(index, must_terms=[], should_terms=[], filter_terms=[], data_sample_accuracy_window=200):
    """
    Find the affected flights and date ranges where a specific set of terms occurs in a particular ES index.

    index: the Elasticsearch index to search
    terms: a list of dictionaries of form { "term": { "<termname>": <value>}}
    """
    query = {
        "size": 0,
        "timeout": "5s",
        "query": {
            "constant_score": {
                "filter": {
                    "bool": {
                        "must": must_terms,
                        "should": should_terms,
                        "filter": filter_terms
                    }
                }
            }
        },
        "aggs": {
            "by_flight_id": {
                "terms": {"field": "flight_id", "size": 1000},
                "aggs": {
                    "last": {
                        "top_hits": {
                            "sort": [{"@timestamp": {"order": "desc"}}],
                            "size": 1,
                            "script_fields": {
                                "timestamp": {
                                    "script": "doc['@timestamp'].value"
                                }
                            }
                        }
                    },
                    "first": {
                        "top_hits": {
                            "sort": [{"@timestamp": {"order": "asc"}}],
                            "size": 1,
                            "script_fields": {
                                "timestamp": {
                                    "script": "doc['@timestamp'].value"
                                }
                            }
                        }
                    },
                    "time_edges": {
                        "histogram": {
                            "min_doc_count": 1,
                            "interval": 1,
                            "script": {
                                "inline": "doc['@timestamp'].value",
                                "lang": "painless",
                            }
                        },
                        "aggs": {
                            "timestamps": {
                                "max": {"field": "@timestamp"}
                            },
                            "timestamp_diff": {
                                "serial_diff": {
                                    "buckets_path": "timestamps",
                                    "lag": 1
                                }
                            },
                            "time_delta_filter": {
                                "bucket_selector": {
                                    "buckets_path": {
                                        "timestampDiff": "timestamp_diff"
                                    },
                                    "script": "if (params != null && params.timestampDiff != null) { params.timestampDiff > " + str(data_sample_accuracy_window) + "} else { false }"
                                }
                            }
                        }
                    }
                }
            }

        }
    }

    return es.search(index=index, body=query)

打破局面

按“索引2”

过滤结果

the condition

    "query": {
        "constant_score": {
            "filter": {
                "bool": {
                    "must": must_terms,
                    "should": should_terms,
                    "filter": filter_terms
                }
            }
        }
    },

must_terms是获取“索引2”中存储的“条件”的所有结果所需的值。

例如,要将结果限制为仅过去10天,当condition为10或12时,我们会添加以下must_terms

must_terms = [
    {
        "range": {
            "@timestamp": {
                "gte": "now-10d",
                "lte": "now"
            }
        }
    },
    {
        "terms": {"condition": [10, 12]}
    }
]

这将返回一组简化的文档,然后我们可以将这些文档传递到我们的聚合中,以找出我们的“样本”所在的位置。

聚合

对于我的用例,我们对我们的飞机有“飞行”的概念,所以我想按照id对返回的结果进行分组,然后将所有出现的“分解”成桶。

    "aggs": {
        "by_flight_id": {
            "terms": {"field": "flight_id", "size": 1000},


            ...


            }
        }

    }

使用top_hits聚合

可以获得第一次出现的上升沿和最后一次出现的下降沿

top_hits

    "last": {
        "top_hits": {
            "sort": [{"@timestamp": {"order": "desc"}}],
            "size": 1,
            "script_fields": {
                "timestamp": {
                    "script": "doc['@timestamp'].value"
                }
            }
        }
    },
    "first": {
        "top_hits": {
            "sort": [{"@timestamp": {"order": "asc"}}],
            "size": 1,
            "script_fields": {
                "timestamp": {
                    "script": "doc['@timestamp'].value"
                }
            }
        }
    },

您可以使用时间戳上的直方图获取两者之间的样本。这会将返回的结果分解为每个唯一时间戳的存储桶。这是一个代价高昂的聚合,但值得。使用内联脚本允许我们使用存储桶名称的时间戳值。

time_edges

    "time_edges": {
        "histogram": {
            "min_doc_count": 1,
            "interval": 1,
            "script": {
                "inline": "doc['@timestamp'].value",
                "lang": "painless",
            }
        },

        ...

    }

默认情况下,直方图聚合会返回一组包含每个存储桶的文档计数的存储桶,但我们需要value。这是serial_diff聚合工作所需的,因此我们必须对结果进行标记max聚合以获得返回值。

    "aggs": {
        "timestamps": {
            "max": {"field": "@timestamp"}
        },
        "timestamp_diff": {
            "serial_diff": {
                "buckets_path": "timestamps",
                "lag": 1
            }
        },

        ...

    }

我们使用serial_diff的结果来确定两个桶是否大致相邻。然后,我们丢弃彼此相邻的样本,并使用bucket_selector聚合为我们的条件创建组合时间范围。这将丢弃小于data_sample_accuracy_window的存储桶。该值取决于您的数据集。

    "aggs": {

        ...

        "time_delta_filter": {
            "bucket_selector": {
                "buckets_path": {
                    "timestampDiff": "timestamp_diff"
                },
                "script": "if (params != null && params.timestampDiff != null) { params.timestampDiff > " + str(data_sample_accuracy_window) + "} else { false }"
            }
        }
    }

serial_diff结果对于我们确定condition的设置时间也至关重要。我们的桶的时间戳最终代表条件信号的“上升”边缘,因此没有一些后处理就会知道下降沿。我们使用timestampDiff值来确定下降沿的位置。