跟踪截止日期以发送提醒的设计模式
Design pattern for tracking deadlines to send reminders
我需要在某个截止日期前几天向用户发送通知。我目前的设计是使用 Cloud Scheduler 定期调用作业,然后在截止日期的小间隔内查找所有匹配记录到功能中,然后提醒它们。一切正常,除了我需要按时创建索引,这对于缩放来说不是一个好主意,因为索引单调递增的值会产生热点。那么,对于这个有效的业务需求,最佳的可扩展设计选择是什么?请注意,我已经熟悉在时间戳前加上一些值,以便在分区内跟踪排序。但是,计划作业需要发现所有截止日期,并且不可能迭代每个分区,因为随着时间的推移,即将到来的截止日期的数量可能远小于分区的数量。
你的设计不错。在数据存储(或其他数据库系统)中,您必须索引下一个触发日期(我建议采用 timestamp
格式)
当您的调度程序运行您的作业(函数、Cloud 运行 或其他)时,它会在 Now()> trigger timestamp
处执行查询。对于找到的每个文档,您只需将其发布在 PubSub 中(只需发布 ID,或根据需要发布整个文档)
- 这里的 PubSub 有助于避免热点,它被用作处理缓冲区
为您的 PubSub 主题设置推送订阅以处理消息并有效地发送通知。发送后,下一个触发时间戳会根据您的用例和规范进行更新
这里,发送通知的过程可以大规模并行(使用Cloud Function或Cloud 运行 -> 我推荐这个,因为在同一个实例上进行并发处理,因此成本最低)
请注意,您的调度程序不得过于频繁地触发您的进程。 (避免每分钟一次,最好每 10 分钟一次或更长时间一次,以避免重复通知)
恕我直言,您的特定使用上下文并不像您想象的那样对 the hotspotting problem 敏感。
当您对按字典顺序关闭的文档有非常高的 read/write 比率时,这个问题确实适用 - 在您的情况下是基于时间戳的通知计划索引。
当您修改计划时会写入索引。正如您在评论中提到的,这不是一个大问题,因为您可以轻松控制此类更新的速度。
即使您偶尔会有广泛的计划更新,我也会提出,在这种情况下由于争用而导致的索引更新延迟峰值并不是一个大问题:您提到通知是在截止日期前几天发送的 - 谁关心此类通知是否会延迟几分钟?此外 - 在读取方面,您可以使用 keys_only queries 后跟直接键查找来消除最终一致性(这对 contention/index 更新延迟很敏感)。
至于读取端 - 您通常一次对索引进行 单个 读取操作 - 查询获取要发送的通知列表间隔。一旦作业获得列表,通常不再需要访问索引:作业仅安排负责发送列表中通知的后续作业(它可以访问相应的通知实体,但不能访问索引本身!)。当然,我假设您将这些查询作业彼此舒适地分开安排。
即使您使用游标来解决查询返回的列表中的通知数量过多而无法一次性处理的情况(每次使用游标都需要重新创建原始查询上下文,很可能再次访问索引) - 您可以错开此类操作以限制并发操作的数量或使它们完全不重叠。参见How to delete all the entries from google datastore? for an example of such staggering using GAE deferred tasks (a similar approach is feasible using the now available, more generic Cloud Tasks,支持未来预定时间。
我需要在某个截止日期前几天向用户发送通知。我目前的设计是使用 Cloud Scheduler 定期调用作业,然后在截止日期的小间隔内查找所有匹配记录到功能中,然后提醒它们。一切正常,除了我需要按时创建索引,这对于缩放来说不是一个好主意,因为索引单调递增的值会产生热点。那么,对于这个有效的业务需求,最佳的可扩展设计选择是什么?请注意,我已经熟悉在时间戳前加上一些值,以便在分区内跟踪排序。但是,计划作业需要发现所有截止日期,并且不可能迭代每个分区,因为随着时间的推移,即将到来的截止日期的数量可能远小于分区的数量。
你的设计不错。在数据存储(或其他数据库系统)中,您必须索引下一个触发日期(我建议采用 timestamp
格式)
当您的调度程序运行您的作业(函数、Cloud 运行 或其他)时,它会在 Now()> trigger timestamp
处执行查询。对于找到的每个文档,您只需将其发布在 PubSub 中(只需发布 ID,或根据需要发布整个文档)
- 这里的 PubSub 有助于避免热点,它被用作处理缓冲区
为您的 PubSub 主题设置推送订阅以处理消息并有效地发送通知。发送后,下一个触发时间戳会根据您的用例和规范进行更新
这里,发送通知的过程可以大规模并行(使用Cloud Function或Cloud 运行 -> 我推荐这个,因为在同一个实例上进行并发处理,因此成本最低)
请注意,您的调度程序不得过于频繁地触发您的进程。 (避免每分钟一次,最好每 10 分钟一次或更长时间一次,以避免重复通知)
恕我直言,您的特定使用上下文并不像您想象的那样对 the hotspotting problem 敏感。
当您对按字典顺序关闭的文档有非常高的 read/write 比率时,这个问题确实适用 - 在您的情况下是基于时间戳的通知计划索引。
当您修改计划时会写入索引。正如您在评论中提到的,这不是一个大问题,因为您可以轻松控制此类更新的速度。
即使您偶尔会有广泛的计划更新,我也会提出,在这种情况下由于争用而导致的索引更新延迟峰值并不是一个大问题:您提到通知是在截止日期前几天发送的 - 谁关心此类通知是否会延迟几分钟?此外 - 在读取方面,您可以使用 keys_only queries 后跟直接键查找来消除最终一致性(这对 contention/index 更新延迟很敏感)。
至于读取端 - 您通常一次对索引进行 单个 读取操作 - 查询获取要发送的通知列表间隔。一旦作业获得列表,通常不再需要访问索引:作业仅安排负责发送列表中通知的后续作业(它可以访问相应的通知实体,但不能访问索引本身!)。当然,我假设您将这些查询作业彼此舒适地分开安排。
即使您使用游标来解决查询返回的列表中的通知数量过多而无法一次性处理的情况(每次使用游标都需要重新创建原始查询上下文,很可能再次访问索引) - 您可以错开此类操作以限制并发操作的数量或使它们完全不重叠。参见How to delete all the entries from google datastore? for an example of such staggering using GAE deferred tasks (a similar approach is feasible using the now available, more generic Cloud Tasks,支持未来预定时间。