【问题标题】:Timeout error in SELECT request with timestamp range带有时间戳范围的 SELECT 请求中的超时错误
【发布时间】:2015-10-17 10:43:48
【问题描述】:

这是我的情况:

  • VPS 服务器:
    • 1 Cassandra 数据库:
      • 键空间:“atim_cloud”
        • 表:“消息”
CREATE TABLE atim_cloud.messages (
    deviceid text,
    channelname text,
    time timestamp,
    avgsignal float,
    latitude float,
    longitude float,
    rssi float,
    snr float,
    stationid text,
    value blob,
    valuetype text,
    PRIMARY KEY ((deviceid, channelname), time)
) WITH CLUSTERING ORDER BY (time DESC)
    AND bloom_filter_fp_chance = 0.01
    AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}'
    AND comment = ''
    AND compaction = {'min_threshold': '4', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32'}
    AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
    AND dclocal_read_repair_chance = 0.1
    AND default_time_to_live = 0
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99.0PERCENTILE';
CREATE INDEX messages_deviceid_idx ON atim_cloud.messages (deviceid);
CREATE INDEX messages_channelname_idx ON atim_cloud.messages (channelname);
CREATE INDEX messages_time_idx ON atim_cloud.messages (time);

我的问题:

此表是为大量数据(数百万行)而制作的。 简单的请求可以正常工作:

SELECT * FROM messages WHERE deviceid ='1DB8D';

我得到:

 deviceid | channelname | time                     | avgsignal | latitude | longitude | rssi   | snr   | stationid | value      | valuetype
----------+-------------+--------------------------+-----------+----------+-----------+--------+-------+-----------+------------+-----------
    1DB8D |      INDEX1 | 2015-07-26 22:21:59+0200 |       9.9 |       45 |         6 | -125.5 |  9.66 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 22:11:58+0200 |      9.89 |       45 |         6 | -125.5 |  9.85 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 22:01:59+0200 |      9.87 |       45 |         6 | -123.5 | 10.08 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 21:51:59+0200 |      9.83 |       45 |         6 | -125.5 |   9.8 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 21:41:59+0200 |      9.83 |       45 |         6 | -124.5 | 10.02 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 21:31:58+0200 |       9.8 |       45 |         6 | -126.5 | 10.35 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 21:21:59+0200 |      9.78 |       45 |         6 | -122.5 |  9.91 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 21:11:59+0200 |      9.82 |       45 |         6 | -130.5 |  8.85 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 21:01:59+0200 |      9.79 |       45 |         6 | -129.5 | 10.11 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 20:51:58+0200 |      9.77 |       45 |         6 | -124.5 | 10.06 |      0E00 | 0x00000000 |       int
    1DB8D |      INDEX1 | 2015-07-26 20:41:59+0200 |      9.78 |       45 |         6 | -123.5 |  9.52 |      0E00 | 0x00000000 |       int

但是当我使用时间戳的计算进行一些更复杂的请求时,例如: (它会发生几次,但不是一直)

SELECT * FROM messages WHERE deviceid = '1DB8D' AND time >= 1437981692831 LIMIT 500 ALLOW FILTERING ;

或者简单地说:

SELECT COUNT(*) FROM messages ;

我得到(几秒钟后。我猜是超时):

errors={}, last_host=127.0.0.1

您有什么建议可以解决我的问题吗? 我正在寻找一些关于索引或主键的建议,但我没有找到任何东西。

如果您对执行此数据表有一些提示,我很高兴听到。多集群呢?我不明白它的一切。

谢谢;)

【问题讨论】:

    标签: cassandra cql cqlsh


    【解决方案1】:

    次要索引只推荐用于低基数的字段。它们对于具有高基数的字段(例如您的时间字段)非常低效。这就是为什么在查询中使用时间字段时会出现超时错误的原因。

    在 Cassandra 中,您应该专注于使用良好的主键,而不是通过创建辅助键来解决架构问题。

    【讨论】:

    • 感谢您的回答,我在 Datastax 中关于 INDEX 的文档中读到了类似的内容(高基数和索引不是朋友),所以我使用“DROP INDEX”命令按时删除了我的索引,但什么也没有发生了。二级索引是什么意思?我必须用这样的新主键重建我的表吗?我应该将我的 INDEX 保留在“时间”列上吗?:PRIMARY KEY (deviceid, channelname) 再次感谢您的快速回答。
    • 听起来您来自关系数据库背景,但由于其分布式特性,Cassandra 与 RDBMS 非常不同。这篇文章可能是一个很好的起点datastax.com/dev/blog/a-deep-look-to-the-cql-where-clause
    • 感谢您的网址。但我还是迷路了。你能直接告诉我在我的情况下我必须做什么吗?
    • 您的问题非常广泛。通常,您需要构造您的架构,以便您的查询仅使用分区键和集群列。不要创建索引。如有必要,您可能需要使用不同的键创建多个表(即使用非规范化技术)来支持您想要执行的查询。
    • 这可能意味着您需要第二个表,该表没有通道名作为键的一部分。添加数据时,会将其插入到两个表中。
    【解决方案2】:

    只是为了呼应 Jim 所说的话,但二级索引并不是真正打算成为“灵丹妙药”,用于修复数据模型的缺陷。您要做的是围绕查询构建数据模型。

    只是好奇,channelname 是干什么用的?您将它作为分区键,但看起来您真的不想通过它进行查询。您是否将其用作“存储桶”以帮助将每个分区的列数保持在 20 亿以下?还是只是为了增加独特性?

    假设你不需要它作为一个“桶”,如果我这样重组你的 PRIMARY KEY:

    PRIMARY KEY (deviceid, time, channelname)
    

    然后,此查询有效:

    aploetz@cqlsh:stackoverflow2> SELECT * FROM messages WHERE deviceid ='1DB8D';
    
     deviceid | time                     | channelname | avgsignal | latitude | longitude | rssi   | snr   | stationid | value      | valuetype
    ----------+--------------------------+-------------+-----------+----------+-----------+--------+-------+-----------+------------+-----------
        1DB8D | 2015-07-26 15:21:59-0500 |      INDEX1 |       9.9 |       45 |         6 | -125.5 |  9.66 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 15:11:58-0500 |      INDEX1 |      9.89 |       45 |         5 | -125.5 |  9.85 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 15:01:59-0500 |      INDEX1 |      9.87 |       45 |         5 | -123.5 | 10.08 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:51:59-0500 |      INDEX1 |      9.83 |       45 |         5 | -125.5 |   9.8 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:41:59-0500 |      INDEX1 |      9.83 |       45 |         5 | -124.5 | 10.02 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:31:58-0500 |      INDEX1 |       9.8 |       45 |         5 | -126.5 | 10.35 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:21:59-0500 |      INDEX1 |      9.78 |       45 |         5 | -122.5 |  9.91 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:11:59-0500 |      INDEX1 |      9.82 |       45 |         5 | -130.5 |  8.85 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:01:59-0500 |      INDEX1 |      9.79 |       45 |         5 | -129.5 | 10.11 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 13:51:58-0500 |      INDEX1 |      9.77 |       45 |         5 | -124.5 | 10.06 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 13:41:59-0500 |      INDEX1 |      9.78 |       45 |         5 | -123.5 |  9.52 |      0E00 | 0x00000000 |       int
    
    (11 rows)
    

    和这个一样:

    aploetz@cqlsh:stackoverflow2> SELECT * FROM messages WHERE deviceid ='1DB8D' AND time >='2015-07-26 14:00:00-0500';
    
     deviceid | time                     | channelname | avgsignal | latitude | longitude | rssi   | snr   | stationid | value      | valuetype
    ----------+--------------------------+-------------+-----------+----------+-----------+--------+-------+-----------+------------+-----------
        1DB8D | 2015-07-26 15:21:59-0500 |      INDEX1 |       9.9 |       45 |         6 | -125.5 |  9.66 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 15:11:58-0500 |      INDEX1 |      9.89 |       45 |         5 | -125.5 |  9.85 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 15:01:59-0500 |      INDEX1 |      9.87 |       45 |         5 | -123.5 | 10.08 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:51:59-0500 |      INDEX1 |      9.83 |       45 |         5 | -125.5 |   9.8 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:41:59-0500 |      INDEX1 |      9.83 |       45 |         5 | -124.5 | 10.02 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:31:58-0500 |      INDEX1 |       9.8 |       45 |         5 | -126.5 | 10.35 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:21:59-0500 |      INDEX1 |      9.78 |       45 |         5 | -122.5 |  9.91 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:11:59-0500 |      INDEX1 |      9.82 |       45 |         5 | -130.5 |  8.85 |      0E00 | 0x00000000 |       int
        1DB8D | 2015-07-26 14:01:59-0500 |      INDEX1 |      9.79 |       45 |         5 | -129.5 | 10.11 |      0E00 | 0x00000000 |       int
    
    (9 rows)
    

    更重要的是,它们无需二级索引或ALLOW FILTERING 指令即可工作。此 PRIMARY KEY 还支持完整的分区键 (deviceid) 查询,因此这些查询应该执行良好。

    从消息中选择计数(*);

    是的,COUNT 的 Cassandra/CQL 实现的性能几乎不如它的 RDBMS/SQL 表亲。 Apple 的 Richard Low(和 DataStax MVP)写了一篇文章 ("Counting Keys In Cassandra") 关于 COUNT 如何“在幕后”工作,这绝对值得一读。本质上,COUNT 必须遍历每一行才能为您提供该数字。因此,如果您针对大型列族(表)运行它,超时。

    注意:如果您发现自己确实需要一个存储桶(以将每个分区的列数保持在 20 亿以下),您可以选择对您的数据有意义的时间/日期增量,并且在该窗口内查询。例如,在上面,您可以将您的存储桶的日期(作为text 字段)构建您的分区键为PRIMARY KEY ((deviceid, datebucket), time, channelname),然后像这样查询:

    SELECT * FROM messages WHERE deviceid ='1DB8D' AND datebucket='20150726';
    

    也许看看这样的事情对你是否有意义。

    编辑: DataStax 的 Patrick McFadin 在Getting Started With Time Series Data Modeling 上写了一篇很棒的文章。在本文中,他描述了使用“时间桶”对时间序列应用程序中的表进行进一步分区。这使您免于达到每个分区 20 亿列的限制,还有助于 Cassandra 避免您当时并不真正关心的(旧)数据。

    【讨论】:

    • 感谢布莱斯的回答。有关更多详细信息:一个设备 (deviceid) 拥有多个具有不同类型数据的通道 (channelname)。大多数时候,我需要在 deviceid 和 channelname 的函数中获取我的数据:SELECT * FROM messages WHERE deviceid=X AND channelname=X LIMIT 500;而其他时候,我需要在我的网站中请求我的表格以获取导入功能或动态刷新,所以我需要时间列,例如:SELECT * FROM messages WHERE deviceid=X AND time
    • 我不太了解你描述的“桶”系统......关于请求,我没有说它永远不会工作,但它不适用于已经保存了很多消息的设备这个数据库(大约 10 000 个)。为了更精确,这个表应该能够计算数百万行。此表可能需要大约每秒处理 1 条新消息,因此一年后大约有 3000 万条消息。
    • 感谢您的 URL 文章,我会阅读的。但我认为没有必要,因为我认为这张桌子不会超过 20 亿点(或者不会在 10 年之前)。那么你相信我设置了 3 个分区键(设备 ID、频道名、时间)并且它会起作用吗?你有什么方法可以将数据从旧表传输到新表吗?
    【解决方案3】:

    我有时间尝试您给我的解决方案: 所以我创建了另外 2 个表来尝试不同的请求(我需要的更少,这 3 个请求如下):

    SELECT * FROM messages WHERE deviceid = '1DB8D' AND time >= 1438853606718 ALLOW FILTERING ; 
    SELECT * FROM messages WHERE deviceid = '1DB8D' AND channelname = 'brutMessage'; 
    SELECT * FROM messages WHERE deviceid = '1DB8D' AND channelname = 'brutMessage' AND time >= 1438853606718;
    

    这是我的结果:

    第一个表(消息):旧表

    PRIMARY KEY ((deviceid, channelname), time) ) WITH CLUSTERING ORDER BY (time DESC)
    
    SELECT * FROM messages WHERE deviceid = '1DB8D' AND time >= 1438853606718 ALLOW FILTERING ;  <==== errors={}, last_host=127.0.0.1
    SELECT * FROM messages WHERE deviceid = '1DB8D' AND channelname = 'brutMessage'; <=== OK
    SELECT * FROM messages WHERE deviceid = '1DB8D' AND channelname = 'brutMessage' AND time >= 1438853606718; <=== OK
    

    第二张表(messages_test):

    PRIMARY KEY (deviceid, time, channelname)) WITH CLUSTERING ORDER BY (time DESC, channelname ASC)
    
    SELECT * FROM messages_test WHERE deviceid = '1DB8D' AND time >= 1438853606718 ; <==== OK
    SELECT * FROM messages_test WHERE deviceid = '1DB8D' AND channelname='brutMessage' AND time >= 1438853606718 ; <==== code=2200 [Invalid query] message="PRIMARY KEY column "channelname" cannot be restricted 
                                                                                                                        (preceding column "ColumnDefinition{name=time, type=org.apache.cassandra.db.marshal.ReversedType
                                                                                                                        (org.apache.cassandra.db.marshal.TimestampType), kind=CLUSTERING_COLUMN, componentIndex=0, 
                                                                                                                        indexName=null, indexType=null}" is either not restricted or by a non-EQ relation)"
                                                                                                                        
    SELECT * FROM messages_test WHERE deviceid = '1DB8D' AND channelname='brutMessage'; <====  code=2200 [Invalid query] message="PRIMARY KEY column "channelname" cannot be restricted 
                                                                                                (preceding column "ColumnDefinition{name=time, type=org.apache.cassandra.db.marshal.ReversedType
                                                                                                (org.apache.cassandra.db.marshal.TimestampType), kind=CLUSTERING_COLUMN, componentIndex=0, 
                                                                                                indexName=null, indexType=null}" is either not restricted or by a non-EQ relation)"
    

    第三张表(messages_test2):

    PRIMARY KEY (deviceid, channelname, time)) WITH CLUSTERING ORDER BY (channelname ASC, time DESC)
    
    SELECT * FROM messages_test2 WHERE deviceid = '1DB8D' AND time >= 1438853606718 ; <==== code=2200 [Invalid query] message="PRIMARY KEY column "time" cannot be restricted 
                                                                                            (preceding column "ColumnDefinition{name=channelname, type=org.apache.cassandra.db.marshal.UTF8Type,
                                                                                            kind=CLUSTERING_COLUMN, componentIndex=0, indexName=null, indexType=null}" 
                                                                                            is either not restricted or by a non-EQ relation)"
    SELECT * FROM messages_test2 WHERE deviceid = '1DB8D' AND channelname = 'brutMessage' AND time >= 1438853606718 ; <==== OK
    SELECT * FROM messages_test2 WHERE deviceid = '1DB8D' AND channelname = 'brutMessage'; <====== OK
    

    您对我的 3 个请求有什么建议吗?

    PS:感谢@BryceAtNetwork23,他向我解释了如何使用 COPY TO/FROM 方法复制表。

    【讨论】:

    • 请起来!我需要一些帮助:(
    猜你喜欢
    • 2020-05-10
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    • 2011-12-22
    • 1970-01-01
    • 2023-01-27
    • 2021-11-13
    • 1970-01-01
    相关资源
    最近更新 更多