amazon-web-services - DynamoDB : Best hash/sort keys for my use case [confusion with AppSync/GraphQL]

标签 amazon-web-services amazon-dynamodb graphql amazon-cognito aws-appsync

我计划使用 AWS Cognito 进行用户身份验证、使用 DynamoDB 进行持久化和使用 AppSync(以及许多 Mobile Hub)来支持 API——一个书评网站。

我很难确定哪个字段应该是我的散列键,哪个应该是我的排序键,以及我应该创建哪个 LSI/GSI。

我有一个书籍 list ,其中包含如下详细信息:

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]
}

每次用户写关于一本书的评论时,我也有一个评论记录。
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!
}

就我而言,书籍可以有多种类型——例如“幻想”和“戏剧”。书籍也有用户的评论,其数据存储在 Cognito 中。我们将在每本书旁边按时间倒序显示评论。

问题 1:如果我非规范化并使用 Drama作为流派而不是流派 ID 2 ,那么如果我稍后需要将流派重命名为 Dramatic 怎么办? ...我不需要更新每个项目吗?

我至少需要能够回答:
  • 获取当前在书店推荐的所有书籍 [ currently_featured_in_book_stores == 真]
  • 获取所有“即将出版”的书籍 [ upcoming_release == 真]
  • 获取所有图书按最多拇指排序 [排序 count_thumbs DESC]
  • 获取所有类型为“喜剧”的书籍 [ genre_ids包含 123或“喜剧”取决于对 的回答第一季度 ]
  • 查询名为“哈利波特”的书 [ title LIKE '%Harry Potter%']
  • 获取所有 ISBN 号为 1、2、3、4 或 9 的图书 [ isbn在 [1,2,3,4,9] ]

  • 问题 2:在 DynamoDB 中构建图书数据的最佳方法是什么,您会使用哪种散列/排序/LSI/GSI?

    由于我使用的是 Cognito,因此用户配置文件数据存储在 DynamoDB 之外。

    问题 3:我应该有一个 User DynamoDB 中的表和双重写入新注册,所以我可以在显示评论时使用 AppSync 填充评论的详细信息?如果没有,我将如何在填充书评详细信息时获得用户的用户名/名字/姓氏?

    问题 4:既然我们已经走了这么远,对 graphql 模式有什么建议吗?

    最佳答案

    我鼓励你阅读 this answer .我之前写过一些关于选择键的一般背景。您还应该打开该答案中的链接,其中提供了 AWS 提供的有关该主题的大部分关键信息。

    在提供答案之前,我想我还应该说明一下,数据架构通常会考虑很多因素。您在问题中提供了一些非常好的信息,但不可避免地没有足够的信息来提供明确的“最佳”解决方案。事实上,即使有更多的信息,你也会得到不同的意见。

    问题2

    也就是说,这就是我在你的情况下会考虑做的事情。我会考虑创建一个名为 Books 的表和一个名为 BookReviews 的表。

    Table: Books
    Partition Key: ISBN
    
    Table: BookReviews
    Partition Key: ISBN
    Sort Key: BookReview-id
    

    我不希望创建任何 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
     });
    

    此操作将检索当前在书店中出售的所有书籍。它使用 scan .如果您还不熟悉 scan , querygetItem你绝对应该花一些时间阅读它们。

    扫描会评估表中的每个项目,因此扫描有时无法在大型表上很好地扩展,如果您只检索少数项目,则可能会很昂贵。查询使用分区键返回一组项目,因此通常快速高效。您可以在查询中使用排序键快速返回分区内的一系列项目。 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,它最终可能比在配置良好的基表上进行扫描更慢。

    bool 值是一个错误的分区键,我会在这里进行扫描。也就是说,如果您在上面创建了 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
     });
    

    获取所有即将出版的书籍

    以上所有内容仍然适用。我会做这样的扫描
    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
     });
    

    我会不经常执行此扫描并将结果缓存在临时存储中(即在应用程序内存中)。

    获取所有图书按最多拇指排序

    这里重要的是“获取所有书籍......”。这会立即告诉您扫描可能是最佳方法。您可以将查询视为仅查看一个分区的扫描。您不想查看部分书籍,而是想要查看所有书籍,因此扫描是最佳选择。

    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
     });
    

    同样,我会很少进行此扫描并缓存顶级书籍。您可以对缓存进行排序并只检索您需要的项目数,可能是前 10、100 或 1000。如果用户继续进行超出缓存范围的分页,您可能需要进行新的扫描。我认为您更有可能只是限制项目数量并停止用户进一步分页。

    获取“喜剧”类型的所有书籍

    同样,我很可能会不经常进行扫描并缓存列表。您可以考虑添加具有分区键类型和排序键 ISBN 的 GSI。就我个人而言,我会从扫描和缓存方法开始,看看你如何进行。您以后可以随时添加 GSI。

    查询名为“哈利波特”的书

    显然你不能缓存这个。使用过滤器表达式对标题进行扫描
     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
     });
    

    您可以查看 condition operators here

    获取所有 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
     });
    

    问题 1

    是的,如果您将流派存储为每个项目的字符串,并且您更改流派名称,则必须更新每个项目。或者作为替代方案,您必须在将项目呈现给用户之前更新项目的类型。

    如果您希望更改流派名称,使用流派_id 映射的想法似乎是一个不错的想法。只需有一个流派名称和 ID 表,在您的应用程序启动时加载它并将其保存在应用程序内存中。您可能需要一个管理功能来重新加载流派映射表。

    将应用程序参数保存在数据库中是一种很好的设计。

    问题 3

    当然,在 DynamoDB 中有一个 User 表。这就是我在使用 Cognito 的应用程序中执行此操作的方式。我在 Cognito 中存储了一组与用户注册相关的最小字段,然后我在用户表中的 DynamoDB 中有大量特定于应用程序的数据。

    问题 4

    关于图形模式,我会查看 this articles by AWS .不太确定这是否有帮助。

    关于amazon-web-services - DynamoDB : Best hash/sort keys for my use case [confusion with AppSync/GraphQL],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50196885/

    相关文章:

    amazon-web-services - 如何在 AWS SES 中验证电子邮件地址

    python-2.7 - aws DynamoDB boto3 查询 GROUP BY 和 ORDER BY

    javascript - 当没有数据返回时,GraphQL 变异返回类型应该是什么?

    reactjs - 从 apollo-client 中的客户端缓存中删除项目的正确方法

    node.js - 通过字符串长度搜索弹性查询

    php - 在 PHP 中通过 SSL 连接到 AWS mysql?

    amazon-web-services - AWS 上的金丝雀发布和蓝绿部署

    java - 我可以在弹性负载均衡器前面的 EC2 中构建服务限制吗?

    java - 使用 DynamoDBMapper 按列查询 DynamoDB

    amazon-web-services - AWS DynamoDB BatchWriteItem-写入容量单位