get_range_slices和CQL查询处理,需要ALLOW FILTERING

时间:2013-10-16 16:42:33

标签: cassandra cql cql3

我有一个以下的CQL表(为了清晰起见,有点简化):

CREATE TABLE test_table (
    user        uuid,
    app_id      ascii,
    domain_id   ascii,
    props       map<ascii,blob>,
    PRIMARY KEY ((user), app_id, domain_id)
)

这个想法是这个表包含许多用户(即行数,例如数十亿)。对于每个用户,会有一些感兴趣的域,每个域会有一些应用程序。对于每个用户/域/应用程序,都会有一小组属性。

我需要扫描整个表并将其内容加载到给定的app_id和domain_id的块中。我的想法是使用TOKEN函数能够在几次迭代中读取整个数据集。所以,像这样:

SELECT props FROM test_table WHERE app_id='myapp1'
  AND domain_id='mydomain1'
  AND TOKEN(user) > -9223372036854775808
  AND TOKEN(user) < 9223372036854775807;

我假设这个查询是有效的,因为我指定了行键的范围,并通过指定聚类键的值,我有效地指定了列范围。但是当我尝试运行此查询时,我收到错误消息“错误请求:无法执行此查询,因为它可能涉及数据过滤,因此可能具有不可预测的性能。如果您想执行此查询,尽管性能不可预测,请使用ALLOW FILTERING”

我对Cassandra的经验有限,我认为这种查询会映射到get_range_slices()调用,它接受切片谓词(即我的app_id / domain_id值定义的列范围)和由此定义的键范围我的令牌范围。似乎我误解了这种查询是如何处理的,或者我误解了get_range_slices()调用的效率。

更具体地说,我的问题是: - 如果这个数据模型对我想到的查询类型有意义 - 如果预期此查询有效 - 如果它有效,那么为什么我收到此错误消息要求我允许过滤

我对最后一个的唯一猜测是,需要从结果中跳过没有给定app_id / domain_id组合的行。

---更新----

感谢所有评论。我一直在对此进行更多研究,但仍有一些我不太了解的事情。

在给定的结构中,我想要得到的就像我的数据集中的矩形区域(假设所有行都具有相同的列)。矩形的顶部和底部由标记范围(范围)确定,左/右边定义为列范围(切片)。因此,这应该自然地转换为get_range_slices请求。我的理解(纠正我,如果我错了)CQL要求我放置ALLOW FILTERING子句的原因是因为会有不包含我要查找的列的行,所以必须跳过它们。并且由于没有人知道在找到符合我的标准(在给定范围内)之前是否必须跳过每隔一行或第一百万行 - 这就是导致不可预测的延迟甚至可能超时的原因。我对吗?我曾尝试编写一个执行相同类型查询但使用低级Astyanax API的测试(在同一个表中,我必须读取使用CQL生成的数据,结果证明非常简单)并且此测试确实有效 - 除了它返回没有列的键,其中行不包含我要求的列片。当然,我必须基于起始令牌实现某种简单的分页,并限制以小块的形式获取数据。

现在我想知道 - 再次,考虑到我需要与数十万用户打交道:部分“旋转”这个表并将其组织起来会更好:

行键:domain_id + app_id +分区号(类似哈希(用户)mod X) 聚类键:列分区号(类似哈希(用户)&gt;&gt; 16 mod Y)+用户

对于“列分区号”...我不确定是否真的需要它。我假设如果我使用这个模型,我将为每个域+应用程序组合提供相对较少的行数(X = 1000..10000)。如果我愿意,这将允许我查询各个分区,即使是并行查询。但是(假设用户是随机UUID)对于100M用户而言,每行将产生数十或数十万列。在一个请求中读取这样一行是不是一个好主意?我相信它应该为Cassandra造成一些记忆压力。所以也许分组阅读(比如,Y = 10..100)会更好吗?

我意识到我要做的不是Cassandra做得好 - 读取可以预先计算的块中的“全部”或大量CF数据子集(如令牌范围或分区键),以便从不同的地方取出主机。但我试图找到一种对这种用例最有效的模式。

顺便说一句,查询如“select * from ... where TOKEN(user)&gt; X和TOKEN(user)

2 个答案:

答案 0 :(得分:5)

简短回答

此警告意味着Cassandra必须读取非索引数据并过滤掉不符合条件的行。如果您将ALLOW FILTERING添加到查询结尾,它会起作用,但它会扫描大量数据:

SELECT props FROM test_table 
WHERE app_id='myapp1' 
AND domain_id='mydomain1' 
AND TOKEN(user) > -9223372036854775808 
AND TOKEN(user) < 9223372036854775807
ALLOW FILTERING;

更长的解释

在您的示例中,主键由两部分组成:user用作分区键,<app_id, domain_id>表单剩余部分。不同用户的行分布在整个集群中,每个节点负责特定范围的令牌环。

单个节点上的行按分区键的哈希值排序(在您的示例中为token(user))。单个用户的不同行存储在单个节点上,按<app_id, domain_id>元组排序。

因此,主键形成树状结构。分区键添加一级层次结构,主键的每个剩余字段添加另一个。默认情况下,Cassandra仅处理从树的连续范围返回所有行的查询(如果使用key in (...)构造,则处理多个范围)。如果Cassandra应过滤掉某些行,则必须指定ALLOW FILTERING

需要ALLOW FILTERING的示例查询:

SELECT * FROM test_table 
WHERE user = 'user1'; 
//OK, returns all rows for a single partition key

SELECT * FROM test_table 
WHERE TOKEN(user) > -9223372036854775808 
AND TOKEN(user) < 9223372036854775807; 
//OK, returns all rows for a continuos range of the token ring

SELECT * FROM test_table 
WHERE user = 'user1'
AND app_id='myapp1'; 
//OK, the rows for specific user/app combination 
//are stored together, sorted by domain_id field

SELECT * FROM test_table 
WHERE user = 'user1'
AND app_id > 'abc' AND app_id < 'xyz'; 
//OK, since rows for a single user are sorted by app

执行的示例查询需要ALLOW FILTERING

SELECT props FROM test_table 
WHERE app_id='myapp1';
//Must scan all the cluster for rows, 
//but return only those with specific app_id

SELECT props FROM test_table 
WHERE user='user1'
AND domain_id='mydomain1';
//Must scan all rows having user='user1' (all app_ids), 
//but return only those having specific domain

SELECT props FROM test_table 
WHERE user='user1'
AND app_id > 'abc' AND app_id < 'xyz'
AND domain_id='mydomain1';
//Must scan the range of rows satisfying <user, app_id> condition,
//but return only those having specific domain

怎么办?

在Cassandra中,无法在主键部分创建二级索引。选项很少,各有利弊:

  • 添加一个具有主键((app_id), domain_id, user)的单独表,并在两个表中复制必要的数据。它允许您查询特定app_id<app_id, domain_id>组合的必要数据。如果您需要查询特定域和所有应用程序 - 第三个表是必要的。此方法称为 实体化视图
  • 使用某种并行处理(hadoop,spark等)为所有应用程序/域组合执行必要的计算。由于Cassandra无论如何都需要读取所有数据,因此可能与单对数据没有太大区别。如果其他对的结果可能会被缓存供以后使用,那么可能会节省一些时间。
  • 如果您的需求可以接受查询效果,请使用ALLOW FILTERING。对于Cassandra来说,数十亿个分区键可能并不算太多。

答案 1 :(得分:2)

假设您使用的是Murmur3Partitioner(这是正确的选择),您不希望在行键上运行范围查询。对此键进行哈希处理以确定哪个节点保存该行,因此不按排序顺序存储。因此,执行此类范围查询需要完整扫描。

如果要执行此查询,则应将某些已知值存储为行键的标记,以便可以查询相等而不是范围。从您的数据看,app_id或domain_id似乎是一个不错的选择,因为在执行查询时,您似乎总是知道这些值。