实时数据捕获的百分位数

时间:2009-08-08 12:56:21

标签: algorithm response-time percentile resampling

我正在寻找一种确定实时数据捕获百分位数的算法。

例如,考虑开发服务器应用程序。

服务器的响应时间可能如下: 17毫秒 33毫秒 52毫秒 60毫秒 55毫秒 等

报告第90百分位响应时间,第80百分位响应时间等非常有用。

朴素算法是将每个响应时间插入列表中。请求统计信息时,对列表进行排序并将值放在适当的位置。

内存使用量与请求数呈线性关系。

是否有一种算法可以在内存使用量有限的情况下产生“近似”百分位数统计量?例如,假设我想以一种处理数百万个请求的方式来解决这个问题,但只想使用一千字节的内存进行百分位跟踪(丢弃旧请求的跟踪不是一个选项,因为百分位数应该是满足所有要求)。

还要求没有分发的先验知识。例如,我不想提前指定任何范围的桶。

7 个答案:

答案 0 :(得分:31)

如果您希望在获得越来越多的数据时保持内存使用量不变,那么您将不得不以某种方式resample获取该数据。这意味着您必须应用某种rebinning方案。你可以等到你在开始重组之前获得一定数量的原始输入,但你无法完全避免它。

所以你的问题实际上是在问“动态分类数据的最佳方法是什么”?有很多方法,但如果你想最小化你可能会收到的值的范围或分布的假设,那么一个简单的方法是平均固定大小 k 的桶,具有对数分布的宽度。例如,假设您希望一次在内存中保存1000个值。选择 k 的大小,例如100.选择最小分辨率,比如1ms。然后

  • 第一个桶处理0-1ms(宽度= 1ms)
  • 之间的值
  • 第二桶:1-3ms(w = 2ms)
  • 第三桶:3-7毫秒(w = 4毫秒)
  • 第四桶:7-15ms(w = 8ms)
  • ...
  • 第十桶:511-1023ms(w = 512ms)

这种类型的log-scaled方法类似于hash table algorithms中使用的分块系统,由某些文件系统和内存分配算法使用。当您的数据具有较大的动态范围时,它可以很好地工作。

随着新值的出现,您可以根据需要选择重新取样的方式。例如,您可以跟踪moving average,使用first-in-first-out或其他更复杂的方法。有关一种方法(由Kademlia使用),请参阅Bittorrent算法。

最终,重组必须失去一些信息。您对分箱的选择将决定丢失哪些信息的具体细节。另一种说法是,恒定大小的内存存储意味着在dynamic rangesampling fidelity之间进行权衡;你如何做出这种权衡取决于你,但是就像任何抽样问题一样,没有解决这个基本事实。

如果你真的对利弊感兴趣,那么在这个论坛上没有答案就足够了。你应该研究sampling theory。关于这个主题的研究很多。

对于它的价值,我怀疑你的服务器时间将具有相对较小的动态范围,因此更宽松的缩放以允许更高的常用值采样可以提供更准确的结果。

修改:要回答您的评论,以下是一个简单的分箱算法示例。

  • 您可以在10个分档中存储1000个值。因此每个箱保持100个值。假设每个bin都实现为动态数组('perl'或Python术语中的'list')。
  • 当有新值出现时:

    • 根据您选择的bin限制确定应该存储哪个bin。
    • 如果bin未满,请将值附加到bin列表。
    • 如果bin已满,请删除bin列表顶部的值,并将新值附加到bin列表的底部。这意味着旧的价值观随着时间的推移而被抛弃。
  • 要查找第90个百分位数,请对bin 10进行排序。第90个百分位数是排序列表中的第一个值(元素900/1000)。

如果您不喜欢丢弃旧值,那么您可以实现一些替代方案来代替使用。例如,当bin变满(在我的示例中达到100个值)时,您可以取最旧的50个元素(即列表中的前50个)的平均值,丢弃这些元素,然后将新的平均元素追加到垃圾箱,留下51个元素的箱子,现在有空间容纳49个新值。这是重组的一个简单例子。

另一个重组的例子是downsampling;例如,丢弃排序列表中的每第5个值。

我希望这个具体的例子有所帮助。要点的重点是有很多方法可以实现恒定的内存老化算法;只有你能根据自己的要求决定什么是满意的。

答案 1 :(得分:18)

我刚刚发布了blog post on this topic。基本思路是减少对精确计算的要求,支持“95%的响应需要500ms-600ms或更短”(对于所有精确百分位数为500ms-600ms)

你可以使用任意数量的任意大小的桶(例如0ms-50ms,50ms-100ms,......只适合你的用例)。通常,对于在最后一个桶中超过特定​​响应时间(例如,对于web应用程序为5秒)的所有请求(即> 5000ms)应该不是问题。

对于每个新捕获的响应时间,您只需为其所属的桶增加一个计数器。为了估计第n个百分位数,所需要的只是总计计数器,直到总和超过总数的百分之n。

这种方法每个桶只需要8个字节,允许跟踪具有1K内存的128个桶。绰绰有余地使用50ms的粒度分析Web应用程序的响应时间。

作为一个例子,这里是Google Chart我用1小时捕获的数据创建的(使用60个计数器,每桶200ms):

enter image description here

好的,不是吗? :) Read more on my blog

答案 2 :(得分:17)

我相信这个问题有很多好的近似算法。一个好的第一步方法是简单地使用固定大小的数组(比如说1K的数据)。修正一些概率p。对于每个请求,以概率p,将其响应时间写入数组(替换其中的最旧时间)。由于数组是实时流的子采样,并且由于子采样保留了分布,因此对该数组进行统计将为您提供完整实时流的统计数据的近似值。

这种方法有几个优点:它不需要先验信息,而且编码也很容易。您可以快速构建它,并通过实验确定,对于您的特定服务器,增长缓冲区对答案的影响可以忽略不计。这就是近似值足够精确的点。

如果您发现需要太多内存来为您提供足够精确的统计信息,那么您将需要进一步挖掘。好的关键词是:“流计算”,“流统计”,当然还有“百分位数”。你也可以尝试“愤怒和诅咒”的方法。

答案 3 :(得分:15)

(自问这个问题以来已经有一段时间了,但我想指出一些相关的研究论文)

过去几年对数据流的近似百分位进行了大量研究。一些有趣的论文,包含完整的算法定义:

所有这些论文都提出了具有亚线性空间复杂度的算法,用于计算数据流上的近似百分位数。

答案 4 :(得分:4)

尝试在“同时估计几个百分位数的顺序程序”(Raatikainen)一文中定义的简单算法。速度快,需要2 * m + 3个标记(m百分位数),并且可以快速准确逼近。

答案 5 :(得分:2)

使用大整数或其他东西的动态数组T [],其中T [n]计算响应时间为n毫秒的数字。如果您确实在服务器应用程序上进行统计,那么可能250毫秒的响应时间是您的绝对限制。因此,对于0到250之间的每个ms,您的1 KB保持一个32位整数,并且您有足够的余地用于溢出bin。 如果你想要更多垃圾箱的东西,可以使用8位数作为1000个垃圾箱,并且计数器溢出的那一刻(即在该响应时间的第256个请求)你将所有箱中的位向下移动1.(有效地将值减半)所有垃圾箱)。这意味着您可以忽略所有垃圾箱,这些垃圾箱捕获的垃圾邮件数量少于最常访问垃圾箱的延迟的1/127。

如果你真的,真的需要一套特定的垃圾箱我建议使用第一天的请求来提出一套合理的固定垃圾箱。在一个对性能敏感的实时应用程序中,任何动态都是非常危险的。如果您选择该路径,您最好知道自己在做什么,或者有一天您会从床上起来解释为什么您的统计跟踪器突然在生产服务器上占用90%的CPU和75%的内存。

至于其他统计数据:对于均值和方差,有一些nice recursive algorithms占用很少的内存。这两个统计量对于许多分布本身就足够有用,因为central limit theorem表示由足够多的自变量引起的分布接近正态分布(由均值和方差完全定义)可以使用最后N个normality tests中的一个(其中N足够大但受你的记忆要求约束)来监视正常性的假设是否仍然成立。

答案 6 :(得分:0)

您可以尝试以下结构:

接受输入n,即。 n = 100

我们将保留一个范围数组 [min, max],按 mincount 排序。

插入值 x - xmin 范围的二分搜索。如果未找到,则取之前的范围(其中 min < x)。如果值属于范围 (x <= max),则递增 count。否则用 [min = x, max = x, count = 1] 插入新范围。

如果范围数达到 2*n – 通过从奇数和 中取 min 将数组折叠/合并为 n(一半) >max 来自偶数个条目,总结它们的 计数

得到即。 p95 从末尾开始求和计数,直到下一次加法达到阈值 sum >= 95%,取 p95 = min + (max - min) * 部分

它将确定测量的动态范围。 n 可以修改以换取内存的准确性(在较小程度上是 cpu)。如果您使值更加离散,即。通过在插入前四舍五入到 0.01 - 它会更快地稳定在范围内。

您可以通过不假设每个范围包含均匀分布的条目来提高准确性,即。一些便宜的东西,比如值的总和,它会给你 avg = sum / count,这将有助于从它所在的范围读取更接近的 p95 值。

您也可以旋转它们,即。在 m = 1 000 000 条目开始填充新数组后,将 p95 作为数组中计数的加权和(如果数组 B 有 A 的 10% 的计数,则它对 p95 值的贡献为 10%)。