【问题标题】:Order Results by Priority & Group Values & then filter results按优先级和组值排序结果,然后过滤结果
【发布时间】:2018-05-15 02:21:11
【问题描述】:

这是我的 DynamoDB 当前数据:

我的目标是创建一个查询,该查询过滤组集中的结果(喜欢“默认”),然后按优先级排序,然后将结果过滤到已登录 == true 和状态 == 空闲的那些。

在 SQL 中是这样的

SELECT * 
FROM userstatustable 
WHERE group == "default" 
  AND loggedIn == true AND status == "idle" 
ORDER BY priority DESC 
LIMIT 1

我将如何创建查询来执行此操作?

下面是我对 DynamoDB 表的 serverless.yml 文件描述。

userStatusTable: #This table is used to track a users current status.
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:custom.userStatusTable}
        AttributeDefinitions: #UserID in this case will be created once and constantly updated as it changes with status regarding the user.
          - AttributeName: userId
            AttributeType: S
        KeySchema:
          - AttributeName: userId
            KeyType: HASH
        ProvisionedThroughput:
            ReadCapacityUnits: ${self:custom.dynamoDbCapacityUnits.${self:custom.pstage}}
            WriteCapacityUnits: ${self:custom.dynamoDbCapacityUnits.${self:custom.pstage}}

我尝试过的事情:

以下是我目前的代码:

 const userStatusParams = {
        TableName: process.env.USERSTATUS_TABLE,
        FilterExpression: "loggedIn = :loggedIn and #s = :status and contains(#g,:group) ",
        //Limit: 1,
        ExpressionAttributeValues: {
          ":loggedIn": true,
          ":status" : "idle",
          ":group" : "DEFAULT"
        },
        ExpressionAttributeNames: {"#s": "status","#g" : "group"}
      };
      var usersResult;
      try {
        usersResult = await dynamoDbLib.call("scan", userStatusParams);
        console.log(usersResult);
      }catch (e) {
        console.log("Error occurred querying for users belong to group.");
        console.log(e);
      }

这使用扫描并且能够返回所有符合条件的结果......但是它不会按优先级对结果进行排序降序。

注意:status 和 group 显然是保留关键字,所以我不得不使用 ExpressionAttributeNames 来说明这一点。另请注意,此表最终将有数千名用户。

【问题讨论】:

  • 我正在考虑可能为优先级创建一个全局二级索引,并且哈希是 userId,这样至少可以按优先级排序。这行得通吗?
  • scan 不使用索引,你可以吗?否则,组上的全局索引,但它需要是字符串而不是集合,这需要用户所在组的一条记录。
  • 我有一个想法,类型或多或少总是相同的,所以我可以使用类型作为哈希键,排序键优先。
  • 哈希键的单个值与扫描相同。
  • @JosephAstrahan 我看到你的表中没有范围键。所以你可以把priority作为范围键,然后上面的scan会自动返回priority排序的结果。

标签: node.js amazon-web-services amazon-dynamodb serverless-framework


【解决方案1】:

所以我找到了解决这个问题的有趣方法。

这是我的新代码。

const userStatusParams = {
        TableName: process.env.USERSTATUS_TABLE,
        IndexName:"typePriorityIndex",
        FilterExpression: "loggedIn = :loggedIn and #s = :status and contains(#g,:group) ",
        KeyConditionExpression: "#t = :type and priority >= :priority",
        Limit: 1,
        ExpressionAttributeValues: {
          ":loggedIn": true,
          ":status" : "idle",
          ":group" : "DEFAULT",
          ":priority" : 0,
          ":type" : "admin"
        },
        ExpressionAttributeNames: {"#s": "status","#g" : "group", "#t" : "type"}
      };
      var usersResult;
      try {
        usersResult = await dynamoDbLib.call("query", userStatusParams);
        console.log(usersResult);
      }catch (e) {
        console.log("Error occurred querying for users belong to group.");
        console.log(e);
      }

注意使用IndexName:“typePriorityIndex”,这里的技巧是在你的表中找到一些东西或制作一些东西,记录都将具有相同的哈希键,然后排序键应该是你想要的想要排序,在我的情况下是优先级。

索引看起来像这样给出一个想法。

我的无服务器文件看起来像这样用于定义它

userStatusTable: #This table is used to track a users current status.
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:custom.userStatusTable}
        AttributeDefinitions: #UserID in this case will be created once and constantly updated as it changes with status regarding the user.
          - AttributeName: userId
            AttributeType: S
          - AttributeName: priority
            AttributeType: N
          - AttributeName: type
            AttributeType: S
        KeySchema:
          - AttributeName: userId
            KeyType: HASH
        ProvisionedThroughput:
            ReadCapacityUnits: ${self:custom.dynamoDbCapacityUnits.${self:custom.pstage}}
            WriteCapacityUnits: ${self:custom.dynamoDbCapacityUnits.${self:custom.pstage}}
        GlobalSecondaryIndexes:
          - IndexName: typePriorityIndex
            KeySchema:
              - AttributeName: type
                KeyType: HASH
              - AttributeName: priority
                KeyType: RANGE
            Projection:
              ProjectionType: ALL
            ProvisionedThroughput:
              ReadCapacityUnits: ${self:custom.dynamoDbCapacityUnits.${self:custom.pstage}}
              WriteCapacityUnits: ${self:custom.dynamoDbCapacityUnits.${self:custom.pstage}}

【讨论】:

  • 我不确定我的解决方案是双重订购吗?因此,如果我想按“this”然后按“that”订购,我目前不知道该怎么做。也许我会创建更多索引?
  • 我的类型可能因用户而异,适用于不同的用户类型。对于该类型的用户,我希望它按优先级排序。理想情况下,按组过滤会更好,然后排序。这将需要再次更改数据库,如有必要我会这样做。在这种情况下,您建议如何设置数据库?
  • 另外我仍然对为什么我的类型和优先级全局索引在这种情况下不好?
  • 我会用另一种组设计来更新我的答案。
【解决方案2】:

链接到我的other answer,它按照设计使用主表。

此方法需要将 UserStatus 中的group 建模从带有字符串的单个记录修改为带有字符串的多个记录。这是因为 DynamoDB 不支持(不过,这是一个很好的功能请求)对集合进行键控。

主表用于更新/插入/删除,如下所示:

+--------+---------+-------+----------+----------+--------+
| userId | group   | type  | priority | loggedIn | status |
+--------+---------+-------+----------+----------+--------+
| 123    | default | admin | 1        | true     | idle   |
+--------+---------+-------+----------+----------+--------+
| 123    | orange  | admin | 1        | true     | idle   |
+--------+---------+-------+----------+----------+--------+
| 124    | default | admin | 3        | false    | idle   |
+--------+---------+-------+----------+----------+--------+
| 125    | orange  | admin | 2        | false    | idle   |
+--------+---------+-------+----------+----------+--------+
  • 分区/哈希键:userId
  • 排序键:组

在(组、优先级)上设置 GSI。这将用于查询。是的,为此索引选择的组合会有重复项:DynamoDB 不会为此烦恼,而且效果很好。

+---------+----------+--------+-------+----------+--------+
| group   | priority | userId | type  | loggedIn | status |
+---------+----------+--------+-------+----------+--------+
| default | 1        | 123    | admin | true     | idle   |
+---------+----------+--------+-------+----------+--------+
| default | 3        | 124    | admin | false    | idle   |
+---------+----------+--------+-------+----------+--------+
| orange  | 1        | 123    | admin | true     | idle   |
+---------+----------+--------+-------+----------+--------+
| orange  | 2        | 125    | admin | false    | idle   |
+---------+----------+--------+-------+----------+--------+

任务:

  • 更新此表上的用户需要更新/插入与用户所属的组一样多的行;
  • 删除用户意味着删除该用户的所有项目。
  • 查询由group = :group and priority >= :priority 完成,过滤status = 'idle' and loggedIn = true
    • 一种变体是按状态或登录进行排序,因为您使用它们进行过滤,这有助于使查询更具选择性,然后在客户端上按优先级排序

我应该采用这种方法吗?我认为当有很多组并且单个组包含多达 20% 的总用户,并且用户属于 2 或 2 个组时,这是一个很好的设计。

【讨论】:

  • 我真的很喜欢这种方法!非常感谢您花时间写这篇文章。比我的方法更有意义
  • 去尝试实施到明天第一件事会让你知道进展如何
  • 抱歉我的延误会在我正确测试后通知您
【解决方案3】:

索引与排序无关。排序只是用于有效检索行的一种方法,因为在排序数组中的搜索可以在对数时间O(log n)中完成,而不是线性时间O (n)。这只是按排序顺序返回行的结果。但让我们专注于以更少的努力找到要返回的确切行(I/O,例如磁盘读取)的能力。

这种类型的查询(按组、状态和更多列)的过滤需求对于 DynamoDB fo 进程的高效处理来说确实很困难。效率是指 DynamoDB 需要从磁盘检索多少行以确定哪些行返回给客户端。如果它返回从表中读取的总行数的 10%,则效率不高。这就是为什么普通的Scanfilters 不如indexed query 好。过滤器是个谎言,因为它们仍然从数据库中读取项目并计入预置容量。索引查询将从接近它实际返回的数字的存储行中检索。这是通过 DynamoDB 实现的,但仅限于 单个分区(具有相同分区/哈希键的项目)和 范围(以 >=、

为什么在进行扫描时没有按排序键排序返回行?因为 DynamoDB 在 Item Collections 中使用排序键,每个集合由 散列键 确定。例如,当结果集包含 2 个唯一的哈希键时,结果集将包含按排序键排序的 2 个单独的部分,换句话说,行不会按单个方向排序,它们'将在结果集的中间重新启动。 需要在内存中排序才能有一个排序集合

为什么不在列上创建一个所有行都可以有一个值的索引呢?如果我们运行扫描,则将返回按优先级(排序键)排序的行。但是所有项目都包含相同的字段值,这是数据异常

那么我应该什么时候创建索引呢?

  • 我想查询一个对于一些/许多行可能为空的字段,因此索引包含的项目将少于整个表;
  • 我的查询将应用一个范围运算符,该运算符将能够选择一小部分数据;

鉴于group 属性可能是最具选择性的,在全局索引上对该属性进行散列会更快,但这会改变模型,需要将每个组存储在一个单独的项目,而不是使用字符串集。这在 NoSQL 世界中不是很方便,需要更多的关系模型。


因此,考虑到可以使用扫描但没有单独的索引,一种方法是运行扫描并在内存中排序。在 node.js 中使用Array#sort() 方法来完成。性能特征更接近二级索引的方法,在这种情况下仅使用索引只会浪费资源。因为如果对索引的查询/扫描返回相同数量的信息,则对表进行扫描会采用表方法。 记住:索引在检索行时具有选择性

我如何知道这是否适合我的用例?好吧,这不是一个明确的规则,但我会说如果你想检索超过 50% 的表行,这是可以的。就成本而言,保留一个单独的索引是没有回报的。甚至可能会考虑另一种设计,因为这不是很有选择性。现在,如果您想要 20% 或更少的数据,那么可以考虑另一种方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-23
    相关资源
    最近更新 更多