【问题标题】:Cassandra TTL gets set to 0 on primary key if no TTL is specified on an update, but if it is, the TTL on the primary key does not change如果更新时没有指定 TTL,Cassandra 的 TTL 会在主键上设置为 0,但如果是,则主键上的 TTL 不会更改
【发布时间】:2015-02-01 12:41:24
【问题描述】:

Cassandra 中的这种行为似乎违反直觉,我想知道为什么会发生这种情况,并可能解决这个问题。


假设我有一个包含三列的表:pk、主键、text 类型、foobigintbar,另一个text

insert into keyspace.table (pk, foo, bar) values ('first', 1, 'test') using ttl 60;

这会在我的表中创建一个生存时间为 60 秒的行。看了一下,是这样的:

  pk  | foo | bar
------------------
first |  1  | test

现在我做:

update keyspace.table using ttl 10 set bar='change' where pk='first';

然后,观察这一行,我看到它发生了以下变化:

  pk  | foo | bar
--------------------
first |  1  | change
first |  1  | <<null>>  // after 10 seconds
   << deleted >>        // after the initial 60 seconds

一切顺利。我想要的是 bar 的生存时间改变,但没有别的,尤其是主键。这种行为是意料之中的。


然而,如果我的更新中没有ttl,或者它被设置为0:

update keyspace.table set bar='change' where pk='first';

然后我会随着时间的推移看到这种行为。

  pk  | foo | bar
--------------------
first |  1  | change
first |  0  | change   // after the initial 60 seconds

换句话说,该行永远不会被删除。 foo 没有改变,所以它的生存时间仍然有效,在它通过后,值被删除(设置为 0)。但是pk 的生存时间确实发生了变化。这完全出乎意料。

为什么只有在我没有在更新中指定生存时间的情况下,主键的生存时间才会改变?我该如何解决这个问题,以便只有在我明确表示要这样做的情况下,主键的生存时间才会改变?

编辑我还发现,如果我使用高于初始值的生存时间,它似乎也会改变主键的生存时间。

update keyspace.table using ttl 70 set bar='change' where pk='first';

  pk  | foo | bar
--------------------
first |  1  | change
first |  0  | change   // after the initial 60 seconds
   << deleted >>       // after the 70 seconds

【问题讨论】:

    标签: cassandra cql cassandra-2.0 cql3 ttl


    【解决方案1】:

    您遇到的效果是由 Cassandra 使用的存储模型引起的。

    在您的示例中,您有一个没有任何集群列的表,表中的每一行都映射到数据存储中的一行(通常称为“Thrift 行”,因为这是通过节俭 API)。表中不属于主键的每个列(因此在您的示例中,foobar 列)都映射到 Thrift 行中的列。除此之外,还会创建一个在 CQL 行中不可见的额外列作为该行存在的标记。

    TTL 过期发生在 Thrift 列级别,而不是 CQL 列。当您INSERT 一行时,您插入的所有列以及该行本身的特殊标记都会获得相同的 TTL。

    如果您UPDATE 一行,则只有您更新的列会获得新的 TTL。未触摸行标记。

    当使用SELECT 运行查询时,将返回至少有一列存在特殊行标记的所有行。这意味着具有最高 TTL 的列定义了 CQL 行的可见时长,除非行本身的标记(仅在使用 INSERT 语句时被触及)具有更长的 TTL。

    如果您想确保行的主键使用与新列值相同的 TTL 进行更新,解决方法很简单:更新行时使用 INSERT 语句。这与使用UPDATE 的效果完全相同,但它还会更新行标记的 TTL。

    此解决方法的唯一缺点是它不能与轻量级事务(INSERTUPDATE 语句中的IF 子句)结合使用。如果您需要将这些与 TTL 结合使用,则必须使用更复杂的解决方法,但我想这将是一个单独的问题。

    如果您想更新一行的某些列,但仍然希望在您最初插入时指定的 TTL 过期后整行消失,Cassandra 不直接支持这一点。唯一的方法是通过首先查询其中一列的 TTL,然后在 UPDATE 操作中使用此 TTL 来找出该行剩余的 TTL。例如,您可以使用SELECT TTL(foo) FROM table1 WHERE pk = 'first';。但是,这会影响性能,因为它会增加延迟(您必须等待SELECT 的结果才能运行UPDATE)。

    作为替代方案,您可以添加一个仅用作“行存在”标记的列,并且您只在INSERT 期间触摸,而在UPDATE 中永远不会触摸。然后,您可以简单地忽略此列为 null 的行,但此过滤需要在客户端实现,如果您无法在 UPDATE 中指定 TTL,它将无济于事,因为更新的列永远不会已删除。

    【讨论】:

    • 如果使用复合主键,有什么变化?
    • 使用复合主键不应该改变任何东西。就低级(Thrift)存储模型而言,复合主键实际上只是一个元组。 CQL 级别上不同列的映射只是语法糖。
    • 如果有集群键?
    • 基本上,即使有集群键也是如此。但是,我在回答中提出的一些声明不再有效,例如,如果存在集群​​键,则具有相同分区键的所有 CQL 行实际上将映射到同一个 Thrift 行。如果您想了解一组特定的 CQL 行和列如何映射到底层数据存储,最好使用“cassandra_cli”查看数据。这将暴露通过 CQL 界面不可见的内部细节。
    • LWT 的问题是当行不存在时你必须使用 INSERT 并且当它已经存在时使用 UPDATE。当使用不带 IF 子句的 INSERT 或 UPDATE 时,它不是 LWT。混合使用 LWT 和非 LWT 操作会导致很多问题,因此不建议使用(请参阅docs.datastax.com/en/cassandra/3.x/cassandra/dml/…)。
    【解决方案2】:

    经过一些测试,这些是预期的结果。 TTL 具有列的粒度。

    • 进行更新时,如果未指定 TTL,则将列 TTL 设置为 0。此操作不会影响其他列 TTL。
    • 我们无法在单个 cql 命令中更新列值并保留旧列值 TTL。
    • 当所有列 TTL 过期时,将删除行(或主/分区键)。如果列的 TTL 或 0,则不会删除该行。

    截至今天(Cassandra 2.1),以下是更新列值并保留其 TTL 的方法:

    SELECT TTL(col1) FROM table1 where pk=1;
    // read the ttl value fetched.
    UPDATE table1 USING TTL <the_ttl_value> set col1='change' where pk=1;
    

    【讨论】:

    • 这并不能真正回答问题。如果您有其他问题,可以点击 进行提问。一旦你有足够的reputation,你也可以add a bounty 来引起对这个问题的更多关注。
    • @Jordan 在这里,我编辑并取消删除了我之前的答案。这是正确的做法吗?
    • 这并不能回答我关于为什么该行有时会被删除而其他时候不会被删除的问题。不过,我感谢您抽出宝贵时间提交问题,我现在正在关注它。
    猜你喜欢
    • 2017-09-23
    • 2019-07-25
    • 1970-01-01
    • 2017-06-08
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多