java - 我如何才能为向 DynamoDB 的大规模迁移实现更好的吞吐量?

标签 java performance amazon-dynamodb

我一直在测试将大数据迁移到 dynamo,我们打算今年夏天在我们的生产账户中进行这项测试。我运行了一个测试,将大约 32 亿个文档批量写入我们的 dynamo 表,该表有一个散列键和范围键以及两个部分索引。每个文件很小,不到1k。虽然我们成功地在大约 3 天内完成了项目的编写,但我们对所体验到的 Dynamo 性能感到失望,并且正在寻求有关如何改进的建议。

为了进行此迁移,我们使用了 2 个 ec2 实例 (c4.8xlarges)。每个运行最多 10 个我们的迁移程序进程;我们已经通过一些内部参数在进程之间分配了工作,并且知道一些进程会比其他进程运行更长时间。每个进程在我们的 RDS 数据库中查询 100,000 条记录。然后,我们将它们分成 25 个分区,并使用 10 个线程的线程池来调用 DynamoDB java SDK 的 batchSave() 方法。每次调用 batchSave() 仅发送 25 个文档,每个文档小于 1k,因此我们预计每个文档仅向 AWS 发出一次 HTTP 调用。这意味着在任何给定时间,我们可以在服务器上拥有多达 100 个线程,每个线程使用 25 条记录调用 batchSave。在此期间,我们的 RDS 实例很好地处理了对它的查询负载,我们的 2 个 EC2 实例也是如此。在 ec2 方面,我们没有最大化我们的 cpu、内存或网络输入或网络输出。我们的写入没有按哈希键分组,因为我们知道这会减慢 dynamo 写入速度。通常,在一组 100,000 条记录中,它们被分成 88,000 个不同的哈希键。我创建的 dynamo 表最初具有 30,000 的写入吞吐量,但在测试期间的某个时刻配置了高达 40,000 的写入吞吐量,因此我们的理解是在 dynamo 端至少有 40 个分区来处理这个问题。

在此期间,我们在对 dynamo 的 batchSave() 调用中看到了非常多变的响应时间。在我为每个 ec2 实例运行 100 个线程的 20 分钟跨度内,平均时间为 0.636 秒,但中值仅为 0.374 秒,因此我们有很多调用花费的时间超过一秒。我希望看到从 EC2 实例到 dynamo 进行这些调用所需的时间更加一致。我们的 dynamo 表似乎配置了足够的吞吐量,EC2 实例的 CPU 使用率低于 10%,网络进出看起来很健康,但还没有接近极限。控制台中的 CloudWatch 图表(相当糟糕......)没有显示任何写入请求的限制。

在我获取这些样本时间后,我们的一些进程完成了它们的工作,因此我们在 ec2 实例上运行的线程较少。当发生这种情况时,我们发现调用 dynamo 的响应时间得到了显着改善。例如当我们在 ec2 实例上运行 40 个线程而不是 100 个线程时,每个线程都调用 batchSave,响应时间提高了 5 倍以上。但是,即使增加了更好的响应时间,我们也没有看到写入吞吐量有所提高。似乎无论我们将写入吞吐量配置成什么,我们都从未真正看到实际吞吐量超过 15,000。

我们需要一些关于如何最好地在 Dynamo 迁移中实现更好性能的建议。当然,我们今年夏天的生产迁移将是时间敏感的,到那时,我们将寻求迁移大约 40 亿条记录。有没有人对我们如何实现整体更高的吞吐率有任何建议?如果我们愿意在迁移期间为我们的主索引支付 30,000 个单位的写入吞吐量,我们如何才能真正实现接近于此的性能?

最佳答案

BatchWrite 延迟的一个组成部分是批处理中花费时间最长的 Put 请求。考虑到您必须遍历 DynamoDBMapper.FailedBatch 的列表,直到它为空,您的进度可能不够快。考虑并行运行多个 DynamoDBMapper.save()调用而不是 batchSave,这样您就可以独立地为您编写的每个项目取得进展。

同样,Cloudwatch 指标是 1 分钟指标,因此您可能会遇到被 1 分钟窗口掩盖的消费和限制峰值。默认情况下,SDK 将重试受限调用 10 times,这一事实使情况更加复杂。在向客户端公开 ProvisionedThroughputExceededException 之前,很难确定实际节流发生的时间和地点。为了提高您的理解,请尝试减少 SDK 重试次数,请求 ConsumedCapacity=TOTAL,使用 Guava RateLimiter self 节流您的写入,如 rate-limited scan blog post 中所述。 ,并记录受限制的主键以查看是否出现任何模式。

最后,表的分区数量不仅取决于您在表上配置的读写容量单位的数量。它还由您存储在表中的数据量驱动。一般一个partition最多存放10GB的数据,然后再split。因此,如果您只是写入表而不删除旧条目,则表中的分区数将无限制地增长。这会导致 IOPS 饥饿——即使您提供 40000 WCU/s,如果由于数据量原因您已经有 80 个分区,则 40k WCU 将分布在 80 个分区中,平均每个分区 500 WCU。要控制表中过时数据的数量,您可以使用限速清理过程来扫描和删除旧条目,或者使用 rolling time-series tables。 (幻灯片 84-95)并删除/迁移整个数据表,因为它们变得不那么相关了。滚动时间序列表比限速清理更便宜,因为您不使用 DeleteTable 操作消耗 WCU,而每个 DeleteItem 调用至少消耗 1 个 WCU。

关于java - 我如何才能为向 DynamoDB 的大规模迁移实现更好的吞吐量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30199550/

相关文章:

amazon-dynamodb - dynamodb 更新表达式是否强一致?

java - 具有独占启动键的 DynamoDB 全局二级索引

java - 使用 JNI 授予对 Android 应用程序的 C/C++ native 端的 root 访问权限,这似乎是不可能的

java - 补充资源包

c# - 在 C# List<String> 中搜索子字符串的更快方法

java - 加快 HashMap 写入速度

amazon-s3 - AWS S3 中是否存在乐观锁定?

java - 在 Java 中从模板访问 Flash

Java 使用 JPA 2.1 和 @ManyToOne 上的 hibernate 部分加载实体

java - 如何提高 double 印的性能?