当前位置: 首页 > 面试题库 >

ElasticSearch Nest。术语聚合及其迭代的更好代码

邓卓
2023-03-14
问题内容

我想获取给定期间内唯一数字用户ID的列表。

假设字段为userId,时间字段为startTime,我成功获得如下结果;

HashSet<int> hashUserIdList= new HashSet<int>(); // guarantees to store unique userIds.

// Step 1. get unique number of userIds
var total = client.Search<Log>(s => s
    .Query(q => q
        .DateRange(c => c.Field(p => p.startTime)
        .GreaterThan(FixedDate)))
        .Aggregations(a => a
            .Cardinality("userId_cardinality", c => c
                .Field("userId"))))
    .Aggs.Cardinality("userId_cardinality");

int totalCount = (int)total.Value;

// Step 2. get unique userId values by Terms aggregation.
var response = client.Search<Log>(s => s
    .Source(source => source.Includes(inc => inc.Field("userId")))
    .Query(q => q
        .DateRange(c => c.Field(p => p.startTime)
        .GreaterThan(FixedDate)))
    .Aggregations(a => a
        .Terms("userId_terms", c => c
            .Field("userId").Size(totalCount))))
    .Aggs.Terms("userId_terms");

// Step 3. store unique userIds to HashSet.
foreach (var element in response.Buckets)
{
    hashUserIdList.Add(int.Parse(element.Key));
}

可以工作,
但效率不高,因为(1)totalCount首先获取,并且(2)它定义Size(totalCount)由于存储桶溢出(如果结果有成千上万个),可能导致500个服务器错误。

以某种foreach方式进行迭代会很好,但是我无法使它们按大小迭代100。我在这里放了From/ SizeSkip/
Take,但是返回值不可靠。

如何正确编码?


问题答案:

对于某些集合,此方法可能是可行的,但需要注意以下几点:

  1. 基数聚合使用HyperLogLog ++算法来 近似 基数。对于低基数字段,此近似值可能完全准确,而对于高基数字段,则近似值不那么准确。
  2. 术语对于 许多 术语而言,聚合可能在计算上很昂贵,因为每个存储桶都需要构建在内存中,然后序列化以响应。

您可能可以跳过基数汇总来获取大小,而只需将其int.MaxValue作为术语汇总的大小即可。在速度方面效率较低的另一种方法是滚动浏览范围内的所有文档,使用源过滤器仅返回您感兴趣的字段。我希望使用Scroll方法可以减轻群集的压力,但我建议您监视您采用的任何方法。

这是对Stack Overflow数据集(2016年6月,IIRC)上这两种方法的比较,研究了两年前的今天和一年前的今天的独特提问者。

术语汇总

void Main()
{
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

    var connectionSettings = new ConnectionSettings(pool)
        .MapDefaultTypeIndices(d => d
            .Add(typeof(Question), NDC.StackOverflowIndex)
        );


    var client = new ElasticClient(connectionSettings);

    var twoYearsAgo = DateTime.UtcNow.Date.AddYears(-2);
    var yearAgo = DateTime.UtcNow.Date.AddYears(-1);

    var searchResponse = client.Search<Question>(s => s
        .Size(0)
        .Query(q => q
            .DateRange(c => c.Field(p => p.CreationDate)
                .GreaterThan(twoYearsAgo)
                .LessThan(yearAgo)
            )
        )
        .Aggregations(a => a
            .Terms("unique_users", c => c
                .Field(f => f.OwnerUserId)
                .Size(int.MaxValue)
            )
        )
    );

    var uniqueOwnerUserIds = searchResponse.Aggs.Terms("unique_users").Buckets.Select(b => b.KeyAsString).ToList();

    // 3.83 seconds
    // unique question askers: 795352
    Console.WriteLine($"unique question askers: {uniqueOwnerUserIds.Count}");
}

滚动API

void Main()
{
    var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

    var connectionSettings = new ConnectionSettings(pool)
        .MapDefaultTypeIndices(d => d
            .Add(typeof(Question), NDC.StackOverflowIndex)
        );

    var client = new ElasticClient(connectionSettings);
    var uniqueOwnerUserIds = new HashSet<int>();

    var twoYearsAgo = DateTime.UtcNow.Date.AddYears(-2);
    var yearAgo = DateTime.UtcNow.Date.AddYears(-1);

    var searchResponse = client.Search<Question>(s => s
        .Source(sf => sf
            .Include(ff => ff
                .Field(f => f.OwnerUserId)
            )
        )
        .Size(10000)
        .Scroll("1m")
        .Query(q => q
            .DateRange(c => c
                .Field(p => p.CreationDate)
                .GreaterThan(twoYearsAgo)
                .LessThan(yearAgo)
            )
        )
    );

    while (searchResponse.Documents.Any())
    {
        foreach (var document in searchResponse.Documents)
        {
            if (document.OwnerUserId.HasValue)
                uniqueOwnerUserIds.Add(document.OwnerUserId.Value);
        }

        searchResponse = client.Scroll<Question>("1m", searchResponse.ScrollId);
    }

    client.ClearScroll(c => c.ScrollId(searchResponse.ScrollId));

    // 91.8 seconds
    // unique question askers: 795352
    Console.WriteLine($"unique question askers: {uniqueOwnerUserIds.Count}");
}

术语汇总比Scroll API方法快24倍。



 类似资料:
  • 抱歉,如果已经问过了,但一直潜伏在SO周围,找不到任何适合我需要的东西。 基本上,我在使用ES的第一次快速尝试中试图实现的是在术语聚合中添加更多计数器。 快速尝试一下,我将以下请求发送给ES。 我现在得到的只是样本在文档中显示的内容。 但是,我真的不知道如何在桶中包含更多的内部聚合。会导致这样的文档的东西。 我应该如何构造聚合,以便按桶包含这些聚合?

  • 问题内容: 我正在尝试使用以下查询对以下数据进行elasticsearch来执行术语聚合,输出将名称分解为标记(请参见下面的输出)。因此,我尝试将os_name映射为multi_field,但现在无法通过它查询。是否可以有没有令牌的索引?例如“ Fedora Core”? 查询: 数据: 输出: 映射: 问题答案: 实际上,您应该像这样更改映射 并且您的aggs应该更改为:

  • 问题内容: 我只是想知道内部和外部迭代的真正好处是什么,以及为什么使用内部操作更好(至少是我所听到的)。在对集合进行内部迭代时,是否还可以删除集合的元素?就像在代码示例中一样: 我知道内部迭代的代码可读性更好,但是还有其他一些好处,例如性能改进? 问题答案: 您的情况有些简单,因为您可以简单地使用resp。而是使用内部迭代的替代方法,它也可以处理更复杂的条件。 在的情况下,这将立即显示内部迭代的优

  • 问题内容: 索引文件如下: 我想要的是按平台计数和输出统计信息。为了进行计数,我可以将术语聚合作为字段进行计数: 这样,我就可以像预期那样将统计数据作为多个存储桶接收到。 现在,我还能以某种方式添加到这些存储桶中吗(以及用于统计的漂亮输出)?我附带的最好的看起来像: 实际上,它可以工作,并且在每个存储桶中返回非常复杂的结构: 当然,可以从此结构中提取平台的名称和网址(例如),但是是否有更干净,更简

  • 索引文档如下: 我想要的是按平台计数和输出统计信息。对于计数,我可以使用带有的术语聚合作为字段进行计数: 通过这种方式,我以多个bucket的形式接收统计数据,看起来就像预期的那样{key:8,doc\u count:162511}。 现在,我可以以某种方式将和(用于漂亮的统计输出)添加到这些桶中吗?我带来的最好的看起来像: 实际上,它可以工作,并在每个桶中返回非常复杂的结构: 当然,平台的名称和

  • 我使用聚合从嵌套字段收集数据并卡住了一点 文件示例: ES允许通过rectangle.attributes._id来分组数据,但是有没有办法让一些“其他”桶把没有添加到任何组中的文档放在那里?或者,也许有一种方法可以通过创建查询来为文档创建桶。我认为桶将是完美的,因为我需要使用“其他”文档进行进一步的聚合。或者也许有一些很酷的解决方法 我使用这样的查询进行聚合 然后得到这个结果 这样的结果将是完美