我来自关系型数据库背景,现在尝试使用亚马逊的DynamoDB。
我有一个表格,其中包含哈希键“DataID”和范围“CreatedAt”以及一堆数据项。
我正在尝试获取所有在特定日期之后创建并按日期排序的项目,这在关系型数据库中非常简单。
在DynamoDB中,我能找到的最接近的东西是使用范围键大于过滤器的查询。唯一的问题是要执行查询,我需要一个哈希键,这违背了初衷。
那么我做错了什么?我的表模式有误吗,哈希键不应该是唯一的吗?还是有其他方法可以查询?
我来自关系型数据库背景,现在尝试使用亚马逊的DynamoDB。
我有一个表格,其中包含哈希键“DataID”和范围“CreatedAt”以及一堆数据项。
我正在尝试获取所有在特定日期之后创建并按日期排序的项目,这在关系型数据库中非常简单。
在DynamoDB中,我能找到的最接近的东西是使用范围键大于过滤器的查询。唯一的问题是要执行查询,我需要一个哈希键,这违背了初衷。
那么我做错了什么?我的表模式有误吗,哈希键不应该是唯一的吗?还是有其他方法可以查询?
根据您当前的表结构,这在DynamoDB中目前是不可能的。巨大的挑战是要理解表的哈希键(分区)应被视为创建单独的表。在某些方面,这真的很强大(将分区键视为为每个用户或客户等创建一个新表)。
查询只能在单个分区中执行。这就是故事的结束。这意味着如果您想按日期查询(您将希望使用自纪元以来的毫秒数),则您想要在单个查询中检索的所有项目必须具有相同的哈希(分区键)。
我应该说明一下。您绝对可以通过您要查找的标准来扫描
,这没有问题,但这意味着您将查看表中的每一行,然后检查该行是否具有与您的参数匹配的日期。这非常昂贵,特别是如果您首先要存储事件按日期存储(即您有很多行)。
您可能会想把所有数据放在单个分区中来解决问题,您当然可以这样做,但是考虑到每个分区仅接收总设置量的一小部分,您的吞吐量将非常低。
最好的方法是确定更有用的分区以保存数据:
您是否真的需要查看所有行,还是仅特定用户的行?
首先按月份缩小列表并执行多个查询(每个月一个)是否可以?或按年份?
如果您正在进行时间序列分析,则有几个选项:更改分区键以计算PUT
上的内容以使query
更容易,或使用另一个AWS产品,如Kinesis,它适用于只追加日志。
更新的回答:
DynamoDB允许指定辅助索引来帮助进行此类查询。辅助索引可以是全局的,这意味着索引跨越哈希键扩展整个表,也可以是本地的,这意味着索引将存在于每个哈希键分区中,因此在进行查询时还需要指定哈希键。
对于这个问题中的用例,您需要在“CreatedAt”字段上使用全局辅助索引。
有关DynamoDB辅助索引的更多信息,请参见辅助索引文档。
原始回答:
DynamoDB不允许仅基于范围键进行索引查找。哈希键是必需的,以便服务知道要查找数据的哪个分区。
当然,您可以执行扫描操作以按日期值进行过滤,但是这将需要完整的表扫描,因此并不理想。
如果您需要跨多个主键按时间执行索引查找记录,则DynamoDB可能不是您使用的理想服务,或者您可能需要使用单独的表(无论是在DynamoDB中还是关系存储中)来存储可以执行索引查找的项目元数据。
CreatedAt
大于某个时间点的记录。 - pkaeding我用以下方法解决了这个问题,即创建了一个全局二级索引。不确定这是否是最佳方法,但希望对某些人有用。
Hash Key | Range Key
------------------------------------
Date value of CreatedAt | CreatedAt
HTTP API用户对检索数据的时间范围有限制,缺省值为24小时。
这样,我总是可以将HashKey指定为当前日期的天数,并在检索时使用>和<操作符作为RangeKey。这样数据也会分布在多个shard中。
你的哈希键(主键和排序键)必须是唯一的(除非你有一个像其他人所说的范围那样的键)。
在你的情况下,为了查询你的表,你应该有一个辅助索引。
| 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. aws dynamodb scan --table-name tableName --region us-east-1 --filter-expression "begins_with(createdTm,:gen)" --expression-attribute-values "{":gen":{"S":"2021-04-15"}}" --select "COUNT"
2. aws dynamodb scan --table-name tableName --region us-east-1 --filter-expression "createdTm BETWEEN :v1 AND :v2" --expression-attribute-values '{":v1":{"S":"2021-04-13"}, ":v2":{"S":"2021-04-14"}}' --select "COUNT"
更新答案 使用可预测吞吐量的Dynamo DB查询没有方便的方法来完成此操作。一种(次优)选择是使用带有人造HashKey&CreatedAt的GSI。然后仅按HashKey查询并提到ScanIndexForward以排序结果。如果您可以想出自然的HashKey(例如物品类别等),则此方法是赢家。另一方面,如果对所有项目保持相同的HashKey,则当数据集增长超过10GB(一个分区)时,它将在很大程度上影响吞吐量。
原始答案: 现在你可以通过使用GSI在DynamoDB中完成这个任务。将“CreatedAt”字段设置为GSI,并发出诸如(GT some_date)之类的查询。将日期存储为数字(自纪元以来的毫秒数)以进行此类查询。
详细信息在此处: Global Secondary Indexes - Amazon DynamoDB:http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html#GSI.Using
这是一个非常强大的功能。请注意,查询仅限于(EQ | LE | LT | GE | GT | BEGINS_WITH | BETWEEN)条件 - 亚马逊 DynamoDB:http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Condition.html
CreatedAt
应该是GSI的范围键,那么你需要选择一个哈希键 - 然后你又回到了起点,因为你只能针对哈希键的特定值查询CreatedAt
上的GT。 - PaF
yyyy
的属性,并以此进行哈希,同时创建一个created
日期作为范围键。这样,每年您可以获得10GB的数据(每天27MB),这对于大多数情况可能足够了。但这意味着当日期查询跨越年份时,您必须针对每年创建一个查询,但至少它能工作,并且比创建虚拟哈希键更安全。 - Ryan ShillingtonCreatedAt
作为排序键创建全局二级索引。然后,您就可以跨所有项目按日期进行查询。这似乎有些取巧,但是否有更好的方法呢? - Bennett McElwee