按日期查询DynamoDB

app*_*ief 86 amazon-web-services nosql amazon-dynamodb

我来自关系数据库背景,并尝试使用亚马逊的DynamoDB

我有一个表,其中包含一个哈希键"DataID"和一个范围"CreatedAt"以及一系列项目.

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

在DynamoDB中,我能找到的最接近的是查询并使用大于filter的范围键.唯一的问题是,为了执行查询,我需要一个破坏目的的哈希键.

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

War*_*rad 37

鉴于您当前的表结构,目前在DynamoDB中无法实现.最大的挑战是要理解表(分区)的哈希键应该被视为创建单独的表.在某些方面,这非常强大(将分区键视为为每个用户或客户创建新表等等).

查询只能在单个分区中完成.这真的是故事的结局.这意味着如果您想按日期查询(您希望从epoch开始使用msec),那么您要在单个查询中检索的所有项目必须具有相同的哈希(分区键).

我应该有资格这个.您绝对可以scan按照您要查找的标准,这没问题,但这意味着您将查看表中的每一行,然后检查该行是否具有与您的参数匹配的日期.这真的很贵,特别是如果你是在第一时间按日期存储事件(即你有很多行).

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

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

  • 你真的需要查看所有行,还是只有特定用户的行?

  • 是否可以按月缩小列表范围,并进行多次查询(每月一次)?还是按年?

  • 如果您正在进行时间序列分析,则有几个选项,将分区键更改为计算的内容PUT以使其query更容易,或者使用另一个像kinesis这样的aws产品,这些产品适合仅附加日志记录.

  • 我想强调你在上一段中提出的关于考虑"按年"的选项.创建一个像`yyyy`这样的属性并在其上创建哈希,但也创建一个`created`日期,您可以将其用作范围键.然后你每年获得10GB的数据(每天27 MB),这对于更多情况可能是好的.它确实意味着你必须在日期查询超过年边界时每年创建一个查询,但至少它会起作用并且比创建虚拟哈希键更安全. (4认同)
  • _“每个分区仅接收总设定量的一小部分”_ -- 由于[自适应容量],这不再是正确的(https://aws.amazon.com/blogs/database/how-amazon-dynamodb-adaptive) -容量容纳不均匀的数据访问模式或为什么您所了解的 dynamodb 可能已过时/)。在我看来,表中可以有一个始终具有相同值的“虚拟属性”。然后使用虚拟属性作为分区键并使用“CreatedAt”作为排序键来创建全局二级索引。然后您可以按日期查询所有项目。看起来很hacky,但是*有更好的方法吗?* (2认同)

Mik*_*ant 33

更新答案:

DynamoDB允许指定二级索引以帮助进行此类查询.辅助索引可以是全局的,也就是说索引跨越散列键跨越整个表,或者本地意味着索引将存在于每个散列键分区中,因此在进行查询时也需要指定散列键.

对于此问题中的用例,您可能希望在"CreatedAt"字段上使用全局二级索引.

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

原答案:

DynamoDB不允许仅对范围键进行索引查找.需要散列密钥,以便服务知道要查找哪个分区以查找数据.

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

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

  • 请参阅以下答案的评论; 现在有*不是*处理这个的方法,至少不是OP所要求的.GSI仍然要求您指定散列键,因此您无法查询"CreatedAt"大于某个点的所有记录. (12认同)
  • 对于那些困惑的人.这个答案是错的.他的原始答案是正确的,但他的最新答案却不是.阅读Warren Parad的回答如下.这是正确的. (9认同)
  • @pkaeding是对的.您可以使用__scan__获取比某些特定_date_更早的记录,但是您无法按排序顺序获取它们.在这种情况下,GSI不会帮助您.无法对_partition_键进行排序,也无法只查询_range_键. (4认同)
  • 您在使用**日期可能会得到****作为主分区**的问题是,你可能会创建一些或一个同行的热点,由于在大多数数据储存新数据查询更频繁比旧数据. (4认同)

Gir*_*esh 8

解决这个问题的方法是通过创建全局二级索引,如下所示.不确定这是否是最好的方法,但希望如果它对某人有用.

Hash Key                 | Range Key
------------------------------------
Date value of CreatedAt  | CreatedAt
Run Code Online (Sandbox Code Playgroud)

对HTTP API用户施加的限制,以指定检索数据的天数,默认为24小时.

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

  • 这是一个很好的选择,除了您可以根据您的用例制作更小的日期槽以分布在更多分片上。如果您只需要能够找到“现在”过去的时间,并且您知道您处理项目的速度足够快,那么您可以将散列键设置为日期+时间的小时部分,例如除以将日期 2021-04-17T16:22:07.000Z 转换为哈希键 2021-04-17T16 和范围键 22:07.000Z,这样您就可以使用 dateHour = "2021-04 等查询来搜索“过去”的项目-17T16" AND 分钟秒 &lt;= 22:07 查找该日期之前的所有项目。 (2认同)

E.T*_*E.T 7

您的哈希键(排序主键)必须是唯一的(除非您有其他人声明的范围).

在您的情况下,要查询您的表,您应该有一个二级索引.

|  ID  | DataID | Created | Data |
|------+--------+---------+------|
| hash | xxxxx  | 1234567 | blah |
Run Code Online (Sandbox Code Playgroud)

您的哈希密钥是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
    }
});
Run Code Online (Sandbox Code Playgroud)

基本上你的查询看起来像:

SELECT * FROM TABLE WHERE DataID = "some_id" AND Created > timestamp;
Run Code Online (Sandbox Code Playgroud)

二级索引将增加所需的读/写容量单位,因此您需要考虑这一点.它仍然比扫描好很多,这在读取和时间上都是昂贵的(并且我认为限于100个项目).

这可能不是最好的方法,但对于习惯于RD的人(我也习惯于SQL)来说,这是提高工作效率的最快方法.由于没有关于模式的限制,你可以掀起一些有效的东西,一旦你有了最有效的工作带宽,就可以改变一切.