【问题标题】:Cassandra data modelingCassandra 数据建模
【发布时间】:2015-04-09 08:23:11
【问题描述】:

所以我正在设计这个用于产品价格跟踪的数据模型。

一个产品可以被很多用户关注,一个用户可以关注很多产品,所以这是多对多的关系。 产品一直处于跟踪状态,但只有在与之前的价格不同时才会插入新价格。

用户为其关注的产品设置了价格上限,因此每次价格变化时,都会检查偏好,如果价格低于他们的阈值,则会通知用户。

所以一开始我想到了以下产品模型:

但是,“subscriberEmails”是一个列表集合,最多可以处理 65536 个元素。但作为一个大数据解决方案,这是我们不希望拥有的边界。所以我们最终为此编写了一个单独的表:

所以现在“usersByProduct”可以拥有多达 20 亿列,这很公平。并且用户偏好存储在“地图”中,该地图再次受到限制,但我们认为这是用户可以关注的最大产品数量。

现在我们面临的问题是:

每次我们想要更新产品的价格时,我们都必须进行如下查询:

INSERT INTO products("Id", date, price) VALUES (7dacedd2-c09b-46c5-8686-00c2a03c71dd, dateof(now()), 24.87);    // Example only

但是 INSERT 操作不接受除 (IF NOT EXISTS) 之外的其他条件子句,这不是我们想要的。只有当价格与前一个不同时,我们才需要更新价格,因此这迫使我们进行两次查询(一个用于读取当前值,另一个用于在必要时更新它)。

PD。 UPDATE 操作确实有 IF 条件,但这不是我们的情况,因为我们需要一个 INSERT。

UPDATE products SET date = dateof(now()) WHERE "Id" = 7dacedd2-c09b-46c5-8686-00c2a03c71dd IF price != 20.3;    // example only 

【问题讨论】:

  • 只是好奇,但为什么需要INSERT?在后台,UPDATEINSERT 是相同的:stackoverflow.com/questions/28350630/…
  • 因为即使我进行了所有操作更新,我仍然必须提供时间戳(在主键中)来插入某个产品价格。在给定时间插入新价格时,数据库中不存在新时间,因此更新操作将始终被视为插入(并且不会检查最后插入的价格是否相同)。
  • 另外,你不想这样做:dateof(now())DateOf 返回一个时间戳,但 now 返回一个 timeuuid,它们具有不同的精度。这意味着您的数据将具有时间戳的外观,但实际上将存储从 now() 生成的 timeuuid,这使您难以按日期范围查询(如果您打算这样做):stackoverflow.com/questions/26237940/…
  • 好点,按日期范围查询是必需的,谢谢!

标签: cassandra cql cassandra-2.0 cql3 datastax


【解决方案1】:

不要尝试在 cassandra 数据库上应用普通模型。它可能有效,但您最终会得到糟糕的性能和可扩展性。

推荐的 Cassandra 数据建模方法是首先找出您对数据库的读取查询并构建您的数据,以便这些读取成本低廉。您可能需要在一定程度上复制写入,但这没关系,因为在 Cassandra 中写入非常便宜。

对于您的特定用例,关键查询似乎能够让所有用户对产品的价格变化感兴趣,因此您为此创建一个表,例如:

create table productSubscriptions ( productId uuid, priceLimit float, createdAt timestamp, email text, primary key (productId,priceLimit,createdAt) );

但由于您还需要了解用户的所有产品订阅,因此您都需要一个包含相同数据的用户键控表:

create table userProductSubscriptions ( email text, productId uuid, priceLimit float, primary key (email, productId) )

通过这两个表,我想您可以看到所有主要查询都可以通过单行选择完成,并且您的插入/删除很简单,但需要您同步修改两个表。

显然,您需要根据您的全部需求来充实架构,但这应该会给您一个示例,说明如何考虑您的 cassandra 架构。

有条件更新问题

对于您的条件插入问题,最简单的答案是:如果您确实需要它,请使用 UPDATE(更新和插入在 CQL 中几乎相同),但这是一项非常昂贵的操作,因此请尽量避免。

对于您的用例,我会将您的产品表一分为三:

create table products ( category uuid, productId uuid, url text, price float, primary key (category, productId) ) create table productPricingAudit ( productId uuid, date timestamp, price float, primary key (productId, date) ) create table priceScheduler ( day text, checktime timestamp, productId uuid, url text, primary key (day, checktime) )

products 表可以容纳完整的目录,可以选择分成类别(以便在单个类别中列出所有产品是单行选择)

productPricingAudit 将有一个插入检索到的最新价格,因为这可以让您调试您可能遇到的任何定价问题

priceScheduler 保存给定日期的所有支票,按支票时间排序。您的调度程序只需在运行时对单行进行列范围查询。

使用这样的架构,您不必关心条件更新,只要更新产品价格,您只需发出 3 次插入,即使它没有改变。

【讨论】:

  • 感谢您的回答,这看起来不错且易于管理,我很确定我会将其合并到架构中。但是我主要担心的是我无法有条件地在现有产品上插入新价格......也就是说,检查最后一个现有价格是否与要插入的价格不同。
  • "productPricingAudit 将有一个带有最新价格的插入,无论它是什么,因为这将让您调试您可能遇到的任何定价问题" 好的,再次感谢,但这么多插入正是我正在尝试的避免。我不想存储每个价格,只是可能的变化。为什么?因为我将有 20 多个工人不断检查价格,如果我遵循这个模型,我可能每天都会有 100 万以上的插入。这就是为什么我只需要在新价格发生变化时存储它。
  • 我会使用 writes + a cleanup daily 脚本,但如果您担心存储使用情况,只需在 priceScheduler 表中添加 lastPrice 列,然后在代码中与之前的价格进行比较。无论如何,您都必须阅读 priceScheduler 数据,并且在您的应用程序中,两个工作似乎不太可能同时更新给定产品的价格
  • 谨慎使用,但lightweight transactions 可能是您在“更新前检查”问题上寻找的内容。不过,请确保您在使用它们之前了解成本。
【解决方案2】:

好的,我将尝试回答我自己的问题:Cassandra 中不支持除“IF NOT EXISTS”之外的条件插入到日期、期间。

最接近的是条件更新,但这在我们的场景中不起作用。所以剩下一个简单的选择:应用程序端逻辑。这意味着您必须阅读上一个条目并在您的应用程序上执行决定。明显的缺点是执行了 2 个查询(一个 SELECT 和一个 INSERT),这显然会增加延迟。

但这适合我们的应用程序,因为每次我们执行查询以将所有应检查的项目排入队列时,我们也可以选择项目的 url 及其当前价格。因此,检查最新价格的工人可以决定是否插入,因为他们有当前价格可以比较。

所以...类似于此的查询将每 X 分钟执行一次:

SELECT id, url, price FROM products WHERE "nextCheckTime" < now();    

// example only, wouldn't even work if nextCheckTime is not part of the PK or index

这是在 Cassandra 集群上执行的一项非常昂贵的操作,因为它必须遍历默认情况下随机存储在不同节点中的所有行。另一个缺点是我们需要一些关于产品和用户的高级和具体的统计数据。

因此,我们决定在这种特殊情况下,关系数据库比 Cassandra 为我们提供更好的服务。

遗憾的是,我们放弃了 Cassandra 的所有优势(快速插入、易于扩展、内置分片...),转而寻求 MySQL 集群或主从实现。

【讨论】:

    猜你喜欢
    • 2015-12-25
    • 1970-01-01
    • 2019-09-02
    • 2018-06-16
    • 2018-07-10
    • 2018-04-02
    • 2016-08-20
    • 2019-06-29
    相关资源
    最近更新 更多