Apache Druid的JSON查询使用手册

JSON查询

概述

JSON查询是Druid内置的本机查询,本机查询级别较低,与内部计算的执行方式密切相关。json查询被设计为轻量级且非常快速地完成。这意味着,对于更复杂的分析或构建更复杂的可视化,需要json查询。

一、时间序列查询(Timeseries)

这些类型的查询采用时间序列查询对象,并返回JSON对象数组,其中每个对象代表时间序列查询所要求的值。

时间序列查询对象示例如下所示:

{
"queryType": "timeseries",
"dataSource": "sample_datasource",
"granularity": "day",
"descending": "true",
"filter": {
    "type": "and",
    "fields": [
    { "type": "selector", "dimension": "sample_dimension1", "value": "sample_value1" },
    { "type": "or",
        "fields": [
        { "type": "selector", "dimension": "sample_dimension2", "value": "sample_value2" },
        { "type": "selector", "dimension": "sample_dimension3", "value": "sample_value3" }
        ]
    }
    ]
},
"aggregations": [
    { "type": "longSum", "name": "sample_name1", "fieldName": "sample_fieldName1" },
    { "type": "doubleSum", "name": "sample_name2", "fieldName": "sample_fieldName2" }
],
"postAggregations": [
    { "type": "arithmetic",
    "name": "sample_divide",
    "fn": "/",
    "fields": [
        { "type": "fieldAccess", "name": "postAgg__sample_name1", "fieldName": "sample_name1" },
        { "type": "fieldAccess", "name": "postAgg__sample_name2", "fieldName": "sample_name2" }
    ]
    }
],
"intervals": [ "2012-01-01T00:00:00.000/2012-01-03T00:00:00.000" ]
}
属性 描述 必须
queryType 设置查询类型为时间序列 timeseries
dataSource 数据源
descending 是否进行降序排序。默认值为false(升序)。
intervals 定义查询时间范围。
granularity 定义查询结果的粒度
filter 过滤器
aggregations 聚合
postAggregations 后聚合
limit 限制结果数量。默认值为无限制。
context 上下文,可用于修改查询行为,包括总计和零填充

上面的查询将从“ sample_datasource”表中返回2个数据点,从2012年1月1日到2012年1月3日之间每天返回一个数据点。每个数据点将是sample_fieldName1的(long)总和,sample_fieldName2的(double)总和和sample_fieldName1的(double)结果除以过滤器集的sample_fieldName2

[
{
    "timestamp": "2012-01-01T00:00:00.000Z",
    "result": { "sample_name1": <some_value>, "sample_name2": <some_value>, "sample_divide": <some_value> }
},
{
    "timestamp": "2012-01-02T00:00:00.000Z",
    "result": { "sample_name1": <some_value>, "sample_name2": <some_value>, "sample_divide": <some_value> }
}
]

Druid可以在时间序列结果集的最后一行包含额外的“总计”行。要启用此功能,请添加 "grandTotal":true 到您的查询context中。总计行将在结果数组中显示为最后一行。即使查询以“降序”模式运行,也将是最后一行。总计行中的后聚合将基于总计汇总进行计算。

{
"queryType": "timeseries",
"dataSource": "sample_datasource",
"intervals": [ "2012-01-01T00:00:00.000/2012-01-03T00:00:00.000" ],
"granularity": "day",
"aggregations": [
    { "type": "longSum", "name": "sample_name1", "fieldName": "sample_fieldName1" },
    { "type": "doubleSum", "name": "sample_name2", "fieldName": "sample_fieldName2" }
],
"context": {
    "grandTotal": true
}
}

时间序列查询通常用零填充空的结果段。如,如果您对间隔2012-01-01/2012-01-04发送“day”粒度时间序列查询,而2012-01-02没有数据,您将收到:

[
{
    "timestamp": "2012-01-01T00:00:00.000Z",
    "result": { "sample_name1": <some_value> }
},
{
"timestamp": "2012-01-02T00:00:00.000Z",
"result": { "sample_name1": 0 }
},
{
    "timestamp": "2012-01-03T00:00:00.000Z",
    "result": { "sample_name1": <some_value> }
}
]

完全位于数据间隔之外的时间段不会填充零。

您可以使用context标记"skipEmptyBuckets": "true"跳过空结果填充。在这种模式下,结果中将省略2012-01-02的数据点。

{
"queryType": "timeseries",
"dataSource": "sample_datasource",
"granularity": "day",
"aggregations": [
    { "type": "longSum", "name": "sample_name1", "fieldName": "sample_fieldName1" }
],
"intervals": [ "2012-01-01T00:00:00.000/2012-01-04T00:00:00.000" ],
"context" : {
    "skipEmptyBuckets": "true"
}
}

二、TopN查询(TopN)

Druid TopN查询根据某些条件返回的结果集中进行排序的结果。从理论上讲,可以将它们视为具有Ordering规范的单个维度上的近似GroupByQuery查询。在此用例中,TopN比GroupBy更快,资源效率更高。这些类型的查询采用topN查询对象,并返回JSON对象数组,其中每个对象代表topN查询所要求查询条件。

TopN是近似值,因为每个数据集处理时,将对它们的前K个结果进行排名,并且仅将这些前K个结果返回给Broker。K在数据库中默认为 max(1000, threshold)。实际上,这意味着如果您要求返回前1000个结果,则前900个结果的正确性将为100%,后续的结果则不能保证100%的正确和有序性,所以通过更改max的设置可以使TopN更加准确。

{
"queryType": "topN",
"dataSource": "sample_data",
"dimension": "sample_dim",
"threshold": 5,
"metric": "count",
"granularity": "all",
"filter": {
    "type": "and",
    "fields": [
    {
        "type": "selector",
        "dimension": "dim1",
        "value": "some_value"
    },
    {
        "type": "selector",
        "dimension": "dim2",
        "value": "some_other_val"
    }
    ]
},
"aggregations": [
    {
    "type": "longSum",
    "name": "count",
    "fieldName": "count"
    },
    {
    "type": "doubleSum",
    "name": "some_metric",
    "fieldName": "some_metric"
    }
],
"postAggregations": [
    {
    "type": "arithmetic",
    "name": "average",
    "fn": "/",
    "fields": [
        {
        "type": "fieldAccess",
        "name": "some_metric",
        "fieldName": "some_metric"
        },
        {
        "type": "fieldAccess",
        "name": "count",
        "fieldName": "count"
        }
    ]
    }
],
"intervals": [
    "2013-08-31T00:00:00.000/2013-09-03T00:00:00.000"
]
}
属性 描述 必要
queryType 设置查询类型为时间序列 topN
dataSource 数据源
intervals 定义查询时间范围。
granularity 定义查询结果的粒度
filter 过滤器
aggregations 聚合
postAggregations 后聚合
dimension 一个String或JSON对象,定义使用的类型
threshold 定义TopN中N的值
metric 一个String或JSON对象,它指定要排序的指标
context 上下文
[
{
    "timestamp": "2013-08-31T00:00:00.000Z",
    "result": [
    {
        "dim1": "dim1_val",
        "count": 111,
        "some_metrics": 10669,
        "average": 96.11711711711712
    },
    {
        "dim1": "another_dim1_val",
        "count": 88,
        "some_metrics": 28344,
        "average": 322.09090909090907
    },
    {
        "dim1": "dim1_val3",
        "count": 70,
        "some_metrics": 871,
        "average": 12.442857142857143
    },
    {
        "dim1": "dim1_val4",
        "count": 62,
        "some_metrics": 815,
        "average": 13.14516129032258
    },
    {
        "dim1": "dim1_val5",
        "count": 60,
        "some_metrics": 2787,
        "average": 46.45
    }
    ]
}
]

多值维度

topN查询可以对多值维度进行分组。在对多值维度进行分组时,匹配行中的所有值将用于为每个值生成一组。查询返回的组可能比行多。例如,一个TopN的字段 tags 经过过滤器 "t1" AND "t3" 将只有ROW1一个匹配,最终与三个组生成结果:t1,t2,和t3。如果只需要包含与过滤器匹配的值,则可以使用过滤的 DimensionSpec属性设置可以少返回一些结果,这也可以提高性能。

混叠

  • 当前的TopN算法是一种近似算法。返回每个段的前1000个局部结果合并以确定全局topN。这样,topN算法在等级和结果上都是近似的。近似结果仅适用于超过1000个结果值的情况。少于1000个唯一维度值的维度上的topN可以被认为是排名准确且集合体准确。

  • 阈值可以通过服务器参数默认值1000进行修改,该参数 Druid.query.topN.minTopNThreshold 需要重新启动服务器才能生效,或者在查询上下文中设置(对每个查询生效)minTopNThreshold。

  • 如果您希望TopN的N数,则均匀分布的维度前100个按一些low-cardinality,均匀分布的维度排序,则可能会找回丢失数据的聚合。

  • 换句话说,topN的最佳用例是您可以确信总体结果始终位于顶部。例如,如果某个特定的站点ID在每天的每个小时中均处于某个指标的前10名中,则它在几天之内在前N名中可能很准确。但是,如果某个站点在任何给定的小时内几乎都没有进入前1000名,但在整个查询粒度中都排名前500名(例如:一个站点在数据集中与具有高度周期性数据的站点混合在一起获得高度统一的流量),则top500查询可能没有使该特定网站具有确切的排名,并且对于该特定网站的汇总可能并不准确。

  • 在使用TopN之前,请考虑是否确实需要确切的结果。获得准确的结果是非常耗费资源的过程。对于绝大多数结果集来说,近似topN算法可提供足够的准确性。

  • 希望获得具有大于1000个唯一值的维度上,要求精确排名和精确聚合时,应发出groupBy查询并自行对结果进行排序。

  • 可以容忍具有大于1000个唯一值的维度上,允许近似结果集时,但需要精确的集合,用户可以发出两个查询,一个用于获取近似topN的N个值,另一个用于N个值使用选择过滤器的topN,该过滤器仅使用第一个的topN结果。

  • 示例一:

    {
        "aggregations": [
            {
                "fieldName": "L_QUANTITY_longSum",
                "name": "L_QUANTITY_",
                "type": "longSum"
            }
        ],
        "dataSource": "tpch_year",
        "dimension":"l_orderkey",
        "granularity": "all",
        "intervals": [
            "1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z"
        ],
        "metric": "L_QUANTITY_",
        "queryType": "topN",
        "threshold": 2
    }
    
  • 示例二:

    {
        "aggregations": [
            {
                "fieldName": "L_TAX_doubleSum",
                "name": "L_TAX_",
                "type": "doubleSum"
            },
            {
                "fieldName": "L_DISCOUNT_doubleSum",
                "name": "L_DISCOUNT_",
                "type": "doubleSum"
            },
            {
                "fieldName": "L_EXTENDEDPRICE_doubleSum",
                "name": "L_EXTENDEDPRICE_",
                "type": "doubleSum"
            },
            {
                "fieldName": "L_QUANTITY_longSum",
                "name": "L_QUANTITY_",
                "type": "longSum"
            },
            {
                "name": "count",
                "type": "count"
            }
        ],
        "dataSource": "tpch_year",
        "dimension":"l_orderkey",
        "filter": {
            "fields": [
                {
                    "dimension": "l_orderkey",
                    "type": "selector",
                    "value": "103136"
                },
                {
                    "dimension": "l_orderkey",
                    "type": "selector",
                    "value": "1648672"
                }
            ],
            "type": "or"
        },
        "granularity": "all",
        "intervals": [
            "1900-01-09T00:00:00.000Z/2992-01-10T00:00:00.000Z"
        ],
        "metric": "L_QUANTITY_",
        "queryType": "topN",
        "threshold": 2
    }
    

三、分组查询(GroupBy)

Druid查询采用groupBy查询对象,并返回JSON对象数组,其中每个对象代表查询所要求的分组条件。

注意:如果要使用时间作为唯一分组进行聚合,或者在单个维度上使用有序的groupBy进行聚合,请考虑使用Timeseries和TopN查询以及groupBy。在某些情况下,它们的性能可能会更好。

{
"queryType": "groupBy",
"dataSource": "sample_datasource",
"granularity": "day",
"dimensions": ["country", "device"],
"limitSpec": { "type": "default", "limit": 5000, "columns": ["country", "data_transfer"] },
"filter": {
    "type": "and",
    "fields": [
    { "type": "selector", "dimension": "carrier", "value": "AT&T" },
    { "type": "or",
        "fields": [
        { "type": "selector", "dimension": "make", "value": "Apple" },
        { "type": "selector", "dimension": "make", "value": "Samsung" }
        ]
    }
    ]
},
"aggregations": [
    { "type": "longSum", "name": "total_usage", "fieldName": "user_count" },
    { "type": "doubleSum", "name": "data_transfer", "fieldName": "data_transfer" }
],
"postAggregations": [
    { "type": "arithmetic",
    "name": "avg_usage",
    "fn": "/",
    "fields": [
        { "type": "fieldAccess", "fieldName": "data_transfer" },
        { "type": "fieldAccess", "fieldName": "total_usage" }
    ]
    }
],
"intervals": [ "2012-01-01T00:00:00.000/2012-01-03T00:00:00.000" ],
"having": {
    "type": "greaterThan",
    "aggregation": "total_usage",
    "value": 100
}
}
属性 描述 必要
queryType 设置查询类型为时间序列 groupBy
dataSource 数据源
dimensions 一个用于进行分组的维度的JSON列表
limitSpec 请参limitSpec具体用法
having 请参having具体用法
granularity 定义查询结果的粒度
filter 过滤器
aggregations 聚合
postAggregations 后聚合
intervals 定义查询时间范围。
subtotalsSpec 一个JSON数组数组,用于返回其他结果集,以对顶级子集进行分组dimensions
context 上下文

上面的查询将返回n * m个数据点,最多可返回5000个点,其中n是country维的基数,m是device维的基数。device维介于sample_datasource表的2012-01-01到2012-01-03之间的每一天,如果数据点的值大于100,那么每个数据点都包含total_usage的(long)和、data_transfer的(double)和,total_usage除以data_transfer针对的特定分组,过滤器集country和device

[
{
    "version" : "v1",
    "timestamp" : "2012-01-01T00:00:00.000Z",
    "event" : {
    "country" : <some_dim_value_one>,
    "device" : <some_dim_value_two>,
    "total_usage" : <some_value_one>,
    "data_transfer" :<some_value_two>,
    "avg_usage" : <some_avg_usage_value>
    }
},
{
    "version" : "v1",
    "timestamp" : "2012-01-01T00:00:12.000Z",
    "event" : {
    "dim1" : <some_other_dim_value_one>,
    "dim2" : <some_other_dim_value_two>,
    "sample_name1" : <some_other_value_one>,
    "sample_name2" :<some_other_value_two>,
    "avg_usage" : <some_other_avg_usage_value>
    }
},
...
]

多值维度

groupBy查询可以对多值维度进行分组。在对多值维度进行分组时,匹配行中的所有值将用于为每个值生成一组。查询返回的组可能比行多。例如,在查询结果的GROUPBY tags与过滤器"t1" AND "t3"将只ROW1最终匹配,但是与三个组生成结果:t1,t2,和t3。如果只需要包含与过滤器匹配的值,则可以使用过滤的DimensionSpec减少返回的结果数量,这也可以提高性能。

小计功能信息

小计功能允许在单个查询中计算多个子组。若要使用此功能,请在查询中添加一个“subtotalsSpec”属性,该列表应该是子组维度集的列表。它应在“维度”属性中包含维度中的“outputName”,其顺序与在“维度”属性中出现的顺序相同

{
"type": "groupBy",
 ...
 ...
"dimensions": [
  {
  "type" : "default",
  "dimension" : "d1col",
  "outputName": "D1"
  },
  {
  "type" : "extraction",
  "dimension" : "d2col",
  "outputName" :  "D2",
  "extractionFn" : extraction_func
  },
  {
  "type":"lookup",
  "dimension":"d3col",
  "outputName":"D3",
  "name":"my_lookup"
  }
],
...
...
"subtotalsSpec":[ ["D1", "D2", D3"], ["D1", "D3"], ["D3"]],
..

}

返回的结果等同于将3个groupBy查询的结果与 DimensionSpec 的json blob 连接在一起,其中“dimensions”字段为[“ D1”,“ D2”,D3“],[” D1“,” D3“]和[” D3“] 就像上面的查询中使用的一样。

[
  {
    "version" : "v1",
    "timestamp" : "t1",
    "event" : { "D1": "..", "D2": "..", "D3": ".." }
    }
  },
    {
    "version" : "v1",
    "timestamp" : "t2",
    "event" : { "D1": "..", "D2": "..", "D3": ".." }
    }
  },
  ...
  ...

   {
    "version" : "v1",
    "timestamp" : "t1",
    "event" : { "D1": "..", "D3": ".." }
    }
  },
    {
    "version" : "v1",
    "timestamp" : "t2",
    "event" : { "D1": "..", "D3": ".." }
    }
  },
  ...
  ...

  {
    "version" : "v1",
    "timestamp" : "t1",
    "event" : { "D3": ".." }
    }
  },
    {
    "version" : "v1",
    "timestamp" : "t2",
    "event" : { "D3": ".." }
    }
  },
...
]

[1] 实现细节

策略

可以使用两种不同的策略来执行GroupBy查询。集群的默认策略由Broker上的“ Druid.query.groupBy.defaultStrategy”运行时属性确定。可以在查询上下文中使用“groupByStrategy”来覆盖它。如果既未设置上下文字段也未设置属性,则将使用"v2"策略。

  • 默认情况下,"v2"旨在提供更好的性能和内存管理。该策略的使用,完全是堆外映射生成每个段的结果。数据处理使用的是,完全堆外并发实时映射和堆上字符串字典合并成每个段结果。这可能会涉及磁盘溢出。数据进程将排序后的结果返回给borker,borker使用N路合并来合并结果流。如有必要,borker将具体化结果(例如,如果查询按其维度以外的列进行排序)。否则,它会在合并结果时将结果流回。

  • 传统策略"v1"使用部分堆(维度键和地图本身)和部分堆(合计值)的映射在数据流程(历史记录,实时,MiddleManager)上生成每段结果。然后,数据处理使用Druid的索引机制合并每个段的结果。默认情况下,此合并是多线程的,但也可以选择是单线程的。borker再次使用Druid的索引机制合并最终结果集。borker合并始终是单线程的。因为Broker使用索引机制合并结果,所以它必须在返回任何结果之前实现完整的结果集。在数据进程和borker上,默认情况下,合并索引完全处于堆中状态,但是可以选择将聚集值存储在堆外。

v1和v2之间的区别

查询API和结果,在两个策略之间是兼容的;但是,从集群配置角度来看,存在一些差异:

  • groupBy v1使用基于行的限制(maxResults)控制资源使用,而groupBy v2使用基于字节的限制。另外,groupBy v1合并结果在堆上,而groupBy v2合并结果在堆外。这些因素意味着内存调整和资源限制在v1和v2之间表现不同。尤其是由于这个原因,某些可以在一个引擎中成功完成的查询可能会超出资源限制。

  • groupBy v1对并发运行的查询数量没有限制,而groupBy v2通过使用有限大小的合并缓冲池来控制内存使用。默认情况下,合并缓冲区的数量是处理线程数量的1/4。您可以根据需要进行调整,以平衡并发和内存使用。

  • groupBy v1支持在Broker或Historical进程上进行缓存,而groupBy v2仅支持在Historical进程上进行缓存。

  • groupBy v1支持使用chunkPeriod并行化执行Broker上的合并,而groupBy v2则忽略chunkPeriod。

  • groupBy v2支持基于数组的聚合和基于哈希的聚合。仅当分组键是单个索引字符串列时才使用基于数组的聚合。在基于数组的聚合中,将字典编码的值用作索引,因此可以直接访问数组中的聚合值,而无需基于哈希查找存储。

[2] 内存调整和资源限制

使用groupBy v2策略时,三个参数控制资源的使用和限制:

  • Druid.processing.buffer.sizeBytes:每个查询用于聚合的堆外哈希表的大小,以字节为单位。最多的Druid.processing.numMergeBuffers将立即创建其中的一个,这也作为并发运行的groupBy查询数量的上限。

  • Druid.query.groupBy.maxMergingDictionarySize:堆查询时按每个查询,对字符串进行分组的字典的大小(以字节为单位)。请注意,这是基于对字典大小的粗略估算,而不是实际大小。

  • Druid.query.groupBy.maxOnDiskStorage:磁盘上用于聚合的空间量,每个查询,以字节为单位。默认情况下,该值为0,这意味着聚合将不使用磁盘。

    如果maxOnDiskStorage为0(默认值),则超过堆上字典大小的限制或堆外聚合表的限制,则查询失败,并显示“超出资源限制”错误,描述已超过的限制。

    如果maxOnDiskStorage大于0,则超出内存限制的查询将开始使用磁盘进行聚合。在这种情况下,当堆上字典大小或堆外哈希表被填满时,将对部分聚合的记录进行排序并将其刷新到磁盘。然后,将清除两个内存结构以进行进一步聚合。查询超过maxOnDiskStorage设置的将失败,并显示“超出资源限制”错误,表明它们已用完磁盘空间。

    使用groupBy v2策略,集群操作者应确保对于最大可能的并发查询负载(由提供Druid.processing.numMergeBuffers),堆外哈希表和堆上合并字典不会超出可用内存 。

    Broker不需要基本groupBy查询的合并缓冲区。query如果只有一个子查询,则带有子查询的查询(使用dataSource)需要一个合并缓冲区,如果有一层以上的嵌套子查询,则需要两个合并缓冲区。具有小计的查询需要一个合并缓冲区。它们可以彼此叠加:一个具有多层嵌套子查询层的groupBy查询(也使用小计)将需要三个合并缓冲区。

    Historicals和提取任务的每个groupBy查询都需要一个合并缓冲区,除非启用了并行组合,在这种情况下,每个查询都需要两个合并缓冲区。

    使用groupBy v1策略时,所有聚合都在堆上完成,而资源限制则通过参数Druid.query.groupBy.maxResults设置。这是结果集中最大结果数的上限。超过此限制的查询将失败,并显示“超出资源限制”错误,表明它们超过了行限制。集群操作者应确保对于预期的并发查询负载,堆上聚合不会超出可用的JVM堆空间。

[3] groupBy v2策略的性能调优

  • 限制叠加优化

    Druid limit尽可能将groupBy查询中的规范下推到Historicals上进行细分,以尽早删减不必要的中间结果,并最大程度地减少传输给Brokers的数据量。默认情况下,仅当orderBy规范中的所有字段都是分组键的子集时才应用此技术。这是因为,limitPushDown如果orderBy规范中包含不在分组键中的任何字段,则不能保证准确的结果。如果您接受诸如topN查询中的快速查询处理的准确性时,也可以启用此技术。

  • 优化哈希表

    groupBy v2策略使用开放式地址哈希表进行汇总。哈希表使用给定的初始存储桶编号进行初始化,并在缓冲区已满时逐渐增长。在哈希冲突中,使用线性探测技术。

    初始存储桶的默认大小为1024,哈希表的默认最大负载因子为0.7。如果您在哈希表中看到太多冲突,则可以调整这些数字。bufferGrouperInitialBuckets和bufferGrouperMaxLoadFactor在高级GROUPBY V2策略配置中有相关说明。

  • 平行组合

    Historicals使用哈希表完成聚合后,将对聚合结果进行排序和合并,然后再将其发送到Broker中以进行Broker中的N路合并聚合。默认情况下,Historicals使用所有可用的处理线程(由配置Druid.processing.numThreads)进行聚合,但使用单个线程对聚合进行排序和合并,这是将线程发送到Broker的http线程。

    这是为了防止某些繁重的groupBy查询阻止其他查询。在Druid中,处理线程在所有提交的查询之间共享,并且它们不可中断。这意味着,如果繁重的查询占用了所有可用的处理线程,则所有其他查询都可能被阻塞,直到繁重的查询完成为止。GroupBy查询通常比时间序列查询或topN查询花费更多的时间,因此应尽快释放处理线程。

    但是,您可能会关心一些非常繁琐的groupBy查询的性能。通常,繁重的groupBy查询的性能瓶颈是合并排序的聚合。在这种情况下,您也可以使用处理线程。这称为并行合并。要启用并行合并,请参阅numParallelCombineThreads“ 高级groupBy v2策略配置”。请注意,只有在实际溢出数据时才能启用并行组合(请参阅内存调整和资源限制)。

    启用并行合并后,groupBy v2策略可以创建合并树以合并排序的聚合。树的每个中间节点都是一个线程,用于合并子节点的聚合。叶节点线程从哈希表(包括溢出表)读取和合并聚合。通常,叶进程比中间节点慢,因为它们需要从磁盘读取数据。结果,默认情况下,较少的线程用于中间节点。您可以更改中间节点的程度。

    请注意,每个Historicals都需要两个合并缓冲区来通过并行合并处理groupBy v2策略查询:一个用于计算每个段的中间聚合,另一个用于并行合并中间聚合。

[4] 备选方案

在某些情况下,其他查询类型可能比groupBy更好。

对于没有“维度”的查询(即仅按时间分组),时间序列查询通常比groupBy更快。主要区别在于,它以完全流式方式实现(利用段已按时间排序的事实),并且不需要使用哈希表进行合并。

对于具有单个“维度”元素(即按一个字符串维度分组)的查询,TopN查询 有时会比groupBy更快。

[5] 内置的groupby

对于"v1"和"v2",内置的groupBys(“查询”类型的数据源)的执行方式有所不同。Broker首先以通常的方式运行内部groupBy查询。然后,"v1"策略使用Druid的索引机制,在内部,实现内部查询,并在这些实现的结果上运行外部查询。"v2"策略则使用可溢出到磁盘的堆外实时映射和堆上字符串字典,在内部查询的结果流上运行外部查询。两种策略都以单线程方式在Broker上执行外部查询。

[6] 配置

本节介绍groupBy查询的配置。您可以runtime.properties在Broker,Historical和MiddleManager进程的文件中设置运行时属性。您可以通过查询上下文设置查询上下文参数。

  • groupBy v2的配置

    运行时属性

    属性 描述 默认
    Druid.query.groupBy.maxMergingDictionarySize 合并期间用于字符串字典的最大堆空间量(大约)。当字典超过此大小时,将触发溢出到磁盘。 100000000
    Druid.query.groupBy.maxOnDiskStorage 合并缓冲区或字典填满时,每个查询用于将结果集溢出到磁盘的最大磁盘空间量。超过此限制的查询将失败。设置为零可禁用使用磁盘。 0(禁用)

    查询上下文:

    属性 描述
    maxMergingDictionarySize 可用于降低Druid.query.groupBy.maxMergingDictionarySize的默认值。
    maxOnDiskStorage 可用于降低Druid.query.groupBy.maxOnDiskStorage的默认值。
  • 高级配置(通用配置)

    运行时属性:

    属性 描述 默认
    Druid.query.groupBy.defaultStrategy 默认groupBy查询策略。 v2
    Druid.query.groupBy.singleThreaded 使用单个线程合并结果。

    查询上下文:

    属性 描述
    groupByStrategy 覆盖Druid.query.groupBy.defaultStrategy的默认值。
    groupByIsSingleThreaded 覆盖Druid.query.groupBy.singleThreaded的默认值。
  • GroupBy v2配置

    运行时属性:

    属性 描述 默认
    Druid.query.groupBy.bufferGrouperInitialBuckets 堆外哈希表中用于结果分组的初始存储桶数。设置为0以使用合理的默认值(1024)。 0
    Druid.query.groupBy.bufferGrouperMaxLoadFactor 用于结果分组的堆外哈希表的最大负载因子。当负载系数超过此大小时,表将增长或溢出到磁盘。设置为0以使用合理的默认值(0.7)。 0
    Druid.query.groupBy.forceHashAggregation 强制使用基于哈希的聚合。
    Druid.query.groupBy.intermediateCombineDegree 合并树中合并在一起的中间节点数。更高的级别需要更少的线程,如果服务器有足够强大的cpu核心,那么通过减少太多线程的开销,这可能有助于提高查询性能 8
    Druid.query.groupBy.numParallelCombineThreads 并行合并线程的数量。该值应大于1以启用并行合并功能。用于并行合并的实际线程数为min(Druid.query.groupBy.numParallelCombineThreads,Druid.processing.numThreads)。 1(已禁用)
    Druid.query.groupBy.applyLimitPushDownToSegment 如果Broker将下限限制到可查询的节点(Historicals,peons),则在段扫描期间限制结果。 真(启用)

    查询上下文:

    属性 描述 默认
    bufferGrouperInitialBuckets 覆盖Druid.query.groupBy.bufferGrouperInitialBuckets的默认值。 没有
    bufferGrouperMaxLoadFactor 覆盖Druid.query.groupBy.bufferGrouperMaxLoadFactor的默认值。 没有
    forceHashAggregation 覆盖Druid.query.groupBy.forceHashAggregation的默认值。 没有
    intermediateCombineDegree 覆盖Druid.query.groupBy.intermediateCombineDegree的默认值。 没有
    numParallelCombineThreads 覆盖Druid.query.groupBy.numParallelCombineThreads的默认值。 没有
    sortByDimsFirst 首先按维度值对结果排序,然后按时间戳排序。
    forceLimitPushDown 当orderby中的所有字段都是分组键的一部分时,Broker将把limit申请下推至Historical进程。当排序使用字段不在分组关键字中时,此优化可能会导致近似结果的准确性降低,因此,在这种情况默认禁用此项。需启用则在上下文中设置
    applyLimitPushDownToSegment 如果Broker将下限限制到可查询的节点(Historical,peons),则在段扫描期间限制结果。此上下文属性可覆盖Druid.query.groupBy.applyLimitPushDownToSegment。
  • GroupBy v1配置

    运行时属性:

    属性 描述 默认
    Druid.query.groupBy.maxIntermediateRows 每段分组允许的最大行数。这是一个调整参数,没有强加任何限制。相反,它可能会将合并工作从每个细分引擎转移到整体合并索引。超过此限制的查询不会失败。 50000
    Druid.query.groupBy.maxResults 最大结果数。超过此限制的查询将失败。 500000

    支持的查询上下文:

    属性 描述 默认
    maxIntermediateRows 用于降低Druid.query.groupBy.maxIntermediateRows的默认值。 没有
    maxResults 用于降低Druid.query.groupBy.maxResults的默认值。 没有
    useOffheap 设置为true可以在合并结果时以非堆方式存储聚合。

基于数组的结果行

Druid内部始终使用基于数组的groupBy结果行表示,但是默认情况下,它在Broker处转换为基于映射的结果格式。为了减少此转换的开销,如果在查询上下文resultAsArray中将其设置为true,则结果也可以直接从Broker基于数组格式返回。

四、扫描查询(Scan)

Scan查询以流模式返回查询结果。

除了将简单的用法(向broker发送scan查询)之外,还可以将scan查询直接发送给Historical或流式摄取任务。如果要并行检索大量数据,这将很有用。

{
"queryType": "scan",
"dataSource": "wikipedia",
"resultFormat": "list",
"columns":[],
"intervals": [
    "2013-01-01/2013-01-02"
],
"batchSize":20480,
"limit":3
}
属性 描述 必要
queryType 设置查询类型为时间序列 scan
dataSource 数据源
intervals 定义查询时间范围。
resultFormat 结果集格式
filter 过滤器
columns 要扫描的维度和指标的字符串数组。如果保留为空,则返回所有维度和指标。
batchSize 返回给客户端之前缓冲的最大行数。默认为20480
limit 要返回多少行。如果未指定,将返回所有行。
order 根据时间戳返回的行的顺序。支持“升序”,“降序”和“无”(默认)。当前,仅对__time字段中包含该列columns且满足时间顺序部分中概述的要求的查询才支持“升序”和“降序” 。
legacy 与“scan-query”类似。默认属性设置Druid.query.scan.legacy,默认为false。
context 上下文

当resultFormat等于list时,结果:

[{
    "segmentId" : "wikipedia_editstream_2012-12-29T00:00:00.000Z_2013-01-10T08:00:00.000Z_2013-01-10T08:13:47.830Z_v9",
    "columns" : [
    "timestamp",
    "robot",
    "namespace",
    "anonymous",
    "unpatrolled",
    "page",
    "language",
    "newpage",
    "user",
    "count",
    "added",
    "delta",
    "variation",
    "deleted"
    ],
    "events" : [ {
        "timestamp" : "2013-01-01T00:00:00.000Z",
        "robot" : "1",
        "namespace" : "article",
        "anonymous" : "0",
        "unpatrolled" : "0",
        "page" : "11._korpus_(NOVJ)",
        "language" : "sl",
        "newpage" : "0",
        "user" : "EmausBot",
        "count" : 1.0,
        "added" : 39.0,
        "delta" : 39.0,
        "variation" : 39.0,
        "deleted" : 0.0
    }, {
        "timestamp" : "2013-01-01T00:00:00.000Z",
        "robot" : "0",
        "namespace" : "article",
        "anonymous" : "0",
        "unpatrolled" : "0",
        "page" : "112_U.S._580",
        "language" : "en",
        "newpage" : "1",
        "user" : "MZMcBride",
        "count" : 1.0,
        "added" : 70.0,
        "delta" : 70.0,
        "variation" : 70.0,
        "deleted" : 0.0
    }, {
        "timestamp" : "2013-01-01T00:00:00.000Z",
        "robot" : "0",
        "namespace" : "article",
        "anonymous" : "0",
        "unpatrolled" : "0",
        "page" : "113_U.S._243",
        "language" : "en",
        "newpage" : "1",
        "user" : "MZMcBride",
        "count" : 1.0,
        "added" : 77.0,
        "delta" : 77.0,
        "variation" : 77.0,
        "deleted" : 0.0
    } ]
} ]

当resultFormat等于compactedList时,结果:

[{
    "segmentId" : "wikipedia_editstream_2012-12-29T00:00:00.000Z_2013-01-10T08:00:00.000Z_2013-01-10T08:13:47.830Z_v9",
    "columns" : [
    "timestamp", "robot", "namespace", "anonymous", "unpatrolled", "page", "language", "newpage", "user", "count", "added", "delta", "variation", "deleted"
    ],
    "events" : [
    ["2013-01-01T00:00:00.000Z", "1", "article", "0", "0", "11._korpus_(NOVJ)", "sl", "0", "EmausBot", 1.0, 39.0, 39.0, 39.0, 0.0],
    ["2013-01-01T00:00:00.000Z", "0", "article", "0", "0", "112_U.S._580", "en", "1", "MZMcBride", 1.0, 70.0, 70.0, 70.0, 0.0],
    ["2013-01-01T00:00:00.000Z", "0", "article", "0", "0", "113_U.S._243", "en", "1", "MZMcBride", 1.0, 77.0, 77.0, 77.0, 0.0]
    ]
} ]

[1] 时间排序

Scan查询当前只支持对非传统查询的时间戳排序。使用时间戳排序将产生的结果你们盟指定来自于哪一个具体的段。使用时间戳排序需要结果集限制要少于Druid.query.scan.maxRowsQueuedForOrdering 的行或所有扫描的段少于Druid.query.scan.maxSegmentPartitionsOrderedInMemory分区时。除非指定了段列表,否则直接发布到historicals的查询不支持时间排序。这些限制的原因在于,时间排序的实现使用两种策略,如果不去限制的话,它们会消耗过多的堆内存。这些策略(下面列出)是根据historicals选择的,具体取决于查询结果集限制和要扫描的段数。

  • 优先级队列:historicals上的每个段都按顺序打开。每行都添加到有界优先级队列中,该队列按时间戳排序。对于超过结果集限制的每一行,具有最早(如果递减)时间戳或最新(如果递升)时间戳的行将出队。处理完每一行后,优先级队列的排序内容将分批流回给broker。尝试将太多的行加载到内存中会带来历史节点内存不足的风险。该Druid.query.scan.maxRowsQueuedForOrdering属性通过限制使用时间排序时,查询结果集中的行数来防止这种情况。

  • N向合并:对于每个段,并行打开每个分区。由于每个分区的行已经按时间排序,因此可以对每个分区的结果执行n路合并。这种方法不会将整个结果集持久存储在内存中(例如Priority Queue),因为它会从合并函数返回批次时将其返回。但是,由于需要为每个分区打开解压缩和解码缓冲区,尝试查询太多分区也可能导致高内存使用。该Druid.query.scan.maxSegmentPartitionsOrderedInMemory限制通过限制使用时间顺序时,随时打开的分区的数量来防止这种情况。

二者Druid.query.scan.maxRowsQueuedForOrdering和Druid.query.scan.maxSegmentPartitionsOrderedInMemory是可配置的,并且可以基于硬件规格与被查询的维度数来调节。也可以使用查询上下文中的maxRowsQueuedForOrdering和maxSegmentPartitionsOrderedInMemory属性来覆盖这些配置属性。

[2] 传统模式

Scan查询支持传统模式,该模式旨在与先前的Scan查询contrib扩展协议兼容。在传统模式下,您可以期望以下行为更改:

  • 该__time列返回为"timestamp"而不是"__time"。这将优先于您可能拥有的其他任何列"timestamp"。
  • __time即使您没有特别要求,该列也包含在列列表中。
  • 时间戳记以ISO8601时间字符串返回。
  • 可以通过传入 "legacy":true 查询JSON或通过 Druid.query.scan.legacy=true 在Druid进程中进行设置来触发传统模式。如果您以前使用的是scan-query contrib扩展名,则最佳迁移方式是在滚动升级过程中激活传统模式,然后在升级完成后将其关闭。

[3] 配置属性

属性 描述 取值 默认
Druid.query.scan.maxRowsQueuedForOrdering 使用时间排序时返回的最大行数 [1,2147483647]中的整数 100000
Druid.query.scan.maxSegmentPartitionsOrderedInMemory 使用时间排序时,每个historical扫描的最大段数 [1,2147483647]中的整数 50
Druid.query.scan.legacy 是否应为Scan查询打开传统模式 是 或 否

[4] 查询上下文属性

属性 描述 取值 默认
maxRowsQueuedForOrdering 使用时间排序时返回的最大行数。覆盖同名的config。 [1,2147483647]中的整数 Druid.query.scan.maxRowsQueuedForOrdering
maxSegmentPartitionsOrderedInMemory 使用时间排序时,每个历史记录扫描的最大段数。覆盖同名的config。 [1,2147483647]中的整数 Druid.query.scan.maxSegmentPartitionsOrderedInMemory

查询context 的JSON对象:

{
    "maxRowsQueuedForOrdering": 100001,
    "maxSegmentPartitionsOrderedInMemory": 100
}

五、时间边界查询(TimeBoundary)

时间边界查询返回数据集的最早和最新数据点

{
    "queryType" : "timeBoundary",
    "dataSource": "sample_datasource",
    "bound"     : < "maxTime" | "minTime" > # optional, defaults to returning both timestamps if not set
    "filter"    : { "type": "and", "fields": [<filter>, <filter>, ...] } # optional
}

时间边界查询包含3个主要部分:

属性 描述 必要
queryType 设置查询类型为时间序列 timeBoundary
dataSource 数据源
bound 可选,设置为maxTime或minTime仅返回最新或最早的时间戳。如果未设置,默认返回两者
filter 见过滤器
context 参见上下文
[ {
"timestamp" : "2013-05-09T18:24:00.000Z",
"result" : {
    "minTime" : "2013-05-09T18:24:00.000Z",
    "maxTime" : "2013-05-09T18:37:00.000Z"
}
} ]

六、段元数据查询(SegmentMeatadata)

  • 段中所有列的基数
  • 段中字符串类型列的最小值/最大值
  • 如果段列以平面格式存储,则估计的字节大小
  • 段内存储的行数
  • 该段所覆盖的区间
  • 段中所有列的列类型
  • 如果以平面格式存储,则估计的总段字节大小
  • 段是否累积
  • 段ID
{
"queryType":"segmentMetadata",
"dataSource":"sample_datasource",
"intervals":["2013-01-01/2014-01-01"]
}
属性 描述 必要
queryType 设置查询类型为时间序列 segmentMetadata
dataSource 数据源
intervals 定义查询时间范围。
toInclude 在结果中包括的列。默认为所有。
merge 将所有单独段的元数据结果合并在一起
context 上下文
analysisTypes 字符串列表,指定应计算哪些列属性(例如基数,大小)并在结果中返回。默认为[“ cardinality”,“ interval”,“ minmax”],但可以使用段元数据查询config覆盖。
lenientAggregatorMerge 如果为true,则合并聚合。
[ {
"id" : "some_id",
"intervals" : [ "2013-05-13T00:00:00.000Z/2013-05-14T00:00:00.000Z" ],
"columns" : {
    "__time" : { "type" : "LONG", "hasMultipleValues" : false, "size" : 407240380, "cardinality" : null, "errorMessage" : null },
    "dim1" : { "type" : "STRING", "hasMultipleValues" : false, "size" : 100000, "cardinality" : 1944, "errorMessage" : null },
    "dim2" : { "type" : "STRING", "hasMultipleValues" : true, "size" : 100000, "cardinality" : 1504, "errorMessage" : null },
    "metric1" : { "type" : "FLOAT", "hasMultipleValues" : false, "size" : 100000, "cardinality" : null, "errorMessage" : null }
},
"aggregators" : {
    "metric1" : { "type" : "longSum", "name" : "metric1", "fieldName" : "metric1" }
},
"queryGranularity" : {
    "type": "none"
},
"size" : 300000,
"numRows" : 5000000
} ]

维度列的类型为STRING,指标列将具有类型FLOAT或LONG或者底层复杂的类型,如hyperUnique类型。时间戳列的类型为LONG。

如果该errorMessage字段为非空,则不需要检查其他的字段因为它们的内容是不确定的。

只有维度列(即具有type为STRING)的列将具有任何基数。其余列(时间戳记和指标列)将显示基数为null。

[1] 时间间隔

如果未指定时间间隔,则查询将使用默认时间间隔,该时间间隔从最近段的结束时间开始到之前的可配置时间长度。该默认时间段的长度是通过以下方式在Broker配置中设置的:Druid.query.segmentMetadata.defaultHistory

[2] 3种类型的toInclude对象

  • all

    "toInclude": { "type": "all"}
    
  • none

    "toInclude": { "type": "none"}
    
  • list

    "toInclude": { "type": "list", "columns": [<string list of column names>]}
    

[3] analysisTypes

这是一列属性,这些属性确定有关列返回的信息量,即要对列执行的分析。

默认情况下,将使用“cardinality”,“interval”和“minmax”类型。如果不需要某个属性,则从此列表中删除

可以通过以下方式在Broker配置中设置默认分析类型: Druid.query.segmentMetadata.defaultAnalysisTypes

列分析的类型

  • cardinality

    cardinality结果将返回每一列的估计基数下限。仅与维列相关。
    
  • minmax

    每列的估计最小值/最大值。仅与维列相关。
    
  • size

    结果中将包含估计的总段字节大小
    
  • intervals

    结果中将包含与查询段关联的间隔列表
    
  • timestampSpec

    结果中将包含存储在段中的数据的timestampSpec
    
  • queryGranularity

    结果中将包含存储在段中的数据的查询粒度
    
  • aggregators

    结果中将包含可用于查询指标列的聚合器列表
    合并可以严格也可以宽松
    结果的形式是列名到聚合器的映射
    
  • rollup

    结果为 true/false/null
    

[4] lenientAggregatorMerge

如果某些段的聚合器未知,或者两个段的同一列使用不兼容的聚合器(例如,longSum更改为doubleSum),则跨段的聚合器元数据之间可能会发生冲突。

聚合器可以严格合并(默认)或宽松合并。通过严格合并,如果存在任何段的聚合器未知或任何类型的冲突,则合并的聚合器列表将为null。通过宽松的合并,将忽略具有未知聚合器的段,并且聚合器之间的冲突只会使该特定列的聚合器无效。

特别是在宽大合并的情况下,单个列的聚合器可能是null。严格合并不会发生这种情况。

七、数据源元数据查询(DatasourceMeatadata)

数据源的元数据查询,返回数据源的元数据信息。

{
    "queryType" : "dataSourceMetadata",
    "dataSource": "sample_datasource"
}

数据源的元数据查询包含两个主要部分:

属性 描述 必要
queryType 设置查询类型为时间序列 dataSourceMetadata
dataSource 数据源
context 参见上下文
[ {
"timestamp" : "2013-05-09T18:24:00.000Z",
"result" : {
    "maxIngestedEventTime" : "2013-05-09T18:24:09.007Z"
}
} ]

八、搜索查询(Search)

搜索查询返回与搜索规范匹配

{
"queryType": "search",
"dataSource": "sample_datasource",
"granularity": "day",
"searchDimensions": [
    "dim1",
    "dim2"
],
"query": {
    "type": "insensitive_contains",
    "value": "Ke"
},
"sort" : {
    "type": "lexicographic"
},
"intervals": [
    "2013-01-01T00:00:00.000/2013-01-03T00:00:00.000"
]
}
属性 描述 必要
queryType 设置查询类型为时间序列 search
dataSource 数据源
granularity 定义查询结果的粒度
filter 过滤器
limit 限制结果数量。默认值为无限制。
intervals 定义查询时间范围。
searchDimensions 要进行搜索的维度。默认在所有维度上进行。 没有
query 查询
sort 指定排序字段 字典(默认),字母,数字等。 没有
context 上下文 没有
[
{
    "timestamp": "2013-01-01T00:00:00.000Z",
    "result": [
    {
        "dimension": "dim1",
        "value": "Ke$ha",
        "count": 3
    },
    {
        "dimension": "dim2",
        "value": "Ke$haForPresident",
        "count": 1
    }
    ]
},
{
    "timestamp": "2013-01-02T00:00:00.000Z",
    "result": [
    {
        "dimension": "dim1",
        "value": "SomethingThatContainsKe",
        "count": 1
    },
    {
        "dimension": "dim2",
        "value": "SomethingElseThatContainsKe",
        "count": 2
    }
    ]
}
]

[1] 实施细节

可以使用两种不同的策略来执行搜索查询。默认策略由Broker上的“ Druid.query.search.searchStrategy”运行时属性确定。可以在查询上下文中使用“ searchStrategy”来覆盖它。如果既未设置上下文字段也未设置属性,则将使用“ useIndexes”策略。

  • “useIndexes”策略首先根据搜索维度对位图索引的支持将其分为两类。然后,它将useIndexes策略和基cursorOnly策略分别应用于支持位图和其他维度的维组。useIndexes策略用于搜索查询处理。对于每个维度,它会读取每个维度值的位图索引,评估搜索谓词,最后检查时间间隔和筛选谓词。useIndexes策略对于大基数的搜索维度性能较低,这意味着大多数搜索维值都是唯一的。

  • “cursorOnly”策略生成基于基础指针计划。该计划创建一个指针标,该指针从queryableIndexSegment中读取一行,然后评估搜索的词。如果某些过滤器支持位图索引,则指针只能读取满足这些过滤器的行,从而节省了I/O成本。但是,对于低选择性的过滤器,它可能会很慢。

  • “auto”策略使用基于搜索成本的计划程序,来选择最佳搜索策略。它评估useIndexes和cursorOnly两种策略的成本,并选择最佳的计划。当前,由于成本估算的开销,默认情况下未启用它。

[2] 服务器配置

属性 描述 默认
Druid.query.search.searchStrategy 默认搜索查询策略。 useIndexes

[3] 查询上下文

属性 描述
searchStrategy 覆盖Druid.query.search.searchStrategy此属性值。

九、选择查询(Select)

从当前新版本开始,删除select的查询使用Scan查询代替,该查询可提高内存使用率和性能。这解决了用户在选择查询中遇到Druid内存不足或速度变慢的问题。

Scan查询具有不同的语法,但支持Select查询的许多功能,包括时间排序和限制。Scan查询不包括Select查询的分页功能;但是,在许多情况下,使用Scan不需要分页,因为它能够在一个调用中返回几乎无限数量的结果。

十、多值维度查询(Multi-value dimensions)

Druid支持多值维度查询。当输入字段包含的值是数组而不是单个值(例如JSON数组或包含一个或多个listDelimiter 字符的TSV字段)时,将生成这些字段。

当多值维度用作分组依据的维度时,本文档描述了groupBy(topN具有类似行为)查询的行为。有关内部表示形式的详细信息,请参见分段中的多值列一 节。本文档中的示例采用本机Druid查询的形式。有关在SQL中使用多值字符串维的详细信息,请参阅Druid SQL文档。

[1] 查询多值维度

假设您有一个数据源,该数据源的段中包含以下行,且其多值维度称为tags。

{"timestamp": "2011-01-12T00:00:00.000Z", "tags": ["t1","t2","t3"]}  #row1
{"timestamp": "2011-01-13T00:00:00.000Z", "tags": ["t3","t4","t5"]}  #row2
{"timestamp": "2011-01-14T00:00:00.000Z", "tags": ["t5","t6","t7"]}  #row3
{"timestamp": "2011-01-14T00:00:00.000Z", "tags": []}                #row4

[2] Filter

所有查询类型以及经过过滤的聚合器都可以对多值维度进行过滤。过滤器在多值维度上遵循以下规则:

  • 如果多值维度的任何值与过滤器匹配,则值过滤器(例如“selector”,“bound”和“in”)将与一行匹配。
  • 如果维度有任何重叠,则“列比较”过滤器将匹配一行。
  • 匹配null或""(空字符串)的值过滤器将匹配多值维度中的空单元格。
  • 逻辑表达式过滤器的行为与在单值维度上的行为相同:如果所有基础过滤器均与该行匹配,则“and”匹配该行;如果任何基础过滤器匹配该行,则“or”匹配该行;如果基础过滤器与该行不匹配,则“not”与该行匹配。

例如,此“or”过滤器将匹配上面数据集的row1和row2,但不匹配row3:

{
   "type": "or",
   "fields": [
   {
       "type": "selector",
       "dimension": "tags",
       "value": "t1"
   },
   {
       "type": "selector",
       "dimension": "tags",
       "value": "t3"
   }
   ]
}

此“and”过滤器将仅匹配上述数据集的row1:

{
    "type": "and",
    "fields": [
    {
        "type": "selector",
        "dimension": "tags",
        "value": "t1"
    },
    {
        "type": "selector",
        "dimension": "tags",
        "value": "t3"
    }
    ]
}

此“selector”过滤器将匹配以上数据集的row4:

{
    "type": "selector",
    "dimension": "tags",
    "value": null
}

[3] Group

topN和groupBy查询可以对多值维度进行分组。在对多值维度进行分组时,匹配行中的所有值将用于为每个值生成一组。可以认为这等效于许多SQL语言支持UNNEST的ARRAY类型上使用的运算符。这意味着查询有可能返回的行多于结果行数。例如,一个topn的维度列tags与filter "t1" AND "t3"将只ROW1匹配,并与三个组生成结果:t1,t2,和t3。如果只需要包含与filter匹配的值,则可以使用filter的 DimensionSpec。这也可以提高性能。

不使用filter的GroupBy查询

{
    "queryType": "groupBy",
    "dataSource": "test",
    "intervals": [
    "1970-01-01T00:00:00.000Z/3000-01-01T00:00:00.000Z"
    ],
    "granularity": {
    "type": "all"
    },
    "dimensions": [
    {
        "type": "default",
        "dimension": "tags",
        "outputName": "tags"
    }
    ],
    "aggregations": [
    {
        "type": "count",
        "name": "count"
    }
    ]
}

返回结果。

[
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t1"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t2"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 2,
        "tags": "t3"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t4"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 2,
        "tags": "t5"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t6"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t7"
    }
    }
]

使用selector查询使用filter的结果进行GroupBy查询

{
    "queryType": "groupBy",
    "dataSource": "test",
    "intervals": [
    "1970-01-01T00:00:00.000Z/3000-01-01T00:00:00.000Z"
    ],
    "filter": {
    "type": "selector",
    "dimension": "tags",
    "value": "t3"
    },
    "granularity": {
    "type": "all"
    },
    "dimensions": [
    {
        "type": "default",
        "dimension": "tags",
        "outputName": "tags"
    }
    ],
    "aggregations": [
    {
        "type": "count",
        "name": "count"
    }
    ]
}

返回结果。

[
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t1"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t2"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 2,
        "tags": "t3"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t4"
    }
    },
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 1,
        "tags": "t5"
    }
    }
]

您可能会惊讶地发现结果中包含“t1”,“t2”,“t4”和“t5”。发生这种情况是因为查询过滤器在分解前应用于行。对于多值维度,“t3”的选择器过滤器将匹配row1和row2,然后进行分解。如果多个值中的任何单个值与查询过滤器匹配,则查询过滤器将匹配一行。

使用selector查询过滤器和“维度”属性中的其他过滤器进行GroupBy查询,要解决上述问题并仅获取返回的“t3”行,则必须使用“过滤的尺寸规格”,如下面的查询所示。

{
    "queryType": "groupBy",
    "dataSource": "test",
    "intervals": [
    "1970-01-01T00:00:00.000Z/3000-01-01T00:00:00.000Z"
    ],
    "filter": {
    "type": "selector",
    "dimension": "tags",
    "value": "t3"
    },
    "granularity": {
    "type": "all"
    },
    "dimensions": [
    {
        "type": "listFiltered",
        "delegate": {
        "type": "default",
        "dimension": "tags",
        "outputName": "tags"
        },
        "values": ["t3"]
    }
    ],
    "aggregations": [
    {
        "type": "count",
        "name": "count"
    }
    ]
}

返回结果。

[
    {
    "timestamp": "1970-01-01T00:00:00.000Z",
    "event": {
        "count": 2,
        "tags": "t3"
    }
    }
]

对于groupBy查询,使用具有spec的结果可能会得到类似的结果,但是使用过滤后的DimensionSpec会更加有效,因为这是在查询处理管道中的最低级别的应用。

十一、查找查询(Lookups)

Lookups是Druid中的一个概念,其中(可选)将维值替换为新值,从而实现类似联接的功能。在Druid中应用查找类似于在数据仓库中加入维度表。就这些文档而言,“key”是指要匹配的值,“value”是指其替换值。因此,如果您想映射 appid-12345到,Super Mega Awesome App则键为appid-12345,值将为 Super Mega Awesome App。

值得注意的是,查找不仅支持将键一对一映射到唯一值(例如国家/地区代码和国家/地区名称)的用例,而且还支持将多个ID映射到同一值(例如,多个应用程序ID)的用例。映射到单个客户经理。当查询是一对一的时,Druid可以在查询时应用其他优化。

查找没有历史记录。他们总是使用当前数据。这意味着,如果某个特定应用程序ID的主要客户经理发生了变化,并且您发出了一个查询,以查找存储该应用程序ID与客户经理的关系,则它将返回该应用程序ID的当前客户经理。

如果您需要数据时间范围敏感的查找,那么当前在查询时不动态支持这种用例,并且此类数据属于原始的非规范化数据,供在Druid中使用。

可以在查询时将很小的查找(键的数量在几十到几百的数量级上)作为“映射”查找(根据维度规格)进行传递。

其他查找类型也可以作为扩展使用,包括:

  • 通过lookups-cached-global从本地文件,远程URI或JDBC全局缓存的查找。
  • 通过kafka-extraction-namespace从Kafka主题全局缓存的查找。

[1] 查询语法

在Druid SQL中,可以使用LOOKUP函数查询查询

SELECT LOOKUP(column_name, 'lookup-name'), COUNT(*) FROM datasource GROUP BY 1

在本机json查询中,可以使用维度列或扩展查询。

[2] 查询执行

当查询执行涉及到的聚合时,Druid可以决定在扫描和聚合行时应用查询,还是在聚合完成后应用查询。聚合完成后应用查找更为有效,因此Druid会这样做。Druid通过检查查找是否标记为“injective”来决定。通常,应该为任何自然一对一的查找设置此属性,以允许Druid尽可能快地运行查询。

内射式查找应包括可能显示在数据集中的所有可能的键,并且还应将所有键映射成唯一值。这很重要,因为非注入式查找可能会将不同的键映射到相同的值,这必须在聚合过程中加以考虑,以免查询结果包含两个应该聚合为一个的结果值。

  • 此查询是内射性的(假定它包含数据中所有可能的键):

    1 -> Foo
    2 -> Bar
    3 -> Billy
    
  • 但有一个不是,因为“ 2”和“ 3”都映射到相同的键:

    1 -> Foo
    2 -> Bar
    3 -> Bar
    

要告诉Druid您的lookup是已经配置的 "injective":true,必须在查找配置中指定。Druid不会自动检测到这一点。

[3] 动态配置

动态查找配置是一项实验功能。不再支持静态配置。以下内容记录了可通过Coordinator访问的群集范围配置的行为。通过服务器“层”的概念传递配置。“层”定义为一组接收查询,一组查询的服务。例如,您可能将所有“历史记录”作为的一部分__default,而“Peons”则作为其任务所使用的数据源的各个层的一部分。查找的层完全独立于历史层。

通过以下URI模板使用JSON访问这些配置

http://<COORDINATOR_IP>:<PORT>/Druid/coordinator/v1/lookups/config/{tier}/{id}

假定以下所有URI都已http://<COORDINATOR_IP>:<PORT>添加。

如果你以前从来没有配置lookup,您必须张贴一个空的JSON对象{}来/Druid/coordinator/v1/lookups/config初始化配置。

将返回以下结果之一:

  • 404, 如果找不到资源
  • 400,如果请求的格式有问题
  • 202, 如果请求被异步接受(POST和DELETE)
  • 200,如果请求成功(仅GET)

[4] 配置传播行为

配置由Coordinator传播到查询服务进程(Broker/Router/Peon/Historical)。查询服务流程具有内部API,用于管理流程上的查找,并且由Coordinator使用。Coordinator会定期检查是否有任何进程需要加载/删除,查找并适当地更新它们。

单个查询服务过程只能同时处理2个查找配置传播请求。应用此限制是为了防止查找处理占用过多的服务器HTTP连接。

[5] 用于配置查找的API

批量更新

可以通过将JSON对象来批量更新查找/Druid/coordinator/v1/lookups/config

{
    "<tierName>": {
        "<lookupName>": {
            "version": "<version>",
            "lookupExtractorFactory": {
            "type": "<someExtractorFactoryType>",
            "<someExtractorField>": "<someExtractorValue>"
            }
        }
    }
}

“version”是由用户分配的任意字符串,当对现有查找进行更新时,用户将需要指定词典上更高的版本。

配置示例

{
    "__default": {
    "country_code": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "map",
        "map": {
            "77483": "United States"
        }
        }
    },
    "site_id": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "cachedNamespace",
        "extractionNamespace": {
            "type": "jdbc",
            "connectorConfig": {
            "createTables": true,
            "connectURI": "jdbc:mysql:\/\/localhost:3306\/Druid",
            "user": "Druid",
            "password": "diurd"
            },
            "table": "lookupTable",
            "keyColumn": "country_id",
            "valueColumn": "country_name",
            "tsColumn": "timeColumn"
        },
        "firstCacheTimeout": 120000,
        "injective": true
        }
    },
    "site_id_customer1": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "map",
        "map": {
            "847632": "Internal Use Only"
        }
        }
    },
    "site_id_customer2": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "map",
        "map": {
            "AHF77": "Home"
        }
        }
    }
    },
    "realtime_customer1": {
    "country_code": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "map",
        "map": {
            "77483": "United States"
        }
        }
    },
    "site_id_customer1": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "map",
        "map": {
            "847632": "Internal Use Only"
        }
        }
    }
    },
    "realtime_customer2": {
    "country_code": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "map",
        "map": {
            "77483": "United States"
        }
        }
    },
    "site_id_customer2": {
        "version": "v0",
        "lookupExtractorFactory": {
        "type": "map",
        "map": {
            "AHF77": "Home"
        }
        }
    }
    }
}

映射中的所有条目将更新成现有条目。没有的条目将被删除。

[6] 更新查询

通过POST请求一个特定的查找提取器工厂/Druid/coordinator/v1/lookups/config/{tier}/{id}将更新该特定的提取器工厂。

示例请求/Druid/coordinator/v1/lookups/config/realtime_customer1/site_id_customer1

{
    "version": "v1",
    "lookupExtractorFactory": {
    "type": "map",
    "map": {
        "847632": "Internal Use Only"
    }
    }
}

这将用上面定义的site_id_customer1替换查找到的realtime_customer1。

  • 获取所有查询

    GET请求到/Druid/coordinator/v1/lookups/config/all将返回所有已知查找的功能和所有层次。

  • 查找

    通过GET特定的查找提取器工厂来完成/Druid/coordinator/v1/lookups/config/{tier}/{id}

    示例:一个GET请求 /Druid/coordinator/v1/lookups/config/realtime_customer2/site_id_customer2应该返回

    {
      "version": "v1",
      "lookupExtractorFactory": {
        "type": "map",
        "map": {
          "AHF77": "Home"
        }
      }
    }
    
  • 删除查询

    DELETE请求/Druid/coordinator/v1/lookups/config/{tier}/{id}也可以从集群删除查找。如果是该层中的最后一次查找,则该层也将被删除。

  • 删除层

    DELETE请求/Druid/coordinator/v1/lookups/config/{tier}会从集群中删除该层。

  • 层列表名称

    GET请求/Druid/coordinator/v1/lookups/config将在动态配置返回已知层名称的列表。除了动态配置中已知的层之外,要发现集群中当前活动的层的列表,设置discover=true 可以按照来添加参数/Druid/coordinator/v1/lookups/config?discover=true

  • 列出查询名称

    GET请求/Druid/coordinator/v1/lookups/config/{tier}会返回已知的查找该层级名称列表。

    这些端点可用于获取已配置的查找到使用诸如“历史记录”之类的查找的进程的传播状态。

[7] 查找状态的API

  • 列出所有查找的加载状态

    GET /Druid/coordinator/v1/lookups/status带有可选的查询参数detailed。

  • 列出层中查找的负载状态

    GET /Druid/coordinator/v1/lookups/status/{tier}带有可选的查询参数detailed。

  • 列出单次查找的加载状态

    GET /Druid/coordinator/v1/lookups/status/{tier}/{lookup}带有可选的查询参数detailed。

  • 列出所有进程的查找状态

    GET /Druid/coordinator/v1/lookups/nodeStatus带有discover用于从Zookeeper发现层的可选查询参数或列出的已配置查找层。

  • 列出层中进程的查找状态

    GET /Druid/coordinator/v1/lookups/nodeStatus/{tier}

  • 列出单个进程的查找状态

    GET /Druid/coordinator/v1/lookups/nodeStatus/{tier}/{host:port}

[8] 内部API

Peon,Router,Broker和Historical进程都可以使用查找配置。这些流程使用一个内部API来从list/load/drop其查找内容/Druid/listen/v1/lookups。这些返回值与集群范围的动态配置遵循相同的约定。以下请求示例可用于调试,但不能用于其他。

  • 获取查询

    一个GET以处理在/Druid/listen/v1/lookups将返回所有在进程当前活跃查询的JSON地图。返回值将是查找到其提取器工厂的json映射。

    {
      "site_id_customer2": {
        "version": "v1",
        "lookupExtractorFactory": {
          "type": "map",
          "map": {
            "AHF77": "Home"
          }
        }
      }
    }
    
  • 查找

    GET请求/Druid/listen/v1/lookups/some_lookup_name将返回LookupExtractorFactory由确定的查找some_lookup_name。返回值将是工厂的json表示形式。

    {
      "version": "v1",
      "lookupExtractorFactory": {
        "type": "map",
        "map": {
          "AHF77": "Home"
        }
      }
    }
    
  • 配置

    要将Broker/Router/Historical/Peon配置为查找层的一部分,请使用Druid.zk.paths.lookupTier属性。

    属性 描述 默认
    Druid.lookup.lookupTier 查询层。这独立于其他层。 __default
    Druid.lookup.lookupTierIsDatasource 对于诸如,为服务任务编制索引之类的事情,数据源将在任务运行时的属性中传递。此选项从与任务数据源相同的值中获取tierName。建议将其用作索引服务的Peon选项(如果有的话)。如果为true,则Druid.lookup.lookupTier不得指定 "false"

    要动态配置管理器的行为,请在Coordinator上使用以下属性:

    属性 描述 默认
    Druid.manager.lookups.hostTimeout 超时(以毫秒为单位,用于处理请求 2000(2秒)
    Druid.manager.lookups.allHostTimeout 超时(以毫秒为单位)以完成所有进程的查找管理。 900000(15分钟)
    Druid.manager.lookups.period 在管理周期之间暂停多长时间 120000(2分钟)
    Druid.manager.lookups.threadPoolSize 可以同时管理的服务进程数 10
  • 重启时保存配置

    可以在重新启动之前保存配置,这样一来,进程将不必等待协调器操作重新填充其查找。为此,设置以下属性:

    属性 描述 默认
    Druid.lookup.snapshotWorkingDir 用于存储当前查找配置快照的工作路径,将此属性保留为null将禁用快照/引导实用程序 null
    Druid.lookup.enableLookupSyncOnStartup 启动时使用Coordinator启用查找同步过程。可查询的进程将从Coordinator获取并加载查找,而不是等待Coordinator为其加载查找。如果集群中未配置任何查找,则用户可以选择禁用此选项。 true
    Druid.lookup.numLookupLoadingThreads 启动时用于并行加载查找的线程数。启动完成后,该线程池将被销毁。在JVM的生存期内不保留它 可用处理器/2
    Druid.lookup.coordinatorFetchRetries 在启动时同步期间,尝试重试几次以从Coordinator获取查找bean列表。 3
    Druid.lookup.lookupStartRetries 在启动时同步或运行时中,尝试重试多少次才能启动每个查找。 3
    Druid.lookup.coordinatorRetryDelay 启动时同步期间,两次重试之间的延迟时间(以毫秒为单位),以从Coordinator中获取查找列表。 60_000

[9] 内省查找

如果查找类型实现,则Broker提供用于查找自省的API LookupIntrospectHandler。

GET请求/Druid/v1/lookups/introspect/{lookupId}将返回完整值。

  • 如:GET /Druid/v1/lookups/introspect/nato-phonetic

    {
        "A": "Alfa",
        "B": "Bravo",
        "C": "Charlie",
        ...
        "Y": "Yankee",
        "Z": "Zulu",
        "-": "Dash"
    }
    

Key列表可以通过检索GET到/Druid/v1/lookups/introspect/{lookupId}/keys

  • 如:GET /Druid/v1/lookups/introspect/nato-phonetic/keys

    [
        "A",
        "B",
        "C",
        ...
        "Y",
        "Z",
        "-"
    ]
    

GET请求/Druid/v1/lookups/introspect/{lookupId}/values"将返回值列表。

  • 如:GET /Druid/v1/lookups/introspect/nato-phonetic/values

    [
        "Alfa",
        "Bravo",
        "Charlie",
        ...
        "Yankee",
        "Zulu",
        "Dash"
    ]
    

十二、Join查询(Joins)

Druid对通过查询时间查找进行联接的支持有限。查询时查找的常见用例是将一个维度值(例如,字符串ID)替换为另一个值(例如,人类可读的字符串值)。这类似于星形模式连接。

Druid尚未完全支持联接。尽管Druid的存储格式允许实现连接(对于作为维包括的列,不存在保真度的损失),但由于以下原因,尚未完全实现对连接的完全支持:

  • 根据我们的专业经验,扩展连接查询一直是使用分布式数据库的一个长期瓶颈。
  • 与管理大量并发,连接繁重的工作负载所预期的问题相比,功能上的增量收益被认为价值不高。
  • 联接查询本质上是基于一组共享键合并两个或更多数据流。我们知道的联接查询的主要高级策略是基于哈希的策略或排序合并策略。基于散列的策略要求除了一个数据集外,其他所有数据集都可以像散列表一样使用,然后对“主”流中的每一行对该散列表执行查找操作。排序合并策略假定每个流按连接键进行排序,因此允许流的增量连接。但是,这些策略中的每一种都需要以排序的顺序或哈希表的形式实现一定数量的流。

当联接的所有侧面都是非常大的表(> 10亿条记录)时,要实现预联接流,就需要进行复杂的分布式内存管理。我们针对高并发,多租户工作负载这一事实,只会加剧内存管理的复杂性。

十三、多租户(Multitenancy considerations)

Druid通常用于增强面向用户的数据应用程序,而多租户是其中的重要要求。本文档概述了Druid的多租户存储和查询功能。

[1] 共享的数据源还是每个租户的数据源?

数据源与数据库表是等效的。多租户工作负载可以为每个租户使用单独的数据源,也可以使用“tenant_id”维护在多个租户之间共享一个或多个数据源。在确定要要设置的租户和数据源的映射关系时,要考虑好每一种设置的优缺点。

[2] 每个租户的数据源的优点:

  • 每个数据源可以具有自己的架构,自己写数据,自己的分区规则以及自己的数据加载和到期规则。
  • 查询可以更快,因为要检查典型租户的查询的段将更少。
  • 您将获得最大的灵活性。

[3] 共享数据源的优点:

  • 每个数据源都需要使用自己的JVM进行实时索引。
  • 每个数据源都需要自己的YARN资源才能用于Hadoop批处理作业。
  • 每个数据源在磁盘上都需要其自己的段文件。
  • 由于这些原因,拥有大量的小型数据源可能是浪费资源的。

一种折衷办法是使用多个数据源,但使用数量少于租户。例如,您可能有一些带有分区规则A的租户,有些带有分区规则B的租户;您可以使用两个数据源,并在两个数据源之间拆分租户。

[4] 对共享数据源进行分区

如果您的多租户群集使用共享数据源,则大多数查询可能会在“tenant_id”维度上进行过滤。如果租户对数据进行了很好的分区,则这类查询的效果最佳。有几种方法可以完成此操作。

  • 使用批索引,您可以使用一维分区按tenant_id对数据进行分区。Druid总是按时间先分区,但是每个时间段内的次分区将在tenant_id上。
  • 使用实时索引,您可以通过调整发送到Druid的流来实现。例如,如果您使用的是Kafka,则可以让您的Kafka生产者按tenant_id的哈希值对主题进行分区。

[5] 定制数据分发

Druid还通过提供可配置的数据分发方式来支持多租户。可以将Druid的Historical过程配置为层,并可以设置规则来确定哪些段进入哪个层。一种使用情况是,与较旧的数据相比,最近的数据倾向于更频繁地访问。通过分层,可以将更多的最新细分托管在功能更强大的硬件上,以提高性能。可以在较便宜的硬件(不同层)上复制最近段的第二个副本,也可以将较旧的段存储在此层上。

[6] 支持高查询并发

Druid的基本计算单位是 段。进程并行扫描段,并且可以设置进程数量Druid.processing.numThreads并行扫描。为了并行处理更多数据并提高性能,可以将更多CPU添加到群集中。Druid段的大小应确定为使得任何给定段上的任何计算最多应在500毫秒内完成。

Druid在内部将扫描段的请求存储在优先级队列中。如果给定的查询需要扫描的段数比集群中可用处理器的总数更多,并且同时运行着许多同样耗时的查询,则我们不希望任何查询都被饿死。Druid的内部处理逻辑将从一个查询中扫描一组段,并在扫描完成后立即释放资源。这允许扫描来自另一个查询的第二组细分。通过使段计算时间保持很小,我们可以确保不断产生资源,并且处理与不同查询有关的段。

Druid查询可以选择priority在查询上下文中设置标志。可以将低速查询(下载或报表查询)降低优先级,而更具交互性的查询可以具有更高的优先级。

broker程序也可以专用于给定的层。例如,一组broker程序可以专用于快速的交互式查询,而另一组broker程序可以专用于较慢的报告查询。Druid还提供了一个Router 进程,可以根据各种查询参数(数据源,时间间隔等)将查询路由到不同的Broker。

十四、缓存查询(Query caching)

Druid在段和整个查询结果级别都支持查询结果缓存。缓存数据可以存储在本地JVM堆中,也可以存储在外部分布式键/值存储中。在所有情况下,Druid缓存都是查询结果缓存。唯一的区别是结果是特定段的部分结果还是整个查询的结果。在这两种情况下,只要任何基础数据发生更改,高速缓存都会失效;它永远不会返回过时的结果。

段级别的缓存允许利用缓存,即使某些基础段是可变的并且正在进行实时提取。在这种情况下,Druid可能会缓存不可变历史段的查询结果,同时在每个查询上重新计算实时段的结果。在这种情况下,整个查询结果级别的缓存没有用,因为它会不断地失效。

段级缓存确实需要Druid合并每个查询的每个段结果,即使它们是从缓存中提供的。因此,如果不存在由于实时提取而导致的无效问题,则整个查询结果级别的缓存会更加高效。

[1] 使用和设置缓存

所有缓存都有一对参数,它们控制着各个查询如何与缓存交互,例如“使用”缓存参数和“填充”缓存参数。这些设置必须通过运行时属性在服务级别启用,以利用缓存,但是可以通过在查询上下文中进行设置来在每个查询的基础上进行控制。“ use”参数显然控制查询是否将使用缓存的结果。“populate”参数控制查询是否将更新缓存的结果。这些是单独的参数,以允许对不常见数据的查询使用缓存的结果,而不会以其他查询(例如大报表或非常旧的数据)不太可能重复使用的结果污染缓存。

[2] broker上的查询缓存

broker同时支持段级缓存和整个查询结果级缓存。段级缓存由useCache和populateCache参数控制。整个查询结果级别的缓存由参数 useResultLevelCache和populateResultLevelCache和运行时属性控制 Druid.broker.cache.*。

与在小型集群的“历史记录”上启用查询缓存相比,在Broker上启用段级缓存可以产生更快的结果。对于较小的生产集群(<5个服务器),这是推荐的设置。设置在broker上的段级高速缓存是不推荐用于大型生产集群的,由于当属性Druid.broker.cache.populateCache被设置为true(和查询上下文参数populateCache不设置为false),从Historicals查询每个段的基础上返回结果,Historicals不会能够进行任何本地结果合并。这降低了Druid集群的伸缩能力。

[3] 查询Historicals缓存

历史记录仅支持段级缓存。段级缓存由查询上下文参数useCache和populateCache和运行时属性控制 Druid.historical.cache.*。

较大的生产集群应仅在历史记录上启用段级缓存设置(而不在Brokers上启用),以避免必须使用Brokers合并所有查询结果。在“Historicals”而不是“Brokers”上启用缓存填充,可使“历史记录”进行自己的本地结果合并,从而减轻了“Brokers”的负担。

[4] 任务摄取的缓存

任务执行程序进程(例如Peon或实验性Indexer)仅支持段级缓存。段级缓存由查询上下文参数useCache和populateCache 和运行时属性控制 Druid.realtime.cache.*。

较大的生产集群应仅在任务执行流程上启用段级缓存填充(而不在Broker上启用),以避免必须使用Brokers合并所有查询结果。在“Historicals”而不是“Brokers”上启用缓存填充,可使“历史记录”进行自己的本地结果合并,从而减轻了“Brokers”的负担。

请注意,任务执行器进程仅支持将其数据保留在本地的caffeine缓存,例如缓存。之所以存在此限制,是因为缓存将结果存储在摄取任务所生成的中间部分片段的级别上。这些中间部分段在各个任务副本之间不一定是相同的,因此远程高速缓存类型 例如memcached,任务执行程序进程将忽略它们。

十五、过滤查询(Spatial filters)

Druid支持根据原点和界限来过滤空间索引。

在任何数据规范中,都可以选择提供空间维度。例如,对于JSON数据规范,可以如下指定空间尺寸:

{
    "type": "hadoop",
    "dataSchema": {
        "dataSource": "DatasourceName",
        "parser": {
            "type": "string",
            "parseSpec": {
                "format": "json",
                "timestampSpec": {
                    "column": "timestamp",
                    "format": "auto"
                },
                "dimensionsSpec": {
                    "dimensions": [],
                    "spatialDimensions": [{
                        "dimName": "coordinates",
                        "dims": ["lat", "long"]
                    }]
                }
            }
        }
    }
}

空间过滤器

属性 描述 必要
dimName 空间维度名称。空间维度可以由多个其他维度构成,也可以作为事件的一部分存在。如果空间维度已经存在,则它必须是一个坐标值数组。
dims 组成空间维度的维度名称列表。

空间过滤器语法

"filter" : {
    "type": "spatial",
    "dimension": "spatialDim",
    "bound": {
        "type": "rectangular",
        "minCoords": [10.0, 20.0],
        "maxCoords": [30.0, 40.0]
    }
}

绑定类型

  • rectangular

    属性 描述 必要
    minCoords 坐标[x,y,z,…]的最小尺寸坐标列表
    maxCoords 坐标[x,y,z,…]的最大尺寸坐标列表
  • radius

    属性 描述 必要
    座标 原点坐标形式为[x,y,z,…]
    半径 浮点半径值
  • polygon

    属性 描述 必要
    横坐标 多边形角的水平坐标
    纵坐标 多边形角的垂直坐标
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容