Cassandra 中的范围查询

range query in Cassandra

我正在使用 Cassandra 2.1.2 和相应的 DataStax Java 驱动程序以及 DataStax 提供的对象映射。

以下 table 定义:

CREATE TABLE IF NOT EXISTS ses.tim (id text PRIMARY KEY, start bigint, cid int);

映射:

@Table(keyspace = "ses", name = "tim")
class MyObj {
    @PartitionKey
    private String id;
    private Long start;
    private int cid;
}

访问者

@Accessor
interface MyAccessor {
    @Query("SELECT * FROM ses.tim WHERE id = :iid")
    MyObj get(@Param("iid") String id);

    @Query(SELECT * FROM ses.tim WHERE start <= :sstart")
    Result<MyObj> get(@Param("sstart") long start);
}

如访问器中所示,我想查询 returns 'start' 小于或等于特定值的所有内容。

根据 table 的这个定义,这是不可能的。因此我尝试创建二级索引:

CREATE INDEX IF NOT EXISTS myindex ON ses.tim (start);

这似乎不太有效(我读了很多解释为什么它决定不支持这个,但我仍然不明白为什么有人会给出这样的限制,无论如何..)

因此,据我了解,我们必须在 WHERE 子句中至少有一个 equals

@Query(SELECT * FROM ses.tim WHERE cid = :ccid AND start <= :sstart")

CREATE INDEX IF NOT EXISTS myindex2 ON ses.tim (cid);

如果这行得通,我将不得不知道 cid 的所有可能值,并分别查询它们并在客户端上完成其余的...但我得到的错误是

Cannot execute this query as it might involve data filtering and thus may have unpredictable performance

然后我尝试了

id text, start bigint, cid int, PRIMARY KEY (id, start, cid)

@Table(keyspace = "ses", name = "tim")
class MyObj {
    @PartitionKey
    private String id;
    @ClusteringColumn(0)
    private Long start;
    @ClusteringColumn(1)
    private int cid;
}

但仍然没有运气。

此外,我尝试将'start'设置为PartitionKey,但只能再次使用Equals查询...

我错过了什么?我怎样才能获得此类查询的结果?

编辑:版本已更新以更正一个

I'm using Cassandra 2.1.3

我认为 2.1.3 尚未发布。 project site 当前显示 2.1.2 为最高版本。

据我所知,您的主要问题是您的分区键 id 要么是唯一的,要么基数太大而对您无用。目前,您正在采用 RDBMS 风格的方法来存储数据(通过唯一 ID)。使用 Cassandra,您希望以一种便于查询的方式存储数据。第一步是选择一个好的键来对数据进行分区。

Therefore I tried creating a secondary index

您不想在这里做的另一件事是使用二级索引。我看得出来你很想这样做,你应该马上把这个想法从脑海中赶走。二级索引是为方便起见而创建的。它们不是为了性能而创建的,也不是为了在数据模型上使用快捷方式而创建的。

Cannot execute this query as it might involve data filtering and thus
may have unpredictable performance.

说到诱惑,看到此消息时,您可能会考虑尝试将 ALLOW FILTERING 添加到您的查询中。绝对不要那样做。它直截了当地警告你,这样做效果不佳,你应该注意这个警告。

if this would work I would have to know ALL possible values for cid, and query them separately and do the rest on the client.

cid有多独特?如果必须获取并遍历所有 cid 太麻烦,那么您应该考虑 picking/creating 一个不太独特的值来进行分区。但是,假设 cid 可以工作,那么您的 table 定义应该是这样的:

CREATE TABLE IF NOT EXISTS ses.tim 
(cid int,
 start bigint,
 id text,
 PRIMARY KEY ((cid),start);

@Table(keyspace = "ses", name = "tim")
class MyObj {
    @PartitionKey
    private int cid;
    @ClusteringColumn(0)
    private Long start;
    private String id;
}

有了这个基础 table 定义,这个查询现在应该可以工作了。

@Query("SELECT * FROM ses.tim WHERE cid = :ccid AND start <= :sstart")

再看看您的数据模型,并且(如果 cid 不是很独特)看看您是否可以想出一个更好的列来对数据进行分组。有关详细信息,请通读 Patrick McFadin 的文章 Getting Started With Time Series Data Modeling。他讨论了一些与您有些相似的用例,可能会为您指明正确的方向。

如果您对同一组数据有不同的查询能力需求,您可以考虑对数据进行反规范化。根据您的问题,听起来您想要以下内容:

  • id
  • 查询
  • 查询 start < X

第一个查询工作正常,正如您在当前架构中指出的那样。但是,如果没有二级索引,第二个查询将无法正常工作,由于您已经调查过的原因,二级索引会很慢(我总是指向 this blog post 关于二级索引。

您表示不想在 cid 上进行分区,因为您需要知道 cid 的所有可能值。

我能想到的三个想法:

  • 使用虚拟主键创建一个单独的 table,这样您的所有数据都存储在同一个分区中。但是,如果您有许多条目创建一个超宽分区并且在任何节点上保存该数据都存在热点,那么这可能会有问题。你打算有多少?

    create table if not exists tim (
        dummy int, 
        start bigint, 
        id text, 
        cid int, 
        primary key (dummy, start)
    );
    

    然后您可以进行如下查询:

    select * from tim where dummy=0 and start <= 10;
    
  • 另一种选择是在您的原始 table 上使用 ALLOW FILTERING,这仍然会执行昂贵的范围查询并过滤数据。

    select * from tim where start <= 10 ALLOW FILTERING;
    
  • 另一种选择是使用 spark-connector 之类的东西来创建进行查询的 spark 作业。连接器会将昂贵的范围查询分解为更小的任务并将数据映射到 RDD,使您能够灵活地进行更复杂的查询并获得良好的性能。