使用 CQL3 在 Cassandra 中进行数据版本控制

Posted

技术标签:

【中文标题】使用 CQL3 在 Cassandra 中进行数据版本控制【英文标题】:Data Versioning in Cassandra with CQL3 【发布时间】:2014-05-21 14:12:57 【问题描述】:

我在 Cassandra 中非常出色(我主要来自 RDBMS 背景,并在各处使用过一些 NoSQL,例如 Google 的 BigTable 和 MongoDB),并且我正在努力为我正在尝试的用例进行数据建模满足。我查看了this 和this 甚至this,但它们并不是我所需要的。

我有这张基本表:

CREATE TABLE documents (
    itemid_version text,       
    xml_payload text,
    insert_time timestamp,
    PRIMARY KEY (itemid_version)
); 

itemid 实际上是一个 UUID(并且对于所有文档都是唯一的),version 是一个 int(版本 0 是“第一个”版本)。 xml_payload 是完整的 XML 文档,可以变得相当大。是的,我实际上是在创建一个版本化的文档存储。

如您所见,我将两者连接起来创建了一个主键,稍后我会在解释要求和/或用例时解释我这样做的原因:

    用户需要获得他想要的单 (1) 个文档,他知道项目 ID 和版本(不一定是最新的) 用户需要获得他想要的单 (1) 个文档,他知道项目 id 但不知道最新版本 用户需要单 (1) 个文档的版本历史记录。 用户需要获取他想要的文档列表(1 个或多个),他知道项目 ID 和版本(不一定是最新的)

我将编写执行用例的客户端代码,请原谅我试图与语言无关的语法

第一个很简单:

$itemid_version = concat($itemid, $version)
$doc = csql("select * from documents where itemid_version = 0;" 
    -f $itemid_version)

现在为了满足第 2 和第 3 个用例,我添加了下表:

CREATE TABLE document_versions (
    itemid uuid,
    version int,
    PRIMARY KEY (itemid, version)
) WITH clustering order by (version DESC);

新记录将随着新文档和现有文档的新版本的创建而添加

现在我们有了这个(用例 #2):

$latest_itemid, $latest_version = csql("select itemid, 
    version from document_versions where item_id = 0 
    order by version DESC limit 1;" -f $itemid)
$itemid_version = concat($latest_itemid, $latest_version)
$doc = csql("select * from documents where itemid_version = 0;" 
    -f $itemid_version)

还有这个(用例 #3):

$versions = csql("select version from document_versions where item_id = 0" 
    -f $itemid)

对于第三个要求,我要添加另一个表:

CREATE TABLE latest_documents (
    itemid uuid,
    version int,
    PRIMARY KEY (itemid, version)
)

为新文档插入记录,为现有文档更新记录

现在我们有了这个:

$latest_itemids, $latest_versions = csql("select itemid, version 
    from latest_documents where item_id in (0)" -f $itemid_list.toCSV())

foreach ($one_itemid in $latest_itemids, $one_version in $latest_versions)
    $itemid_version = concat($latest_itemid, $latest_version)
    $latest_docs.append(
        cql("select * from documents where itemid_version = 0;" 
        -f $itemid_version))        

现在我希望清楚为什么我将itemidversion 连接起来为documents 创建索引而不是创建复合键:我不能在WHEREWHERE 子句中使用ORSELECT

您可以假设只有一个进程会执行插入/更新,因此您无需担心一致性或隔离问题。

我在正确的轨道上吗?有很多事情不适合我……但主要是因为我还不了解 Cassandra:

我觉得documents 的主键应该是 (itemid, version) 的组合,但我不能满足用例 #4(从查询中返回列表)...我不可能使用由于性能下降(网络开销),每个文档都有一个单独的 SELECT 语句......或者我可以(应该)吗? 如果事先不知道版本,则需要 2 次获取文档。可能是我必须接受的妥协,或者可能有更好的方法。

【问题讨论】:

【参考方案1】:

这对德克斯特有什么作用?

它实际上与您的解决方案非常相似,只是您可以存储所有版本并能够仅从一个表 (document_versions) 中获取“最新”版本。

在大多数情况下,我认为您可以在单个 SELECT 中获得所需的内容,但用例 #2 获取文档的最新版本,其中首先需要在 document_versions 上进行预 SELECT。

第二次尝试

(我从第一次尝试中删除了代码,向任何关注 cmets 的人道歉)。

CREATE TABLE documents (
        itemid_version text,
        xml_payload text,
        insert_time timestamp,
        PRIMARY KEY (itemid_version)
);

CREATE TABLE document_versions (
        itemid text,
        version int,
        PRIMARY KEY (itemid, version)
) WITH CLUSTERING ORDER BY (version DESC);


INSERT INTO documents (itemid_version, xml_payload, insert_time) VALUES ('doc1-1', '<?xml>1st</xml>', '2014-05-21 18:00:00');
INSERT INTO documents (itemid_version, xml_payload, insert_time) VALUES ('doc1-2', '<?xml>2nd</xml>', '2014-05-21 18:00:00');
INSERT INTO documents (itemid_version, xml_payload, insert_time) VALUES ('doc2-1', '<?xml>1st</xml>', '2014-05-21 18:00:00');
INSERT INTO documents (itemid_version, xml_payload, insert_time) VALUES ('doc2-2', '<?xml>2nd</xml>', '2014-05-21 18:00:00');

INSERT INTO document_versions (itemid, version) VALUES ('doc1', 1);
INSERT INTO document_versions (itemid, version) VALUES ('doc1', 2);
INSERT INTO document_versions (itemid, version) VALUES ('doc2', 1);
INSERT INTO document_versions (itemid, version) VALUES ('doc2', 2);

    用户需要获得他想要的单 (1) 个文档,他知道项目 ID 和版本(不一定是最新的)

    SELECT * FROM 文档 WHERE itemid_version = 'doc1-2';

    用户需要获得他想要的单个 (1) 文档,他知道项目 id 但不知道最新版本 (您可以将第一个查询结果中的串联 itemid + 版本输入到第二个查询中)

    SELECT * FROM document_versions WHERE itemid = 'doc2' LIMIT 1;

    SELECT * FROM 文档 WHERE itemid_version = 'doc2-2';

    用户需要单个 (1) 文档的版本历史记录。

    SELECT * FROM document_versions WHERE itemid = 'doc2';

    用户需要获取他想要的文档列表(1 个或多个),他知道项目 ID 和版本(不一定是最新的)

    SELECT * FROM documents WHERE itemid_version IN ('doc1-2', 'doc2-1');

干杯,

【讨论】:

感谢@reggoodwin ...至于用例#4,我知道这是一个在这里抛出活动扳手的用例,因为如果我没有那个要求,你的解决方案会对我有用。基本上,对于用例 #4,它归结为“权利”——即,某些用户只能获得 某些 版本的文档(同样,特定用户可能没有 i> 有权获得最新版本)。 实际上,对于这种方法,SELECT * FROM documents where itemid IN ('doc1', 'doc2'); 返回文档的 ALL 版本,而不仅仅是最新版本。 Dexter,感谢您用我的用例 #4 示例标记问题。在添加一些最终插入之前,我没有测试最后一个查询。我现在重新提交了一个不同的代码示例,希望效果更好。它实际上与您的解决方案非常相似。 实际上这种方法是一种 Cassandra 反模式,因为您试图最小化多次读取。第一条经验法则是每个表都应该被构造为服务于特定的查询。 我有没有机会在表格数据中使用键值系统来使用 Cassandra 对其(数据)进行版本控制?我的目标是在每次数据进入并形成版本时为用户提供插入、删除和更新补丁。注意:这些补丁是与当前最新版本相比的候选版本。【参考方案2】:

让我们看看我们是否可以从您的查询开始以自上而下的方式提出一个模型:

CREATE TABLE document_versions (
  itemid uuid,
  name text STATIC,
  vewrsion int,   
  xml_payload text,
  insert_time timestamp,
  PRIMARY KEY ((itemid), version)
) WITH CLUSTERING ORDER BY (version DESC);

    用例 1:用户需要获得他想要的单 (1) 个文档,他知道项目 id 和版本(不一定是最新的)

    SELECT * FROM document_versions 
      WHERE itemid = ? and version = ?;
    

    用例2:用户需要得到他想要的单(1)个doc,他知道item id但不知道最新版本

    SELECT * FROM document_versions
      WHERE itemid = ? limit 1;
    

    用例 3:用户需要单 (1) 个文档的版本历史记录。

    SELECT * FROM document_versions 
      WHERE itemid = ?
    

    用例 4:用户需要获取他想要的文档列表(1 个或多个),他知道项目 id 和版本(不一定是最新的)

    SELECT * FROM documents 
      WHERE itemid = 'doc1' and version IN ('1', '2');
    

    所有这些查询的一个表是正确的方法。我建议参加 Datastax 免费在线课程:DS220 Data Modeling

【讨论】:

确实,这可能是 Cassandra v2.2 及更高版本since it added support for IN clause for any partition field 的更好答案。请注意,我在v2.2 release 之前一年多问过这个问题。

以上是关于使用 CQL3 在 Cassandra 中进行数据版本控制的主要内容,如果未能解决你的问题,请参考以下文章

工具Cassandra 2.1正式发布,引入CQL3元组和用户定义类型

CQL3 (Cassandra) upsert 增加一个计数器

如何从零开始理解 CQL3 表模型?

使用 Cassandra (CQL 3) 实现类似表的堆栈

cassandra在服务端像leveldb一样进行插入初试成功

如果结果集较大,则使用 Cassandra 和 DataStax Java 驱动程序时出现 NoHostAvailableException