Cassandra在一致性级别为ONE的读取查询中超时(需要1个响应,但只有0个副本响应)。

9
我正在对一个有500,000行的表进行读取和更新查询,有时在处理约300,000行时会出现以下错误,即使没有节点下线也是如此。
“Cassandra timeout during read query at consistency ONE (1 responses were required but only 0 replica responded)”
基础设施详情: 有5个Cassandra节点,5个Spark和3个Hadoop节点,每个节点都有8个核心和28 GB内存,并且Cassandra的复制系数为3。 Cassandra版本为2.1.8.621 | DSE版本为4.7.1 | Spark版本为1.2.1 | Hadoop版本为2.7.1。
Cassandra配置:
read_request_timeout_in_ms (ms): 10000
range_request_timeout_in_ms (ms): 10000
write_request_timeout_in_ms (ms): 5000
cas_contention_timeout_in_ms (ms): 1000 
truncate_request_timeout_in_ms (ms): 60000
request_timeout_in_ms (ms): 10000.

我尝试通过将read_request_timeout_in_ms(毫秒)增加到20,000来执行相同的作业,但是没有帮助。

我正在对两个表进行查询。以下是其中一个表的创建语句:

创建表:

CREATE TABLE section_ks.testproblem_section (
    problem_uuid text PRIMARY KEY,
    documentation_date timestamp,
    mapped_code_system text,
    mapped_problem_code text,
    mapped_problem_text text,
    mapped_problem_type_code text,
    mapped_problem_type_text text,
    negation_ind text,
    patient_id text,
    practice_uid text,
    problem_category text,
    problem_code text,
    problem_comment text,
    problem_health_status_code text,
    problem_health_status_text text,
    problem_onset_date timestamp,
    problem_resolution_date timestamp,
    problem_status_code text,
    problem_status_text text,
    problem_text text,
    problem_type_code text,
    problem_type_text text,
    target_site_code text,
    target_site_text text
    ) WITH bloom_filter_fp_chance = 0.01
    AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}'
    AND comment = ''
    AND compaction = {'class': 
    'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
    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';

查询:

1) SELECT encounter_uuid, encounter_start_date FROM section_ks.encounters WHERE patient_id = '1234' AND encounter_start_date >= '" + formatted_documentation_date + "' ALLOW FILTERING;

2) UPDATE section_ks.encounters SET testproblem_uuid_set = testproblem_uuid_set + {'1256'} WHERE encounter_uuid = 'abcd345';

注:以上为IT技术相关内容,涉及查询和更新操作。

你能发布你的创建表吗? - phact
...并且我会尝试使用TRACING ON来分析问题,以及您的查询。 - uri2x
@phact,我已经添加了创建表的代码。感谢您的回复。 - Abhinandan Satpute
@uri2x 添加了查询。 - Abhinandan Satpute
不要在生产 OLTP 查询中使用 allow filtering,它会变得很慢。您应该设计表的主键(分区和聚集)以便您可以使用常规的CQL查询。 - phact
@phact - 我会考虑你的建议并尝试实现它。 - Abhinandan Satpute
2个回答

10
通常,当出现超时错误时,这意味着您正在尝试执行一些在Cassandra中不可扩展的操作。解决方法通常是修改模式。

我建议您在运行查询时监视节点,以查看是否可以发现问题区域。例如,您可以运行“watch -n 1 nodetool tpstats”以查看是否有任何队列积压或丢失项目。请参见其他监视建议here

您配置中可能存在的一个问题是,您说您有五个Cassandra节点,但只有3个Spark工作程序(或者您是在每个Cassandra节点上都有三个Spark工作程序?)您至少需要在每个Cassandra节点上拥有一个Spark工作程序,以便将数据加载到Spark中本地完成而不是通过网络完成。

如果没有看到您的模式和查询,很难提供更多信息。您是从单个分区读取吗?当从单个分区读取时,我在大约300,000行附近开始收到超时错误。请参见此处的问题。到目前为止,我唯一找到的解决方法是在我的分区键中使用客户端哈希来将分区分成大约100K行的较小块。到目前为止,我还没有找到一种方法来告诉Cassandra不要超时查询,因为我预计这需要很长时间。


非常感谢。我会尝试您的建议。对于集群的错误/简短信息,我很抱歉。实际上,EC2集群有5个Cassandra节点,5个Spark工作节点,其中2个Spark工作节点位于2个Cassandra节点上,其他3个节点具有Hadoop和Spark工作节点。不好意思,但如何检查正在从多少分区读取数据? - Abhinandan Satpute
cfstats和cfhistograms - phact
@Abhinandan - 你使用ALLOW FILTERING表明你正在尝试进行表扫描。在Cassandra中,这不是高效的,因此你应该重新设计模式以针对单个分区进行查询,或者将表加载到Spark RDD中,以便可以并行处理它。 - Jim Meyer
1
我将concurrent_reads从64更改为128,使用20个核心,现在没有出现任何错误。这是真正的解决方案吗? - Abhinandan Satpute
你是在执行CQL查询还是将数据加载到Spark时遇到了超时错误?我以前没有尝试过concurrent_reads。需要考虑的主要问题是,您是否真的需要完整的表扫描,或者您可以将数据分区成较小的块,以便更便宜地查询。如果是这样,那么修改架构就是有意义的。 - Jim Meyer
显示剩余2条评论

-1

不要认为配置是根本原因,而是数据模型问题。

看到 section_ks.encounters 表的结构会很酷。

建议在设计表结构之前,仔细考虑预计运行的具体查询。

就我所见,这两个查询期望使用不同的section_ks.encounters结构来以良好的性能运行。

让我们回顾每个提供的查询,并尝试设计表:

第一个查询:

SELECT encounter_uuid, encounter_start_date FROM section_ks.encounters WHERE patient_id = '1234' AND encounter_start_date >= '" + formatted_documentation_date + "' ALLOW FILTERING;

  • 首先,如果Cassandra强制您添加ALLOW FILTERING,则表明查询或表结构不够优化。
  • 第二点。主键。有关Cassandra中主键的定义的精彩解释。如果patient_id列和encounter_start_date列形成复合主键,则给定的查询将快速运行且无需强制使用ALLOW FILTERING语句。在PRIMARY KEY()语句中枚举的列应与查询中筛选的顺序相对应。
  • 为什么原始查询需要ALLOW FILTERING?通过分区键,Cassandra知道数据位于哪个节点上。如果patient_id列不是分区键,则Cassandra必须扫描所有5个节点以查找所请求的患者。当我们在节点间有大量数据时,这种全面扫描通常会因超时而失败。

以下是一个有效适用于给定查询的表结构示例:

create table section_ks.encounters(
    patient_id bigint, 
    encounter_start_date timestamp, 
    encounter_uuid text,
    some_other_non_unique_column text,
    PRIMARY KEY (patient_id, encounter_start_date)
);
  • patient_id列将成为“分区键”。负责在Cassandra节点之间分配数据。简单来说(省略复制功能):不同范围的患者将存储在不同的节点上。
  • encounter_start_date列将成为“聚簇键”,负责在分区内进行数据排序。

现在可以从查询中删除ALLOW FILTERING:

SELECT encounter_uuid, encounter_start_date 
FROM section_ks.encounters 
WHERE patient_id = '1234' AND encounter_start_date >= '2017-08-19';

第二个查询:

UPDATE section_ks.encounters SET testproblem_uuid_set = testproblem_uuid_set + {'1256'} WHERE encounter_uuid = 'abcd345';

表结构应该看起来接近于:

create table section_ks.encounters(
    encounter_uuid text, -- partition key
    patient_id bigint,
    testproblem_uuid_set text, 
    some_other_non_unique_column text,
    PRIMARY KEY (encounter_uuid)
);

如果我们确实想要通过encounter_uuid进行快速过滤,那么它应该被定义为分区键。

关于设计有效数据模型的好文章:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接