Z J*_*nes 6 amazon-web-services amazon-dynamodb amazon-cognito graphql aws-appsync
我计划将AWS Cognito用于用户身份验证,使用DynamoDB进行持久性,并使用AppSync(和许多Mobile Hub)来为API提供支持 - 书评网站.
我很难确定哪个字段应该是我的哈希键,哪个应该是我的排序键,以及我应该创建哪个LSI/GSI.
我有一份书籍清单,详细信息如下:
type Book {
isbn: Int!
year: Int!
title: String!
description: String
front_cover_photo_url: String
genre_ids: [Int]
count_thumbs: Int
us_release_date: String
upcoming_release: Boolean
currently_featured_in_book_stores: Boolean
best_seller: Boolean
reviews: [Review]
}
Run Code Online (Sandbox Code Playgroud)
每次用户撰写有关图书的评论时,我也会有评论记录.
type Review {
isbn: Int!
id: ID!
created_at: String!
# The user that submitted the review
user_id: String!
# The number of thumbs out of 5
thumbs: Int!
# Comments on the review
comments: String!
}
Run Code Online (Sandbox Code Playgroud)
在我的案例中,书籍可以有多种类型 - 例如"幻想"和"戏剧".书籍也有用户评论,其数据存储在Cognito中.我们将在每本书旁边以反向时间顺序显示评论.
问题1:如果我将非规范化并Drama用作流派而不是流派ID 2,那么如果我需要稍后将该流派重命名为Dramatic...我不需要更新每个项目吗?
我需要能够至少回答:
currently_featured_in_book_stores== True]upcoming_release== True]count_thumbsDESC 排序]genre_ids包含123或"喜剧"的书籍,取决于Q1的回答]titleLIKE'%Harry Potter%']isbnIN [1,2,3,4,9]]的书籍问题2:在DynamoDB中构建图书数据的最佳方法是什么,你会使用哪种哈希/排序/ LSI/GSI?
由于我使用的是Cognito,因此用户配置文件数据存储在DynamoDB之外.
问题3:我是否应该User在DynamoDB中有一个表并双重写新注册,因此我可以使用AppSync在显示他们的评论时填充评论的详细信息?如果没有,在填写书评详细信息时如何获取用户的用户名/名字/姓氏?
问题4:既然我们已经走了这么远,对graphql架构有什么建议吗?
我鼓励你阅读这个答案.我之前写过以提供有关选择键的一般背景知识.您还应该打开该答案中的链接,这些链接提供AWS在该主题上提供的大部分关键信息.
在提供答案之前,我想我还应该注意数据架构通常会考虑很多因素.你已经在问题中提供了一些非常好的信息,但不可避免地提供了一个明确的"最佳"解决方案.事实上,即使有更多信息,您也会得到不同的意见.
也就是说,这就是我想要做的事情.我将考虑创建一个名为Books的表和一个名为BookReviews的表.
Table: Books
Partition Key: ISBN
Table: BookReviews
Partition Key: ISBN
Sort Key: BookReview-id
Run Code Online (Sandbox Code Playgroud)
我不打算创建任何GSI或LSI.
您的大部分查询都涉及查找"所有图书"并以某种方式订购.这些列表听起来不是时间敏感的.例如,当用户要求获得最受欢迎的100本书时,他们需要知道最受欢迎的书籍,包括直到最后一秒才计算的每一票?我对此表示怀疑.此外,这些列表是针对个人用户的吗?听起来不像.
我的一般提示是这个; 将原始数据存储在DynamoDB中,并实时更新.创建公共书籍列表并在一段时间内(可能每天)更新它们,将这些列表存储在缓存中.(可选)您可以将这些列表存储在DynamoDB中的单独表中,并在销毁缓存时查询它们.
获取目前在书店中出现的所有书籍
var params = {
TableName: "Books",
ExpressionAttributeValues: {
":a": {
BOOL: true
}
},
FilterExpression: "currently_featured_in_book_stores = :a"
};
dynamodb.scan(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Run Code Online (Sandbox Code Playgroud)
此操作将检索当前在书店中展示的所有书籍.它使用扫描.如果您还不熟悉scan,query和getItem,那么您一定要花些时间阅读它们.
扫描会评估表中的每个项目,因此扫描有时在大型表上无法很好地扩展,如果您只检索一些项目,则扫描成本会很高.查询使用分区键返回一组项目,因此通常快速有效.您可以在查询中使用排序键来快速返回分区中的一系列项目.GetItem使用唯一的主键,效率很高.
如果您的桌子有100个项目,那么您执行的任何扫描都将花费100 RCU.如果执行查询,并且查询分区中只有2个项目,则会花费2个RCU.
如果Books表中的很大一部分项目的current_featured_in_book_stores = true,我会进行扫描.如果表中只有少量项目具有current_featured_in_book_stores = true且这是一个非常频繁的查询,您可以考虑在Books表上创建一个GSI,其中包含current_featured_in_book_stores的分区键和ISBN的排序键.
想象一下,你的书籍表有100本书,50本有current_featured_in_book_stores = true.执行扫描需要100 RCU,而且查询费用不会太高.现在假设只有一本书有current_featured_in_book_stores = true,执行扫描将花费100 RCU,但查询只需1 RCU.但是,在添加GSI之前,您应该仔细考虑,它们不会与基表共享吞吐量,您必须为GSI单独购买RCU.如果您在配置GSI之下,它最终可能比在配置良好的基表上扫描慢.
布尔值是一个错误的分区键,我会在这里进行扫描.也就是说,如果你创建了GSI,你的查询将如下所示:
var params = {
TableName: "Books",
IndexName: "Index_Books_In_Stores",
ExpressionAttributeValues: {
":v1": {
BOOL: true
}
},
KeyConditionExpression: "currently_featured_in_book_stores = :v1"
};
dynamodb.query(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Run Code Online (Sandbox Code Playgroud)
获取即将推出的所有书籍
以上所有内容仍然适用.我会做这样的扫描
var params = {
TableName: "Books",
ExpressionAttributeValues: {
":a": {
BOOL: true
}
},
FilterExpression: "upcoming_release = :a"
};
dynamodb.scan(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Run Code Online (Sandbox Code Playgroud)
我不经常扫描并将结果缓存到临时存储(即应用程序内存)中.
获取按大拇指排序的所有书籍
这里最重要的是"获取所有书籍......".这告诉你马上扫描可能是最好的方法.您可以将查询视为仅查看一个分区的扫描.你不想看书的分区,你想要所有的书,所以扫描是要走的路.
如果您对具有排序键的表或索引执行查询,则DynamoDB将返回已排序项的唯一方法.在这种情况下,项目将根据排序键自动按排序顺序返回.因此,对于此搜索,您只需要扫描以获取所有书籍,然后按所选属性(拇指)客户端对其进行排序.扫描只返回所有书籍,看起来像这样.
var params = {
TableName: "Books"
};
dynamodb.scan(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Run Code Online (Sandbox Code Playgroud)
同样,我会很少进行此扫描并缓存顶级书籍.您可以订购缓存并只检索所需的项目数量,可能是前10,100或1000.如果用户继续进行超出缓存范围的分页,则可能需要执行新扫描.我认为您更有可能只限制项目数量并停止用户进一步分页.
获取所有类型为"喜剧"的书籍
同样,我很可能不经常扫描并缓存列表.您可以考虑添加具有分区键类型和排序密钥ISBN的GSI.我个人会从扫描和缓存方法开始,看看你是如何继续下去的.您可以随时添加GSI.
查询名为"哈利波特"的书
显然你不能缓存这个.使用标题上的filterexpression进行扫描
var params = {
TableName: "Books",
ExpressionAttributeValues: {
":a": {
S: "Harry Potter"
}
},
FilterExpression: "title CONTAINS :a"
};
dynamodb.scan(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Run Code Online (Sandbox Code Playgroud)
您可以在此处查看条件运算符
获取所有ISBN 1,2,3,4或9的书籍
对于这个,在每个ISBN上做一个GetItem并将其添加到一个集合中.下面的查询得到一本书.你可以将它放在一个循环中,并遍历你想要获得的ISBN集.
var params = {
Key: {
"ISBN": {
S: "1"
}
},
TableName: "Books"
};
dynamodb.getItem(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Run Code Online (Sandbox Code Playgroud)
是的,如果您将类型作为字符串存储在每个项目中,并且您更改了类型名称,则必须更新每个项目.或者作为替代方案,您必须在将项目呈现给用户之前更新该项目的类型.
如果您希望更改流派名称,使用genre_id映射的想法似乎是一个好主意.只需拥有一个流派名称和ID表,在应用程序启动时加载它并将其保存在应用程序内存中.您可能需要一个管理功能来重新加载类型映射表.
将应用程序参数保存在数据库中是一种很好用的设计.
问题3
当然,在DynamoDB中有一个User表.这就是我在使用Cognito的应用程序中执行此操作的方式.我在Cognito中存储了一组与用户注册相关的最小字段,然后我在用户表中的DynamoDB中有很多特定于应用程序的数据.
关于图表模式,我会查看AWS的这篇文章.不太确定是否有帮助.
| 归档时间: |
|
| 查看次数: |
1369 次 |
| 最近记录: |