按日期查询DynamoDB


102

我来自关系数据库背景,试图与Amazon的DynamoDB合作

我有一个带有哈希键“ DataID”和范围“ CreatedAt”以及其中一堆项目的表。

我正在尝试获取在特定日期之后创建并按日期排序的所有项目。在关系数据库中,这非常简单。

在DynamoDB中,我能找到的最接近的内容是查询,并且使用范围键大于过滤器。唯一的问题是,要执行查询,我需要一个无效的哈希键。

那我在做什么错?我的表架构是否错误,哈希键是否应该唯一?还是有另一种查询方式?

Answers:


34

更新的答案:

DynamoDB允许指定二级索引来帮助这种查询。二级索引可以是全局的(意味着该索引跨哈希键跨整个表),也可以是局部的(意味着该索引将存在于每个哈希键分区中),因此要求在进行查询时也指定哈希键。

对于此问题中的用例,您需要在“ CreatedAt”字段上使用全局二级索引。

有关DynamoDB二级索引的更多信息,请参见二级索引文档。

原始答案:

DynamoDB不允许仅在范围键上进行索引查找。哈希密钥是必需的,以便服务知道要查找哪个分区以查找数据。

您当然可以执行扫描操作以按日期值进行过滤,但是这将需要全表扫描,因此并不理想。

如果您需要跨多个主键按时间对记录进行索引查找,则DynamoDB可能不是您理想的服务,或者您可能需要利用单独的表(在DynamoDB或关系存储中)存储项目您可以执行索引查找的元数据。


14
请参阅下面答案的评论;现在没有办法解决这个问题,至少对于OP的要求来说还没有。GSI仍然要求您指定哈希键,因此您无法查询CreatedAt大于某个特定点的所有记录。
2015年

4
@pkaeding是正确的。使用scan可以使记录的日期早于某个特定日期,但不能按排序的顺序获取它们。在这种情况下,GSI不会为您提供帮助。无法对分区键进行排序,也无法仅查询范围键。
gkiko

15
对于你们中那些困惑的人。这个答案是错误的。他最初的答案是正确的,但他的最新答案却不正确。阅读下面的沃伦·帕拉德的答案。这是正确的。
瑞安·希灵顿

1
@MikeBrant我想使用大于号在表的GSI哈希键(CreatedAt)上查询(而不是扫描,它查看表中的每个项目,使其效率非常低下且非常昂贵)。据我所知,这是无法完成的。
Aziz Javed

4
使用日期作为主分区时,您可能会遇到的问题是,由于大多数数据存储中新数据的查询比旧数据的查询更为频繁,因此您可能在某些或一个对等对象上创建热点。
知识

53

考虑到您当前的表结构,DynamoDB当前无法做到这一点。巨大的挑战是要了解表(分区)的哈希键应视为创建单独的表。在某些方面,它确实非常强大(将分区键视为为每个用户或客户创建新表等)。

查询只能在单个分区中进行。那真的是故事的结局。这意味着,如果您要按日期查询(自纪元起便要使用毫秒),则要在单个查询中检索的所有项目都必须具有相同的哈希(分区键)。

我应该证明这个。您绝对可以scan按照要查找的条件来进行操作,这没问题,但这意味着您将查看表中的每一行,然后检查该行是否具有与您的参数匹配的日期。这确实非常昂贵,尤其是如果您要首先按日期存储事件(即,您有很多行)。

您可能会想将所有数据放在一个分区中来解决该问题,您绝对可以,但是由于每个分区仅接收总设置量的一小部分,因此吞吐量会非常低。

最好的办法是确定要创建的更有用的分区来保存数据:

  • 您是否真的需要查看所有行,还是仅查看特定用户的行?

  • 首先按月份缩小列表范围,然后进行多个查询(每个月查询一次)是否可以?还是按年?

  • 如果要进行时间序列分析,则有两个选项,可以将分区键更改为可简化计算PUT的值query,或者使用kinesis之类的其他AWS产品,使其适用于仅追加日志记录。


4
我要强调你在最后一段中提出的关于考虑“按年”的选择。创建一个像这样的属性yyyy并对其进行哈希处理,还创建一个created日期,您可以将其用作范围键。然后,您每年可以获得10GB的数据(每天27 MB),这可能在更多情况下是可以的。这确实意味着,当日期查询超出年份边界时,您必须每年创建一个查询,但是至少它会起作用,并且比创建虚拟哈希键更安全。
瑞安·希灵顿


1
如上面的链接所述,严格基于时间的分区键可能会导致热点。如果必须使用基于时间的分区键,则最好在分区键中添加其他元素,以在多个分区上分散时间。我看到了一些建议,只是使用0-n之间的前缀,其中n是每个存储分区应分配的分区数。
德累斯顿

@RyanShillington 全局二级索引没有10GB的限制。该限制仅适用于本地二级索引。
西蒙·佛斯伯格

18

解决该问题的方法是创建如下的全球二级索引。不确定这是否是最好的方法,但希望它是否对某人有用。

Hash Key                 | Range Key
------------------------------------
Date value of CreatedAt  | CreatedAt

对HTTP API用户的限制,用于指定检索数据的天数,默认为24小时。

这样,我总是可以将HashKey指定为当前日期,并且RangeKey可以在检索时使用>和<运算符。这样,数据也可以分布在多个分片上。


8

您的哈希键(排序的主键)必须是唯一的(除非您具有其他人指出的范围)。

对于您的情况,要查询表,您应该有一个辅助索引。

|  ID  | DataID | Created | Data |
|------+--------+---------+------|
| hash | xxxxx  | 1234567 | blah |

您的哈希密钥是ID。您的二级索引定义为:DataID-Created-index(这是DynamoDB将使用的名称)

然后,您可以进行如下查询:

var params = {
    TableName: "Table",
    IndexName: "DataID-Created-index",
    KeyConditionExpression: "DataID = :v_ID AND Created > :v_created",
    ExpressionAttributeValues: {":v_ID": {S: "some_id"},
                                ":v_created": {N: "timestamp"}
    },
    ProjectionExpression: "ID, DataID, Created, Data"
};

ddb.query(params, function(err, data) {
    if (err) 
        console.log(err);
    else {
        data.Items.sort(function(a, b) {
            return parseFloat(a.Created.N) - parseFloat(b.Created.N);
        });
        // More code here
    }
});

本质上,您的查询如下所示:

SELECT * FROM TABLE WHERE DataID = "some_id" AND Created > timestamp;

次要索引将增加所需的读/写容量单位,因此您需要考虑这一点。它仍然比进行扫描要好得多,因为这样做会导致读取和时间花费很大(我认为仅限于100项)。

这可能不是最好的方法,但是对于习惯RD(我也熟悉SQL)的人来说,这是提高生产率的最快方法。由于对模式没有任何限制,因此您可以快速进行一些工作,一旦拥有了以最有效的方式工作的带宽,就可以进行更改。


1
您说没有限制,但是您应该知道这种方法意味着您最多可以保存10GB的数据(单个分区的最大值)。
瑞安·希灵顿

如果DataID是已知的,那将是这种方法。但是在这里,我们需要获取创建日期超过某个日期的每一行。
Yasith Prabuddhaka

3

您可以按照“产品类别” ID的方式使Hash键生效,然后将范围键作为时间戳的组合,并在其末尾附加一个唯一的ID。这样,您就知道哈希键,并且仍然可以查询大于日期。


1

您可以有多个相同的哈希键;但前提是您的范围键有所不同。可以将其视为文件格式;您可以在同一文件夹中使用相同名称的2个文件,只要它们的格式不同即可。如果它们的格式相同,则它们的名称必须不同。相同的概念适用于DynamoDB的哈希/范围键;只需将哈希视为名称,将范围视为格式。

另外,我不记得他们在OP时是否有这些文件(我不相信他们有),但是现在它们提供了本地二级索引。

我对这些的理解是,它现在应该允许您执行所需的查询,而不必进行全面扫描。缺点是必须在创建表时指定这些索引,并且(我相信)在创建项目时不能为空。此外,它们需要额外的吞吐量(尽管通常不如扫描那么多)和存储,因此对于某些人来说,这不是一个完美的解决方案,而是一个可行的选择。

我仍然建议Mike Brant的答案作为使用DynamoDB的首选方法。并自己使用该方法。在我的情况下,我只有一个仅具有哈希键作为ID的中央表,然后是具有可查询的哈希和范围的辅助表,然后该项目将代码直接指向中央表的“感兴趣的项目” 。

有关二级索引的其他数据,可以在此处的 Amazon DynamoDB文档中找到

无论如何,希望这将对在此线程上发生的任何其他事件有所帮助。


我尝试创建一个DynamoDB表,其中存在哈希类型的AWSDynamoDBKeySchemaElement'createdAt',再次存在类型范围为AWSDynamoDBKeySchemaElement'createdAt'的错误,但我收到一个错误,错误消息为Error Domain = com.amazonaws.AWSDynamoDBErrorDomain Code = 0“(null)” UserInfo = {____ type = com.amazon.coral.validate#ValidationException,消息= KeySchema中的Hash键和Range键元素都具有相同的名称}。所以我不认为您的意思是正确的。
user1709076 2015年

我相信您会误解(尽管我想我的描述也不太清楚)。一个表中不能有两个具有相同名称的不同属性(列),但是当您创建一个带范围键的哈希键时,只要它们的范围不同,就可以有多个都使用相同哈希值的项,并且反之亦然。例如:您的哈希是“ ID”,范围是“ Date”,只要它们的日期不同,则可以有两个ID为“ 1234”的实例。
DGolberg 2015年

啊,戈尔德伯格!我现在得到你。那很棒。因此,对于我来说,由于我仅且始终只想在“ date = x之后”查询文本消息,因此看来我可以将所有文本消息设置为具有相同的“ fake_hash = 1”。然后执行我的query.keyConditionExpression = @“ fake_hash = 1和#Date>:val”。非常感谢你。如果您还有其他输入,我会很高兴听到,因为具有始终相同值的哈希值似乎很奇怪?
user1709076 2015年

我必须再次检查,但是我很确定您可以对仅哈希表进行查询...尽管如果您使用日期/时间戳作为哈希,我建议您记录到尽可能短的单位,例如毫秒或纳秒/微秒(无论代码可以记录的最小时间单位是多少),以减少重叠日期/时间的机会。另外,您可以添加乐观锁定以进一步减少重叠的可能性:docs.aws.amazon.com/amazondynamodb/latest/developerguide/… 如果发生冲突,只需重试其他时间。
DGolberg,2015年

-11

更新的答案 没有使用可预测吞吐量的Dynamo DB查询执行此操作的便捷方法。一种(次优)选择是将GSI与人工HashKey和CreatedAt一起使用。然后仅通过HashKey进行查询,并提及ScanIndexForward以对结果进行排序。如果您可以提出一个自然的HashKey(例如商品的类别等),则此方法是一个赢家。另一方面,如果所有项目都使用相同的HashKey,则当数据集超过10GB(一个分区)时,它将主要影响吞吐量。

原始答案: 您现在可以使用GSI在DynamoDB中执行此操作。将“ CreatedAt”字段设置为GSI,并发出(GT some_date)之类的查询。对于此类查询,将日期存储为数字(自纪元以来的毫秒数)。

有关详细信息,请参见:全球二级索引-Amazon DynamoDB:http ://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using

这是一个非常强大的功能。请注意,查询仅限于(EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN)条件-Amazon DynamoDB:http ://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html


31
我之所以投票,是因为据我所知,您的答案是错误的。就像表的主键一样,您只能使用EQ运算符查询GSI的哈希键。如果您暗示那CreatedAt应该是GSI的范围键,则需要选择一个哈希键-然后返回到开始的位置,因为您将只能查询GT CreatedAt的特定值散列键。
PaF 2014年

同意PaF。使用带有哈希键的GSI作为创建时间并不能解决OP中提出的问题。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.