【问题标题】:Cassandra query failure (Tombstones)Cassandra 查询失败(墓碑)
【发布时间】:2017-08-22 11:09:27
【问题描述】:

所以这让我发疯了。我尝试在 Cassandra 中查询我的一张表,结果显示查询失败。我试图深入挖掘其背后的原因,发现这是因为墓碑。我将 GC_GRACE_SECONDS 更改为零并使用 nodetool 触发了压缩,当我再次查询时它工作正常。但是在随后的呼叫中,查询再次失败,原因相同。我正在使用 cassandra-nodejs 驱动程序。 这是我的数据模型。

CREATE TABLE my_table (
    firstname text,
    lastname text,
    email text,
    mobile text,
    date timeuuid,
    value float,
    PRIMARY KEY (firstname, lastname, email, mobile)
) WITH CLUSTERING ORDER BY (lastname ASC, email ASC, mobile ASC);

这是我要在该数据模型上执行的查询。

SELECT firstname, email, toDate(date) as date, mobile, value FROM my_table  WHERE date >= minTimeuuid('2017-03-25 00:00:00+0000') AND date <= minTimeuuid('2017-03-28 23:59:59+0000') ALLOW FILTERING;

结果将有大约 40k 行。 this 表明,如果我们删除某些内容,它将被标记为墓碑,并在为给定表设置 GC_GRACE_SECONDS 后被删除。如果我理解正确的话。

  1. 我从不删除表格的任何行,怎么会出现墓碑问题?
  2. 当且仅当我们删除一行时,该行是否会被标记为 Tombstone?
  3. 清除墓碑,然后查询相同的作品有时有时不,为什么会这样?
  4. 增加 tombstone_failure_threshold 值是个好主意吗? (单节点集群应用)

我正在使用 cassandra 3.5,cqlsh 版本为 5.0.1。并且查询在终端上运行良好,但是当我们使用外部客户端执行时出错(使用 nodejs 驱动程序的 cassandra 快速应用程序)。我有一个单节点集群应用程序。

编辑 1

这是我在字段中插入空值的日志(我只插入了名称和时间戳);

  activity                                                                                        | timestamp                  | source        | source_elapsed
-------------------------------------------------------------------------------------------------+----------------------------+---------------+----------------
                                                                              Execute CQL3 query | 2017-03-29 10:28:27.342000 | 172.31.34.179 |              0
                   Parsing select * FROM testtomb WHERE name = 'Dhaval45'; [SharedPool-Worker-2] | 2017-03-29 10:28:27.342000 | 172.31.34.179 |             64
                                                       Preparing statement [SharedPool-Worker-2] | 2017-03-29 10:28:27.342000 | 172.31.34.179 |            101
                              Executing single-partition query on testtomb [SharedPool-Worker-3] | 2017-03-29 10:28:27.342000 | 172.31.34.179 |            210
                                              Acquiring sstable references [SharedPool-Worker-3] | 2017-03-29 10:28:27.342000 | 172.31.34.179 |            223
 Skipped 0/0 non-slice-intersecting sstables, included 0 due to tombstones [SharedPool-Worker-3] | 2017-03-29 10:28:27.342000 | 172.31.34.179 |            243
                                 Merged data from memtables and 0 sstables [SharedPool-Worker-3] | 2017-03-29 10:28:27.342000 | 172.31.34.179 |            288
                                         Read 2 live and 0 tombstone cells [SharedPool-Worker-3] | 2017-03-29 10:28:27.342001 | 172.31.34.179 |            310
                                 Merged data from memtables and 0 sstables [SharedPool-Worker-3] | 2017-03-29 10:28:27.342001 | 172.31.34.179 |            323
                                                                                Request complete | 2017-03-29 10:28:27.342385 | 172.31.34.179 |            385

这是我查询已执行删除查询的字段时的日志。最初用户 Dhaval15 的名字是“aaaa”,然后是单元格 aaa。然后再次对同一用户执行选择查询给了我这个日志。

       activity                                                                                        | timestamp                  | source        | source_elapsed
-------------------------------------------------------------------------------------------------+----------------------------+---------------+----------------
                                                                              Execute CQL3 query | 2017-03-29 10:35:18.581000 | 172.31.34.179 |              0
                   Parsing select * FROM testtomb WHERE name = 'Dhaval15'; [SharedPool-Worker-1] | 2017-03-29 10:35:18.581000 | 172.31.34.179 |             65
                                                       Preparing statement [SharedPool-Worker-1] | 2017-03-29 10:35:18.581000 | 172.31.34.179 |            113
                              Executing single-partition query on testtomb [SharedPool-Worker-3] | 2017-03-29 10:35:18.581000 | 172.31.34.179 |            223
                                              Acquiring sstable references [SharedPool-Worker-3] | 2017-03-29 10:35:18.581000 | 172.31.34.179 |            235
 Skipped 0/0 non-slice-intersecting sstables, included 0 due to tombstones [SharedPool-Worker-3] | 2017-03-29 10:35:18.581000 | 172.31.34.179 |            256
                                 Merged data from memtables and 0 sstables [SharedPool-Worker-3] | 2017-03-29 10:35:18.581001 | 172.31.34.179 |            305
                                         Read 1 live and 1 tombstone cells [SharedPool-Worker-3] | 2017-03-29 10:35:18.581001 | 172.31.34.179 |            338
                                 Merged data from memtables and 0 sstables [SharedPool-Worker-3] | 2017-03-29 10:35:18.581001 | 172.31.34.179 |            351
                                                                                Request complete | 2017-03-29 10:35:18.581430 | 172.31.34.179 |            430

【问题讨论】:

  • 能否提供失败信息?正在生成的异常日志。
  • 我不断收到 操作失败 - 收到 0 个响应和 1 个失败。 {traceQuery : true } 选项无法以某种方式工作,并且在终端上查询成功运行

标签: cassandra cassandra-3.0 cassandra-node-driver


【解决方案1】:

在 Cassandra 墓碑创建时即使您不执行删除查询,当您插入空值时。

墓碑消耗空间。当你执行选择查询时,cassandra 需要通过 tombstone 过滤掉数据。如果生成巨大的墓碑,您的选择查询性能将下降。

由于巨大的墓碑和ALLOW FILTERING,您的查询失败。不要在生产中使用ALLOW FILTERING。这是非常昂贵的。在不指定分区键的情况下执行查询时,Cassandra 需要扫描所有节点的所有行。

将您的数据模型更改为如下所示:

CREATE TABLE my_table (
    year int,
    month int,
    date timeuuid,
    email text,
    firstname text,
    lastname text,
    mobile text,
    value float,
    PRIMARY KEY ((year, month), date)
);

您可以在此处指定从日期中提取的年份和月份。
现在您可以通过指定分区键进行查询:

SELECT * FROM my_table WHERE year = 2017 AND month = 03 AND date >= minTimeuuid('2017-03-25 00:00:00+0000') AND date <= minTimeuuid('2017-03-28 23:59:59+0000') ;

这将非常有效地返回结果并且不会失败。

如果您需要使用名字和姓氏进行查询,请在它们上创建索引

CREATE INDEX index_firstname ON my_table (firstname) ;
CREATE INDEX index_lastname ON my_table (lastname) ;

然后你可以用名字或姓氏查询

SELECT * FROM my_table WHERE firstname = 'ashraful' ;
SELECT * FROM my_table WHERE lastname  = 'islam' ;

由于高基数问题,我没有在电子邮件和电话上创建索引。而是创建物化视图或另一个表以通过电话或电子邮件进行查询

CREATE MATERIALIZED VIEW mview_mobile AS
    SELECT *
    FROM my_table
    WHERE mobile IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND date IS NOT NULL
    PRIMARY KEY (mobile, year, month, date);


CREATE MATERIALIZED VIEW mview_email AS
        SELECT *
        FROM my_table
        WHERE email IS NOT NULL AND year IS NOT NULL AND month IS NOT NULL AND date IS NOT NULL
        PRIMARY KEY (email, year, month, date);

现在您可以通过电话或电子邮件查询

SELECT * FROM mview_mobile WHERE mobile = '018..';
SELECT * FROM mview_email WHERE email = 'ashraful@...';

更多关于卡桑德拉墓碑的信息:http://thelastpickle.com/blog/2016/07/27/about-deletes-and-tombstones.html

【讨论】:

  • 我会尝试但仍然对有时清除墓碑工作而有时他们不工作感到困惑?为什么会这样?
  • 墓碑占用空间。当你执行选择查询时,cassandra 需要通过 tombstone 过滤掉数据。如果生成了巨大的墓碑,您的选择查询性能将会下降。
  • 如果我不想改变我的表结构,还有其他方法吗?
  • 然后检查你所有的插入和更新语句,确保没有插入空值
  • 感谢您的时间:)
【解决方案2】:
  1. 我从不删除表格的任何行,怎么会出现墓碑问题?

@Ashraful Islam 的回答是正确的。

此外, 如果您显式插入具有 NULL 值的数据,它将在内部创建墓碑。

例如:插入 my_table (firstname,lastname,email,mobile, .....) 值 ('abd', 'gef', 'xxx@abc.com', '+67899...', null , 空值, .....);

其他列将有空值(内部生成墓碑,因为 Cassandra 必须用这些列不存在或具有值的内容来指示)。

正如您提到的作为主键的列太多,它会创建太大的宽行(这也是不鼓励的),带有太多的空值,因此最终会产生大量的墓碑。在您的情况下,它可能已经超过了阈值限制。

正如@Ashraful 所说,这个数据模型不好。由于您需要按时间查询 您应该设计您的模型,以便您可以按时进行一些范围查询。在不提及分区键的情况下进行查询,因此在大型数据集中使用 ALLOW FILTERING 在 Cassandra 中是一种反模式。

  1. 当且仅当我们删除一行时,该行是否会被标记为 Tombstone?

您可以删除完整的或特定的列。如果删除了一行,则将整行标记为墓碑。如果删除了特定列(或插入/更新 NULL 值),则会在该特定列上创建墓碑(这也是不鼓励的)我猜你的情况会发生这种情况。

  1. 清除墓碑,然后查询相同的作品有时有时不,为什么会这样?。

我想你现在可以确定为什么会发生这种情况了 :)。

  1. 增加 tombstone_failure_threshold 值是个好主意吗? (单节点集群应用)

增加 tombstone_failure_threshold 只会减少错误的数量,但不能解决实际问题。随着数据集的增加,它不会提高性能并越过阈值。

重要的是,在单节点集群应用程序中,您可以将 GC_GRACE_SECONDS 设置为 0。Tombstone 将在压缩发生后立即删除。 GC_GRACE_SECONDS 在使用多节点集群时至关重要,我猜这也是使用 NoSQL 的实际目的。

【讨论】:

  • 感谢您抽出宝贵时间 :) 是的,每行中的空列可能太多
  • 我认为在 Cassandra 中建模数据之前,您应该遵循一些基本规则。有很多关于这方面的文章。您已将所有列作为分区键,这意味着您已限制查询(您必须在 where 子句中提及所有列)。你能提供所有的值吗?同样,它也没有解决允许过滤问题。您仍然无法按时间进行范围查询。为了支持您的查询,您仍然必须在不提及分区键的情况下读取数据,因此最终使用 ALLOW FILTERING。
  • 仅供参考,C* 使用分区键来定位请求的行所在的节点。现在您必须提及所有值以告知 C* 她必须请求哪个节点来获取数据。同样,您已经更改了数据结构。那么为什么不根据您的查询更改模型:)
  • 是的,我不介意改变它。但只是有点想知道这些事情。
  • 检查是否明确插入空值。检查我在回答中提供的示例。
猜你喜欢
  • 2019-11-30
  • 2023-03-08
  • 2015-05-31
  • 2019-07-09
  • 2018-11-20
  • 2017-02-09
  • 2015-06-14
  • 2017-02-09
  • 1970-01-01
相关资源
最近更新 更多