【问题标题】:Cassandra: List 10 most recently modified recordsCassandra:列出 10 条最近修改的记录
【发布时间】:2015-11-07 23:08:00
【问题描述】:

我在尝试对我的数据进行建模以便我可以有效地查询 Cassandra 以获取最近修改的最后 10 条(实际上是任意数量)记录时遇到了麻烦。每条记录都有一个 last_modified_date 列,由应用程序在插入/更新记录时设置。

我已经从这个示例代码中排除了数据列。

主数据表(每条记录只包含一行):

CREATE TABLE record (
    record_id int,
    last_modified_by text,
    last_modified_date timestamp,
    PRIMARY KEY (record_id)
);

解决方案 1(失败)

我尝试创建一个单独的表,该表使用了集群键顺序。

表格(每条记录一行;只插入最后修改日期):

CREATE TABLE record_by_last_modified_index (
    record_id int,
    last_modified_by text,
    last_modified_date timestamp,
    PRIMARY KEY (record_id, last_modified_date)
) WITH CLUSTERING ORDER BY (last_modified_date DESC);

查询:

SELECT * FROM record_by_last_modified_index LIMIT 10

此解决方案不起作用,因为聚类顺序仅适用于具有相同分区键的记录的排序。由于每一行都有不同的分区键 (record_id),因此查询结果不包含预期的记录。

解决方案 2(低效)

我尝试过的另一个解决方案是简单地查询 Cassandra 的所有 record_id 和 last_modified_date 值,对它们进行排序并选择我的应用程序中的前 10 条记录。这显然效率低下,并且无法很好地扩展。

解决方案 3

我考虑的最后一个解决方案是对所有记录使用相同的分区键,并使用聚类顺序来确保记录正确排序。该解决方案的问题是数据不会在节点之间正确分区,因为所有记录都将具有相同的分区键。对我来说,这似乎不是首发。

【问题讨论】:

  • 虽然在其解释中提供了信息,但该解决方案适用于每次修改某些内容时插入记录的人。我不需要跟踪每个更新,只需找出最近修改的记录即可。

标签: cassandra cql


【解决方案1】:

我认为您尝试做的更多是关系数据库模型,并且在某种程度上是 Cassandra 中的反模式。

Cassandra 仅根据聚类列对事物进行排序,但预计排序顺序不会改变。这是因为当 memtables 作为 SSTables(排序字符串表)写入磁盘时,SSTables 是不可变的,不能有效地重新排序。这就是不允许您更新聚类列的值的原因。

如果您想对聚集的行重新排序,我知道的唯一方法是删除旧行并批量插入新行。为了使这更加低效,您可能需要先进行读取以确定记录 ID 的 last_modified_date 是什么,以便您可以删除它。

所以我会寻找一种不同的方法,例如将更新写为新的集群行并将旧的保留在那里(可能随着时间的推移使用 TTL 清理它们)。因此,当您执行 LIMIT 查询时,您的最新更新将始终位于最前面。

在分区方面,您需要将数据分成几个类别,以便将数据分布在您的节点上。这意味着您不会对表格进行全局排序,而只能在类别内进行排序,这是由于分布式模型造成的。如果您真的需要全局排序,那么也许可以考虑将 Cassandra 与 Spark 配对。排序在时间和资源上非常昂贵,所以如果你真的需要它,请仔细考虑。

更新:

再想一想,您应该能够在 Cassandra 3.0 中使用物化视图做到这一点。该视图将为您处理凌乱的删除和插入,以重新排序聚集的行。下面是 3.0 alpha 版本中的样子:

首先创建基表:

CREATE TABLE record_ids (
    record_type int,
    last_modified_date timestamp,
    record_id int,
    PRIMARY KEY(record_type, record_id));

然后创建该表的视图,使用 last_modified_date 作为集群列:

CREATE MATERIALIZED VIEW last_modified AS
    SELECT record_type FROM record_ids
    WHERE record_type IS NOT NULL AND last_modified_date IS NOT NULL AND record_id IS NOT NULL
    PRIMARY KEY (record_type, last_modified_date, record_id)
    WITH CLUSTERING ORDER BY (last_modified_date DESC);

现在插入一些记录:

insert into record_ids (record_type, last_modified_date, record_id) VALUES ( 1, dateof(now()), 100);
insert into record_ids (record_type, last_modified_date, record_id) VALUES ( 1, dateof(now()), 200);
insert into record_ids (record_type, last_modified_date, record_id) VALUES ( 1, dateof(now()), 300);

SELECT * FROM record_ids;

 record_type | record_id | last_modified_date
-------------+-----------+--------------------------
           1 |       100 | 2015-08-14 19:41:10+0000
           1 |       200 | 2015-08-14 19:41:25+0000
           1 |       300 | 2015-08-14 19:41:41+0000

SELECT * FROM last_modified;

 record_type | last_modified_date       | record_id
-------------+--------------------------+-----------
           1 | 2015-08-14 19:41:41+0000 |       300
           1 | 2015-08-14 19:41:25+0000 |       200
           1 | 2015-08-14 19:41:10+0000 |       100

现在我们更新基表中的一条记录,并且应该看到它移动到视图中列表的顶部:

UPDATE record_ids SET last_modified_date = dateof(now()) 
WHERE record_type=1 AND record_id=200;

所以在基表中,我们看到 record_id=200 的时间戳已更新:

SELECT * FROM record_ids;

 record_type | record_id | last_modified_date
-------------+-----------+--------------------------
           1 |       100 | 2015-08-14 19:41:10+0000
           1 |       200 | 2015-08-14 19:43:13+0000
           1 |       300 | 2015-08-14 19:41:41+0000

在视图中,我们看到:

 SELECT * FROM last_modified;

 record_type | last_modified_date       | record_id
-------------+--------------------------+-----------
           1 | 2015-08-14 19:43:13+0000 |       200
           1 | 2015-08-14 19:41:41+0000 |       300
           1 | 2015-08-14 19:41:10+0000 |       100

因此,您会看到 record_id=200 在视图中向上移动,如果您对该表进行限制 N,您将获得 N 个最近修改的行。

【讨论】:

  • 你肯定在这里赢得了 +1!
  • 我一定会考虑使用物化视图。在 Cassandra 3.0 稳定之前,我无法使用它。这个问题最困难的部分是找出合适的分区键。这些记录没有“record_type”列并且相当稀疏,所以我唯一的解决方案可能是生成一个任意分区键,该键仅用于对数据进行分区。谢谢!
【解决方案2】:

CQL 查询按字段排序的整个表/视图的唯一方法是使分区键保持不变。恰好一台机器(乘以复制因子)将保存整个表。例如。使用始终为零的 partition INT 分区键和作为需要排序的字段的集群键。即使集群中有更多节点,您也应该观察到类似于在排序字段上有索引的单节点数据库的读/写/容量性能。这并没有完全违背 Cassandra 的目的,因为它可以帮助将来扩展。

如果性能不足,您可以决定通过增加分区种类来进行扩展。例如。当使用 4 个节点时,从 0、1、2、3 中随机选择插入将高达四倍的读/写/容量性能。然后要找到“10 个最近的”项目,您必须手动查询所有 4 个分区并对结果进行合并排序。

理论上,Cassandra 可以为 INSERT 提供动态 node-count-max-modulo 分区键,为 SELECT 提供合并排序(ALLOW FILTERING)。

Cassandra 的设计目标不允许全局排序

要允许写入、读取和存储容量随节点数线性扩展,Cassandra 需要:

  • 每个插入都位于一个节点上。
  • 每个选择都位于一个节点上。
  • 客户端在所有节点之间以类似方式分配工作负载。

如果我理解正确,结果是全表单字段排序查询将始终需要从整个集群中读取并合并排序。

注意物化视图等同于表格,它们没有任何神奇的属性使它们更擅长全局排序。请参阅http://www.datastax.com/dev/blog/we-shall-have-order,其中 Aaron Ploetz 同意 cassandra 和 cql 无法在没有分区和规模的情况下对一个字段进行排序。

示例解决方案

CREATE KEYSPACE IF NOT EXISTS
    tmpsort
WITH REPLICATION =
    {'class':'SimpleStrategy', 'replication_factor' : 1};

USE tmpsort;

CREATE TABLE record_ids (
    partition int,
    last_modified_date timestamp,
    record_id int,
    PRIMARY KEY((partition), last_modified_date, record_id))
    WITH CLUSTERING ORDER BY (last_modified_date DESC);

INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 1, DATEOF(NOW()), 100);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 2, DATEOF(NOW()), 101);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 3, DATEOF(NOW()), 102);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 1, DATEOF(NOW()), 103);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 2, DATEOF(NOW()), 104);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 3, DATEOF(NOW()), 105);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 3, DATEOF(NOW()), 106);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 3, DATEOF(NOW()), 107);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 2, DATEOF(NOW()), 108);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 3, DATEOF(NOW()), 109);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 1, DATEOF(NOW()), 110);
INSERT INTO record_ids (partition, last_modified_date, record_id) VALUES ( 1, DATEOF(NOW()), 111);

SELECT * FROM record_ids;

-- Note the results are only sorted in their partition
-- To try again:
-- DROP KEYSPACE tmpsort;

请注意,如果没有 WHERE 子句,您将获得令牌(分区键)顺序的结果。见https://dba.stackexchange.com/questions/157537/querying-cassandra-without-a-partition-key

其他数据库分布模型

如果我理解正确 - CockroachDB 在任何给定时间将单调递增数据到一个节点时类似地瓶颈读/写性能,但存储容量将线性扩展。此外,其他范围查询,如“最旧的 10”或“在日期 X 和日期 Y 之间”,会将负载分布在更多节点上,而不是 Cassandra。这是因为 CockroachDB 的数据库是一个巨大的排序键值存储,每当排序数据范围达到一定大小时,它就会重新分配。

【讨论】:

    【解决方案3】:

    我认为公认的解决方案还有另一个问题。 如果您有多个副本,则不能保证插入按顺序结束。

    来自 datastax 文档:

    现在() - 在协调器节点中,执行语句时以毫秒为单位生成一个新的唯一 timeuuid。 timeuuid 的时间戳部分符合 UTC(世界时)标准。此方法对于插入值很有用。 now() 返回的值保证是唯一的。

    当您有多个副本时,您也有多个协调节点,因为可以选择任何节点作为协调节点。这意味着您的插入不按顺序排列,因为节点上的时间有任何微小的变化。 因此,实际上在您的参考帧之后发生的插入可能会在先前插入的记录之前排序,因为 now() 只是在协调器节点上生成一个稍晚一点的日期。

    您正试图对您的数据获得一些一致(或单一参考)的观点。不幸的是,在分布式环境中,没有一个对事实的单一引用。

    【讨论】:

      猜你喜欢
      • 2011-07-24
      • 2017-08-05
      • 2021-11-20
      • 2022-12-16
      • 1970-01-01
      • 2023-01-22
      • 1970-01-01
      • 2016-08-28
      • 1970-01-01
      相关资源
      最近更新 更多