如何在cassandra中获得x%百分位数

zte4gxcn  于 2021-06-15  发布在  Cassandra
关注(0)|答案(1)|浏览(442)

考虑一个具有以下结构的表:

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可能很棘手。

41zrol4v

41zrol4v1#

只要你一直提供 name -如果不指定分区并将所有内容都包含在一个分区中,则此请求可能会非常昂贵。我想你是说 ((name), when) 不是 ((name, when)) 在您的表中,否则您的要求是不可能没有完整的表扫描(使用hadoop或spark)。
uda是可行的,但它可能是昂贵的,除非你愿意接受一个近似值。为了让它完全准确,你需要做2次传球(即做一次计数,比第2次传球进入x组,但由于没有隔离,这也不会是完美的)。所以,如果你需要它的精确性,你最好的办法可能就是把整个 statistics[name] 本地分区或让uda在计算之前在Map中建立整个集合(或多数)(如果分区太大,则不建议这样做)。即:

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的算法r)这是非常容易实现的,如果你的数据集被期望有一个正态分布,它将给你一个不错的近似值。如果您的数据集不是正态分布,这可能会非常遥远。对于正态分布,实际上还有很多其他的选择,但我认为在uda中r是最容易实现的。比如:

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, {});

在上面的例子中,百分位函数会更快地变慢,玩采样器的大小可以给你或多或少的准确性,但太大,你开始影响性能。通常一个uda的值超过10k(甚至是像 count )开始失败。同样重要的是要认识到,在这些场景中,虽然单个查询返回单个值,但要获得它需要大量的工作。因此,大量的查询或大量的并发将给您的协调员带来很大的压力。对于cassandra-10783,这确实需要>3.8(我建议使用3.11.latest+)
注意:我没有承诺我没有错过示例udas中的off by 1错误-我没有完全测试,但是应该足够接近,您可以从那里开始工作

相关问题