存储和查询两个日期时间之间的公告
Storing and querying for announcements between two datetimes
背景
我必须设计一个 table 来在 DynamoDB 中存储公告。每条公告的结构如下:
{
"announcementId": "(For the frontend to identify an announcement to the backend)",
"author": "(id of author)",
"displayStartDatetime": "",
"displayEndDatetime": "",
"title": "",
"description": "",
"image": "(A url to an image)",
"link": "(A single url to another page)"
}
由于我们仍在设计 table,因此允许更改结构。特别是,announcementId
、displayStartDatetime
和 displayEndDatetime
可以更改。
主要访问模式是查找当前公告。用户有一个网页,他们可以在其中查看所有当前公告及其详细信息。
每个公告都有一个日期,用于确定何时开始显示 (displayStartDatetime
) 以及何时停止显示 (displayEndDatetime
)。在当前日期时间过去 displayEndDatetime
后,该公告仍应保留在 table 中,以供管理员参考。
开始和结束日期时间精确到分钟。
问题
理想情况下,我想要一种在一次查询中查询 table 所有当前公告的方法。
但是,我得出的结论是不可能将两个日期时间融合到一个排序键中,因为不可能对两个同等重要的数据进行排序(例如,将时间戳存储为字符串将意味着一个将比另一个 important/greater。
因此,作为妥协,我想将 table 值按 displayEndDatetime
排序,以便我可以过滤掉过去的公告。这是因为随着时间的推移,过去的公告会比未来的公告多,所以优化它会更有好处。
妥协的解决方案
目前,我(不是很好)的解决方案是:
- 使用一个 "hot" 分区键并使用
displayEndDatetime
作为排序键。
这允许我过滤掉过去的公告,但这也意味着所有数据都在一个分区中。我可以 运行 时不时地安排一项工作,将过去的公告移动到不同的间隔分区。
Scan
通过table
我相信 Scan
在执行任何过滤之前会查看 table 中的每个项目。这个解决方案似乎不如 1。但它是最简单的实现方式,它可以让我保留 announcementId
作为分区键。
Scan
table 的 GSI
由于 Scan
将查看每个项目,创建 GSI (announcementId (PK), displayEndDatetime (SK)
) 并扫描它以检索所有 announcementId
可能更有效通过。之后,可以再次请求获取所有公告。
问题
使用 DynamoDB 存储所有公告然后查找当前公告的最佳解决方案是什么?
虽然我已经列出了一些可能的解决方案来排序displayEndDatetime
,但重点仍然是在开始和结束日期时间之间查找公告。
编辑
以下是@tugberk 的背景问题的答案:
- 您预计接收的写入速率是多少(即您需要处理的每秒峰值写入)?
我不确定管理员将如何使用这个系统,公告可以非常定期(大约 3 次/天)或非常不频繁(大约 3 次/月)。
- 您预计每天存储多少新数据,您认为这将如何增长?
如上所述,这可能是每天 3 条公告或每月 3 条公告。只要我应该担心,这可能会保持不变。
- 读取速率是多少(例如每秒峰值读取)?
我预计每秒的峰值读取量约为 500-1000 reads/s。随着用户的增加,这个数字预计会增长。
- 用户一次可以看到多少条公告(即在任何时间点可以看到多少条公告)?实际想想,这应该不会超过几个(比如最多10-20个)。
我预计可查看公告的最大数量为 30-40。这是因为可能会有多个长期 运行ning 公告和短期公告。平均而言,我预计会有 5-10 条公告。
- 您乐见这里存在的数据不一致差距是多少(即您是否需要秒级精度,或者您乐于在显示和隐藏公告时延迟约 1 分钟)?
我认为公告开始显示的速度很重要,尤其是当管理员认为这是发布紧急公告(可能紧急到分钟)的好平台时。但是,何时停止显示不太重要,但为了避免混淆用户,公告应在超过其显示结束日期时间后最多 4 小时停止显示。
这类问题在这里总是很难回答,因为对答案的假设太多,很难掌握所有事实。但我会尽量给你这样的想法,这可能会帮助你考虑你的数据存储选择,并给你更多的选择。
我知道我在做什么,并且真的需要使用 DynamoDB
Edited this answer based on the OP's answers to my original questions.
由于内部原因,您确实需要为此使用 DynamoDB,因此我认为将数据存储在两个 DynamoDB table 中更适合 table 以提供几乎所有的读取和写入服务如果你有一个 table,我能想到的访问模式将命中多个分区。您可以摆脱 GSI,但是如何做并不是很简单,而且我不确定这样做是否有任何优势。
您需要优化的核心是读取,正如您提到的那样,它可以达到 2K/rps,这足以使它成为您优化架构的部分。根据您每天发布 3 条公告的假设,就写入而言,无需担心。
大体思路是这样的:
我会考虑使用一个 DynamoDB table 来处理写入,您可以将 author
标识符配置为分区键,将 announcement
标识符配置为排序键(并将您的主键作为两者的组合)。这将使您可以轻松查询给定作者的所有公告。
我还会有第二个 DynamoDB table 来处理读取,您将只存储活动公告,您的应用程序可以使用 Scan
查询和检索所有这些公告查询(即 O(N)
),正如您提到的那样,这不是一个问题,在任何时间点只会有 30-40
个活动公告。让我们想象这甚至是 500,你仍然可以接受这个结构。在分区和排序键方面,我只需要一个 active
布尔字段作为分区键,您将始终将其设置为 true
,您可以将公告 ID 作为排序键,并且将两者组合为主键。如果您关心这些公告的类型,您可以相应地调整排序键,但要确保它是唯一的(即考虑连接公告标识符,例如 {displayBeginDatetime-in-yyyyMMddHHmmss-format}-{announcementId}
。通过这种方式,您将保证您只会命中一个分区. 但是,您实际上可以简化这一点并将公告标识符作为分区键和主键,因为我几乎可以肯定 DynamoDB 会将您的所有数据存储在一个分区中,因为它会非常小。最好确认这一点,因为我我不是 100% 确定。这里的重点是你最好确保使用此查询命中一个分区。
这可能是这样工作的,我忽略了一些边缘情况:
- 在第一个DynamoDB 中记录写入以进行公告。写公告时,将
displayEndDatetime
配置为该行的TTL,假设公告到期时不需要此table中的这条记录。
- 有一份工作 运行
N
分钟(一个或多个,取决于您可以处理的数据不一致差距),这将 Scan
整个 DynamoDB table 跨分区(以分页方式进行),并决定哪些公告当前可见。然后,将您的数据写入第二个 DynamoDB table,它将在我们上面建立的结构中处理读取,这样您的消费者就可以从这个 w/o 中读取数据,因为数据已经存在,所以不用担心任何过滤过滤(例如这里所有的公告都是可见的)。请注意,Scan
在这里很好,因为你是 运行 每 N
分钟一次,假设你可以接受至少 1 分钟 + 处理时间数据不一致的差距。如果您没有很强的数据一致性要求,我建议 运行 每 10 分钟左右执行一次。
- 在读取存储系统上,也将
displayEndDatetime
配置为该行的 TTL 以便它被自动删除。
- Configure DynamoDB streams on the first DynamoDB table, which has 24 hours retention and exactly once delivery guarantee, and have a lambda consumer of this stream, which to handle when an item is deleted (will happen when TTL kicks in for a particular row) to keep a record of this announcements somewhere else, for longer retention reasons, and will need to expose it through different access pattern (e.g. show all the announcements per author so that they can reenable old announcements), as you mentioned in you question. You can configure a lambda event sourcing with DynamoDb streams,这将允许您通过重试等方式处理失败。确保这些 lambda 中的逻辑是幂等的,以便您可以安全地重试。
The below is the parts from my original question, which are still relevant to anyone who might be trying to achieve the same. So, I will leave them here but they are less relevant as the OP needs to use DynamoDB.
为什么选择 DynamoDB?
首先,我会质疑为什么您为此需要 DynamoDB,因为看起来您的需求读取量比写入量大,我认为 DynamoDB 由于其开箱即用的分区而最闪耀自然。
以下问题将帮助您了解您是否真的需要 DynamoDB,或者您是否可以使用更灵活的数据存储系统:
- 您预计接收的写入速率是多少(即您需要处理的每秒峰值写入)?
- 您预计每天存储多少新数据,您认为这将如何增长?
- 读取速率是多少(例如每秒峰值读取)?
- 用户一次可以看到多少条公告(即在任何时间点可以看到多少条公告)?实际上思考,这不应该超过几个(例如最多 10-20)。这将帮助您了解您是否需要一次拉取所有可见的公告,或者需要一个分页系统。
- 您乐见这里的数据不一致差距是多少(即您是否需要秒级精度,或者您乐于在显示和隐藏公告时延迟约 1 分钟)?
其实我不需要DynamoDB
根据我对您对此用例的消费和管理需求的假设,我相信您不需要为此使用 DynamoDB,并假设对此没有大量写入(这可能是错误的),并且如果这些假设是正确的,那么以上是为您设计的超级解决方案。假设它是正确的,我认为你最好为此使用 PostgreSQL,它可以让你轻松地更改你的访问模式,因为你认为适合进一步索引,并且对于你当前的访问模式,你可以进行范围查询在开始和结束时间。
背景
我必须设计一个 table 来在 DynamoDB 中存储公告。每条公告的结构如下:
{
"announcementId": "(For the frontend to identify an announcement to the backend)",
"author": "(id of author)",
"displayStartDatetime": "",
"displayEndDatetime": "",
"title": "",
"description": "",
"image": "(A url to an image)",
"link": "(A single url to another page)"
}
由于我们仍在设计 table,因此允许更改结构。特别是,announcementId
、displayStartDatetime
和 displayEndDatetime
可以更改。
主要访问模式是查找当前公告。用户有一个网页,他们可以在其中查看所有当前公告及其详细信息。
每个公告都有一个日期,用于确定何时开始显示 (displayStartDatetime
) 以及何时停止显示 (displayEndDatetime
)。在当前日期时间过去 displayEndDatetime
后,该公告仍应保留在 table 中,以供管理员参考。
开始和结束日期时间精确到分钟。
问题
理想情况下,我想要一种在一次查询中查询 table 所有当前公告的方法。
但是,我得出的结论是不可能将两个日期时间融合到一个排序键中,因为不可能对两个同等重要的数据进行排序(例如,将时间戳存储为字符串将意味着一个将比另一个 important/greater。
因此,作为妥协,我想将 table 值按 displayEndDatetime
排序,以便我可以过滤掉过去的公告。这是因为随着时间的推移,过去的公告会比未来的公告多,所以优化它会更有好处。
妥协的解决方案
目前,我(不是很好)的解决方案是:
- 使用一个 "hot" 分区键并使用
displayEndDatetime
作为排序键。
这允许我过滤掉过去的公告,但这也意味着所有数据都在一个分区中。我可以 运行 时不时地安排一项工作,将过去的公告移动到不同的间隔分区。
Scan
通过table
我相信 Scan
在执行任何过滤之前会查看 table 中的每个项目。这个解决方案似乎不如 1。但它是最简单的实现方式,它可以让我保留 announcementId
作为分区键。
Scan
table 的 GSI
由于 Scan
将查看每个项目,创建 GSI (announcementId (PK), displayEndDatetime (SK)
) 并扫描它以检索所有 announcementId
可能更有效通过。之后,可以再次请求获取所有公告。
问题
使用 DynamoDB 存储所有公告然后查找当前公告的最佳解决方案是什么?
虽然我已经列出了一些可能的解决方案来排序displayEndDatetime
,但重点仍然是在开始和结束日期时间之间查找公告。
编辑
以下是@tugberk 的背景问题的答案:
- 您预计接收的写入速率是多少(即您需要处理的每秒峰值写入)?
我不确定管理员将如何使用这个系统,公告可以非常定期(大约 3 次/天)或非常不频繁(大约 3 次/月)。
- 您预计每天存储多少新数据,您认为这将如何增长?
如上所述,这可能是每天 3 条公告或每月 3 条公告。只要我应该担心,这可能会保持不变。
- 读取速率是多少(例如每秒峰值读取)?
我预计每秒的峰值读取量约为 500-1000 reads/s。随着用户的增加,这个数字预计会增长。
- 用户一次可以看到多少条公告(即在任何时间点可以看到多少条公告)?实际想想,这应该不会超过几个(比如最多10-20个)。
我预计可查看公告的最大数量为 30-40。这是因为可能会有多个长期 运行ning 公告和短期公告。平均而言,我预计会有 5-10 条公告。
- 您乐见这里存在的数据不一致差距是多少(即您是否需要秒级精度,或者您乐于在显示和隐藏公告时延迟约 1 分钟)?
我认为公告开始显示的速度很重要,尤其是当管理员认为这是发布紧急公告(可能紧急到分钟)的好平台时。但是,何时停止显示不太重要,但为了避免混淆用户,公告应在超过其显示结束日期时间后最多 4 小时停止显示。
这类问题在这里总是很难回答,因为对答案的假设太多,很难掌握所有事实。但我会尽量给你这样的想法,这可能会帮助你考虑你的数据存储选择,并给你更多的选择。
我知道我在做什么,并且真的需要使用 DynamoDB
Edited this answer based on the OP's answers to my original questions.
由于内部原因,您确实需要为此使用 DynamoDB,因此我认为将数据存储在两个 DynamoDB table 中更适合 table 以提供几乎所有的读取和写入服务如果你有一个 table,我能想到的访问模式将命中多个分区。您可以摆脱 GSI,但是如何做并不是很简单,而且我不确定这样做是否有任何优势。
您需要优化的核心是读取,正如您提到的那样,它可以达到 2K/rps,这足以使它成为您优化架构的部分。根据您每天发布 3 条公告的假设,就写入而言,无需担心。
大体思路是这样的:
我会考虑使用一个 DynamoDB table 来处理写入,您可以将
author
标识符配置为分区键,将announcement
标识符配置为排序键(并将您的主键作为两者的组合)。这将使您可以轻松查询给定作者的所有公告。我还会有第二个 DynamoDB table 来处理读取,您将只存储活动公告,您的应用程序可以使用
Scan
查询和检索所有这些公告查询(即O(N)
),正如您提到的那样,这不是一个问题,在任何时间点只会有30-40
个活动公告。让我们想象这甚至是 500,你仍然可以接受这个结构。在分区和排序键方面,我只需要一个active
布尔字段作为分区键,您将始终将其设置为true
,您可以将公告 ID 作为排序键,并且将两者组合为主键。如果您关心这些公告的类型,您可以相应地调整排序键,但要确保它是唯一的(即考虑连接公告标识符,例如{displayBeginDatetime-in-yyyyMMddHHmmss-format}-{announcementId}
。通过这种方式,您将保证您只会命中一个分区. 但是,您实际上可以简化这一点并将公告标识符作为分区键和主键,因为我几乎可以肯定 DynamoDB 会将您的所有数据存储在一个分区中,因为它会非常小。最好确认这一点,因为我我不是 100% 确定。这里的重点是你最好确保使用此查询命中一个分区。
这可能是这样工作的,我忽略了一些边缘情况:
- 在第一个DynamoDB 中记录写入以进行公告。写公告时,将
displayEndDatetime
配置为该行的TTL,假设公告到期时不需要此table中的这条记录。 - 有一份工作 运行
N
分钟(一个或多个,取决于您可以处理的数据不一致差距),这将Scan
整个 DynamoDB table 跨分区(以分页方式进行),并决定哪些公告当前可见。然后,将您的数据写入第二个 DynamoDB table,它将在我们上面建立的结构中处理读取,这样您的消费者就可以从这个 w/o 中读取数据,因为数据已经存在,所以不用担心任何过滤过滤(例如这里所有的公告都是可见的)。请注意,Scan
在这里很好,因为你是 运行 每N
分钟一次,假设你可以接受至少 1 分钟 + 处理时间数据不一致的差距。如果您没有很强的数据一致性要求,我建议 运行 每 10 分钟左右执行一次。 - 在读取存储系统上,也将
displayEndDatetime
配置为该行的 TTL 以便它被自动删除。 - Configure DynamoDB streams on the first DynamoDB table, which has 24 hours retention and exactly once delivery guarantee, and have a lambda consumer of this stream, which to handle when an item is deleted (will happen when TTL kicks in for a particular row) to keep a record of this announcements somewhere else, for longer retention reasons, and will need to expose it through different access pattern (e.g. show all the announcements per author so that they can reenable old announcements), as you mentioned in you question. You can configure a lambda event sourcing with DynamoDb streams,这将允许您通过重试等方式处理失败。确保这些 lambda 中的逻辑是幂等的,以便您可以安全地重试。
The below is the parts from my original question, which are still relevant to anyone who might be trying to achieve the same. So, I will leave them here but they are less relevant as the OP needs to use DynamoDB.
为什么选择 DynamoDB?
首先,我会质疑为什么您为此需要 DynamoDB,因为看起来您的需求读取量比写入量大,我认为 DynamoDB 由于其开箱即用的分区而最闪耀自然。
以下问题将帮助您了解您是否真的需要 DynamoDB,或者您是否可以使用更灵活的数据存储系统:
- 您预计接收的写入速率是多少(即您需要处理的每秒峰值写入)?
- 您预计每天存储多少新数据,您认为这将如何增长?
- 读取速率是多少(例如每秒峰值读取)?
- 用户一次可以看到多少条公告(即在任何时间点可以看到多少条公告)?实际上思考,这不应该超过几个(例如最多 10-20)。这将帮助您了解您是否需要一次拉取所有可见的公告,或者需要一个分页系统。
- 您乐见这里的数据不一致差距是多少(即您是否需要秒级精度,或者您乐于在显示和隐藏公告时延迟约 1 分钟)?
其实我不需要DynamoDB
根据我对您对此用例的消费和管理需求的假设,我相信您不需要为此使用 DynamoDB,并假设对此没有大量写入(这可能是错误的),并且如果这些假设是正确的,那么以上是为您设计的超级解决方案。假设它是正确的,我认为你最好为此使用 PostgreSQL,它可以让你轻松地更改你的访问模式,因为你认为适合进一步索引,并且对于你当前的访问模式,你可以进行范围查询在开始和结束时间。