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

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

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

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

例如,按名称计算50%值百分比的最佳方法是什么?我想了想:
a) 编写自定义聚合函数+查询,如:

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

b) 先按名称计算元素

  1. 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中建立整个集合(或多数)(如果分区太大,则不建议这样做)。即:

  1. CREATE OR REPLACE FUNCTION all(state tuple<double, map<int, int>>, val int, percentile double)
  2. CALLED ON NULL INPUT RETURNS tuple<double, map<int, int>> LANGUAGE java AS '
  3. java.util.Map<Integer, Integer> m = state.getMap(1, Integer.class, Integer.class);
  4. m.put(m.size(), val);
  5. state.setMap(1, m);
  6. state.setDouble(0, percentile);
  7. return state;';
  8. CREATE OR REPLACE FUNCTION calcAllPercentile (state tuple<double, map<int, int>>)
  9. CALLED ON NULL INPUT RETURNS int LANGUAGE java AS
  10. 'java.util.Map<Integer, Integer> m = state.getMap(1, Integer.class, Integer.class);
  11. int offset = (int) (m.size() * state.getDouble(0));
  12. return m.get(offset);';
  13. CREATE AGGREGATE IF NOT EXISTS percentile (int , double)
  14. SFUNC all STYPE tuple<double, map<int, int>>
  15. FINALFUNC calcAllPercentile
  16. INITCOND (0.0, {});

如果你愿意接受一个近似值,你可以使用一个采样库,假设你存储了1024个元素,当你的uda得到元素时,你就用一个递减的概率替换其中的元素(vitter的算法r)这是非常容易实现的,如果你的数据集被期望有一个正态分布,它将给你一个不错的近似值。如果您的数据集不是正态分布,这可能会非常遥远。对于正态分布,实际上还有很多其他的选择,但我认为在uda中r是最容易实现的。比如:

  1. CREATE OR REPLACE FUNCTION reservoir (state tuple<int, double, map<int, int>>, val int, percentile double)
  2. CALLED ON NULL INPUT RETURNS tuple<int, double, map<int, int>> LANGUAGE java AS '
  3. java.util.Map<Integer, Integer> m = state.getMap(2, Integer.class, Integer.class);
  4. int current = state.getInt(0) + 1;
  5. if (current < 1024) {
  6. // fill the reservoir
  7. m.put(current, val);
  8. } else {
  9. // replace elements with gradually decreasing probability
  10. int replace = (int) (java.lang.Math.random() * (current + 1));
  11. if (replace <= 1024) {
  12. m.put(replace, val);
  13. }
  14. }
  15. state.setMap(2, m);
  16. state.setDouble(1, percentile);
  17. state.setInt(0, current);
  18. return state;';
  19. CREATE OR REPLACE FUNCTION calcApproxPercentile (state tuple<int, double, map<int, int>>)
  20. CALLED ON NULL INPUT RETURNS int LANGUAGE java AS
  21. 'java.util.Map<Integer, Integer> m = state.getMap(2, Integer.class, Integer.class);
  22. int offset = (int) (java.lang.Math.min(state.getInt(0), 1024) * state.getDouble(1));
  23. if(m.get(offset) != null)
  24. return m.get(offset);
  25. else
  26. return 0;';
  27. CREATE AGGREGATE IF NOT EXISTS percentile_approx (int , double)
  28. SFUNC reservoir STYPE tuple<int, double, map<int, int>>
  29. FINALFUNC calcApproxPercentile
  30. INITCOND (0, 0.0, {});

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

展开查看全部

相关问题