按优先级排序结果&集团价值观和然后过滤结果

时间:2017-12-01 02:03:22

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

这是我的DynamoDB当前数据:

enter image description here

我的目标是创建一个查询,过滤组集中的结果(类似于“默认”),然后按优先级排序,然后将结果过滤到那些其中loggedIn == true和status == idle。< / p>

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

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

注意:状态和群组显然是保留关键字,所以我不得不使用ExpressionAttributeNames来解释这一点。另请注意,此表最终将包含数千名用户。

3 个答案:

答案 0 :(得分:1)

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

此类查询的过滤需求(按组,状态和更多列)对DynamoDB进行高效处理确实很困难。效率我指的是DynamoDB需要从磁盘检索多少行来确定返回到客户端的行。如果它返回从表中读取的总行数的10%,则效率不高。这就是常规Scanfilters一起不如indexed query的原因。过滤器是谎言,因为它们仍然从数据库中读取项目,并且计入预配置容量。索引查询将从存储行中检索接近其实际返回的数字。这是通过DynamoDB实现的,但限制为单个分区(具有相同分区/散列键的项目)和范围(以&gt; =,&lt; =开头) )用于排序键。

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

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

那我何时应该创建一个索引?

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

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

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

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

答案 1 :(得分:1)

链接到我按照设计使用主表的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
    • 一个变体是对状态或loggedIn进行排序,因为您使用它们进行过滤,这有助于使查询更具选择性,然后对客户端的优先级进行排序

我应该遵循这种方法吗?我认为这是一个很好的设计,当有很多组,一个组占用户总数的20%,用户属于2或2组。

答案 2 :(得分:-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:&#34; typePriorityIndex&#34;,这里的技巧是在你的表中找到一些东西或在记录中都有相同的东西并使其成为哈希键,然后是排序键应该是你想要排序的东西,在我看来是优先考虑的。

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

enter image description here

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

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