如何在Cassandra中获得X%的百分比

时间:2018-09-27 03:08:44

标签: cassandra cql cassandra-3.0

考虑具有以下结构的表:

CREATE TABLE statistics (name text, when timestamp, value int, 
PRIMARY KEY ((name, when)));

例如,按名称计算50%值百分位数的最佳方法是什么? 我想到了:

a)编写自定义聚合函数+查询,如:

SELECT PERCENTILE(value, 0.5) FROM statistics WHERE name = '...'

b)首先按名称计数元素

SELECT COUNT(value) FROM statistics WHERE name = '...'

然后按值升序对第(0.5 / count)行值进行分页。说,如果计数为100,它将排在第50行。

c)您的想法

我不确定案例A是否可以处理任务。当行数奇数时,情况B可能会很棘手。

1 个答案:

答案 0 :(得分:4)

只要您始终提供name-如果不指定分区并将所有内容都放在一个分区中,此请求可能会非常昂贵。我假设您的意思是表中的((name), when)而不是((name, when)),否则如果没有全表扫描(使用hadoop或spark),您的询问是不可能的。

UDA可以使用-但除非您愿意接受近似值,否则它可能会很昂贵。要使其完全准确,您需要进行2次传递(即做一次计数,而不是第二次传递才能将X放入集合中,但是由于没有隔离,因此也不是完美的)。因此,如果您需要非常准确的方法,那么最好的选择是在计算前仅在本地拉整个statistics[name]分区,或者让UDA在地图中建立整个集合(或多数)(如果分区变大则不建议使用) 。即:

CREATE OR REPLACE FUNCTION all(state tuple<double, map<int, int>>, val int, percentile double)
  CALLED ON NULL INPUT RETURNS tuple<double, map<int, int>> LANGUAGE java AS '
java.util.Map<Integer, Integer> m = state.getMap(1, Integer.class, Integer.class);
m.put(m.size(), val);
state.setMap(1, m);
state.setDouble(0, percentile);
return state;';

CREATE OR REPLACE FUNCTION calcAllPercentile (state tuple<double, map<int, int>>)
  CALLED ON NULL INPUT RETURNS int LANGUAGE java AS 
  'java.util.Map<Integer, Integer> m = state.getMap(1, Integer.class, Integer.class);
  int offset = (int) (m.size() * state.getDouble(0));
  return m.get(offset);';

CREATE AGGREGATE IF NOT EXISTS percentile (int , double) 
  SFUNC all STYPE tuple<double, map<int, int>>
  FINALFUNC calcAllPercentile
  INITCOND (0.0, {});

如果愿意接受一个近似值,则可以使用一个采样池,比如说您存储了1024个元素,并且当UDA获取元素时,您以降低的统计机会替换其中的元素。 (vitter's algorithm R)这很容易实现,并且如果您的数据集预期具有正态分布,则将为您提供一个不错的近似值。如果您的数据集不是正态分布,则可能相差很远。对于正态分布,实际上实际上还有很多其他选择,但是我认为R在UDA中最容易实现。喜欢:

CREATE OR REPLACE FUNCTION reservoir (state tuple<int, double, map<int, int>>, val int, percentile double)
  CALLED ON NULL INPUT RETURNS tuple<int, double, map<int, int>> LANGUAGE java AS '
java.util.Map<Integer, Integer> m = state.getMap(2, Integer.class, Integer.class);
int current = state.getInt(0) + 1;
if (current < 1024) {
    // fill the reservoir
    m.put(current, val);
} else {
    // replace elements with gradually decreasing probability
    int replace = (int) (java.lang.Math.random() * (current + 1));
    if (replace <= 1024) {
        m.put(replace, val);
    }
}
state.setMap(2, m);
state.setDouble(1, percentile);
state.setInt(0, current);
return state;';

CREATE OR REPLACE FUNCTION calcApproxPercentile (state tuple<int, double, map<int, int>>)
  CALLED ON NULL INPUT RETURNS int LANGUAGE java AS 
  'java.util.Map<Integer, Integer> m = state.getMap(2, Integer.class, Integer.class);
  int offset = (int) (java.lang.Math.min(state.getInt(0), 1024) * state.getDouble(1));
  if(m.get(offset) != null)
      return m.get(offset);
  else
      return 0;';

CREATE AGGREGATE IF NOT EXISTS percentile_approx (int , double) 
  SFUNC reservoir STYPE tuple<int, double, map<int, int>>
  FINALFUNC calcApproxPercentile
  INITCOND (0, 0.0, {});

在上面,百分位数功能会更快变慢,使用采样器的大小可能会或多或少地给您带来准确性,但是太大了,您会开始影响性能。通常,超过10k值的UDA(甚至是简单的功能,例如count)都会开始失败。同样重要的是要在这些情况下认识到,尽管单个查询返回单个值,但要花费大量的工作。因此,许多此类查询或大量并发性会给您的协调员带来很大压力。对于CASSANDRA-10783

,确实需要> 3.8(我建议3.11.latest +)

注意:我不保证在示例UDA中没有遗漏过1的错误-我没有进行全面测试,但应该足够接近,您才能从那里开始工作