aws s3 client
将AWS S3 .NET客户端LOH分配减少98%
内容 问题发现 为什么会出问题呢? 引入最佳魔术数— 81,920 空闲的手 还有一件事 TLDR-给我好东西 脚注 问题发现 我们在Codeweavers所做的一件事就是帮助人们找到他们的下一辆车。 通常,这涉及到客户看到他们要购买的汽车-我的意思是,您会在不看外观的情况下购买汽车吗? 持有这个责任该应用程序是为淫秽金额分配,时间GC上花费的,而像饼干怪兽吃得好...饼干一般吃RAM 最坏 的罪犯。
我们不时地希望从生产环境中获取此应用程序的内存转储。 我们已经完成了足够的时间,因此我们已经将最常见的诊断步骤自动化了,并将其捆绑到一个称为ADA(自动转储分析)的小工具中。 如果你有兴趣,你可以找到的工具, 在这里 ,所有谈到本文中的代码在这里 。
我们运行的分析器之一是转储在大对象堆(LOH)上找到的所有byte []数组。 针对我们的8 GB内存转储运行该分析器后,我们发现了数百个byte []数组,长度为131,096或131,186。 好吧,这很奇怪。 在Notepad ++中打开某些文件只会给我们带来很多随机字符。
我把科学方法丢到窗外一秒钟,我决定将所有转储的byte []数组重命名为* .jpg-嘿,现在有些文件现在显示缩略图! 经过仔细检查,大约50%的文件是图像。 其余50%都无法完全打开为图像。 在Notepad ++中打开少数非图像文件显示,它们在文件开头都有一行类似于此的行:-
0;chunk-signature=48ebf1394fcc452801d4ccebf0598177c7b31876e3fbcb7f6156213f931b261d
好的,这开始变得更有意义了。 长度为131,096的byte []数组是纯图像。 不是图像的byte []数组的长度为131,186,并且在其余内容之前具有块签名行。 我想 签名是内容的SHA256哈希。
在继续进行之前,值得确定该应用程序在图像处理方面的繁忙程度。 我们使用AWS SNS和SQS在我们的服务器场中分布了所有图像处理。 使用CloudWatch指标,我们可以轻松看到:
好了,所以相当 繁忙。 值得注意的是,在进行任何以 性能为中心的工作之前,请务必确定执行代码的频率和当前成本。 如果代码路径的成本很高(例如,花费二十秒钟),但每天只命中一次,则不值得研究。 但是,如果相同的代码路径被击中很多(例如一天一百万次),那么绝对值得研究。
此时,我想到了两个罪魁祸首。 我们已经建立了有问题的应用程序来做很多图像处理。 但是有一些移动的部分和两种启动图像处理的方式:-
图片被推送给我们 我们从SFTP提取图像 之后,我们转换图像,然后将其上传到AWS S3 。 在此阶段,我倾向于使用SFTP,因为它可能需要验证从服务器接收到的每个块。 但是,随着我的预感,我开始追逐野鹅,于是我无视我的直觉,便将大块签名插入Google并粉碎了输入。 谷歌指出AWS S3是罪魁祸首 。 但这仅仅是理论,我们需要证明这一点。
如果我们上传相同的图像十次并使用dotTrace来查看LOH,则会看到一个有趣的模式:
每次我们在AWS S3 .NET客户端上调用PutObject时,看起来固定的成本为0.3 MB。 这是一个问题,因为这意味着每次使用PutObject时,每次上载都要付出0.3 MB的高额费用。 只想确认一下; 如果将上传次数从十次增加到一百次会发生什么?
是的,我们可以肯定地说,每次调用PutObject都会产生0.3 MB的昂贵分配。 更进一步,并使用ProcDump转储该过程:-
procdump64.exe -ma -64 AWS-S3.exe
通过ADA运行转储文件,我们看到有两组byte []数组具有完全相同的特性; 50%的长度为131,096,其他50%的长度为131,186。 重命名时,文件的一半是图像,文件的一半具有块签名起始行。 至此,我们可以确定AWS S3 .NET客户端正在将byte []数组直接分配到LOH上。 那是 个问题。
为什么会出问题呢? LOH是一个已收集 但从未压缩 的内存区域,尽管从现在开始可以进行.NET v4.5.1 压缩 ,但警告字眼的LOH压缩代价很高。 每兆字节大约2.3毫秒 。 一条很好的经验法则是,短暂的物体绝 不能进入LOH。
等于或大于85,000字节的对象直接进入LOH。 LOH的操作与其他内存区域大不相同。 会定期收集和压缩其他内存区域,这意味着您可以在垃圾收集器运行后将新对象添加到末尾。 而LOH尝试将新分配的对象放入废弃对象遗留的剩余空间中。 如果新分配的对象的大小与可用空间完全相同或较小,则此方法很好用。 如果找不到空间,则LOH必须增长以容纳该对象。
它有助于像书架一样思考它; 在内存的其他区域中,不再使用的书籍被简单地扔掉,剩余的书籍被推到一起,任何新书都放在书架的末端。
在不可能的LOH内,将丢弃书籍(对象),并记录该空间中以前的页数(字节),并在下次将书分配到该书架时(LOH)它试图找到一个可以容纳那么多页面(字节)的空白空间。 如果书架不能容纳新分配的书(对象),则必须扩展书架以容纳新书(对象)。
垃圾收集器将从LOH收集死对象,与此同时,将新对象分配给LOH。 这可能导致长时间运行的应用程序出现生命周期的情况,其中LOH大小已增加到几GB(因为新对象无法容纳到现有的空白空间中),但实际上仅包含几个活动对象。 这称为LOH碎片。 在这种情况下,我们非常 幸运,因为将其放入LOH的byte []数组有两个大小; 131,186和131,096。 这意味着,无论哪种大小的旧对象死亡并被收集,新分配的对象都恰好适合将其放入空白空间的大小。
好吧,回到有趣的东西。
引入最佳魔术数— 81,920 多亏了dotTrace,我们才能够准确地确定导致LOH碎片的原因。 它也向我们显示,每次调用PutObject的固定成本0.3 MB发生在ChunkedUploadWrapperStream的构造函数内部:
快速访问该文件在AWS-SDK-网库。 显示创建的两个byte []数组的长度至少为131,072:-
这就是为什么将这些byte []数组直接分配给LOH的原因,它们高于LOH阈值(85,000个字节)。 此时,有几种可能的解决方案:
使用System.Buffers从byte []数组池中租用两个byte []数组 使用Microsoft.IO.RecycableMemoryStream并使用Stream的池直接对传入流进行操作 公开DefaultChunkSize,以便API的使用者可以自行设置 将DefaultChunkSize降低到低于LOH阈值的数字(85,000字节) 第一个和第二个解决方案可能是获得最大胜利的解决方案,但这将需要大量的请求,并引入了库维护人员可能不希望的依赖项²。 第三种解决方案意味着图书馆的使用者必须了解该问题并将其设置为合理的数目,以避免分配LOH。 不,似乎第四个解决方案最有可能被接受,破坏现有功能的可能性也最小。
我们所需要的只是一个低于85,000的数字,通常像84,000这样的数字非常合适。 但是,在发现此问题之前几周,当我偶然发现这个宝石时,我正在四处寻找参考源 (正在研究另一个问题):
Windows内存页的大小为4,096字节 ,因此,选择低于LOH阈值(85,000字节)的内存页是很有意义的。 是时候分叉,分支, 创建问题并提出拉取请求了 。
幸运的是,我们可以在本地进行更改³,看看有什么好处。 通过PutObject上传同一图像的一百次统计:-
空闲的手 在等待对我的请求请求进行审核的同时,我决定去查阅AWS S3文档,偶然发现了预签名URL的概念。 听起来很有趣! 创建上传器的V2:-
上载相同文件一百次时,我们看到它具有以下统计信息:-
那真是太棒了,而我们实际上要做的就是阅读文档! 好吧,这是不对的,您的好处是可以阅读包含所有内容的摘要文章。 您在此处看到的工作是在一周的时间内完成的,介于两次客户工作之间。
使用GetPreSignedURL的一个小缺点是,如果修改了GetPreSignedUrlRequest并且未相应地修改WebRequest,则AWS将返回HTTP 403 Forbidden (例如,删除WebRequest上的XAmzAclHeader)。 这是因为客户端哈希和服务器哈希不再匹配。
还有一件事 感谢我的上一篇文章,我了解了什么是书呆子狙击 -我对自己做了很多事情。 在此阶段,我感到对其他东西可以省掉有点头晕,我一直在看着LOH上还剩下0.4 MB。 同样,dotTrace将我们指向代码路径的方向,导致向LOH分配了0.4 MB:-
耶克斯, 那看起来很严肃 。 安静地退后一步,尝试另一种策略; 我们知道一个预签名的URL看起来像这样:
https://##bucket_name##.s3.##region_name##.amazonaws.com/##path##/##file_name##?X-Amz-Expires=300&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=##access_key##/20180613/##region_name##/s3/aws4_request&X-Amz-Date=20180613T233349Z&X-Amz-SignedHeaders=host;x-amz-acl&X-Amz-Signature=6bbcb0f802ad86022674e827d574b7a34a00ba76cd1411016c3581ba27fa5450
由于AWS非常友好地发布了他们的签名过程 ,因此我们应该能够自行生成该URL。 在这一点上,我承认我已经准备好接受失败,只留下0.4 MB的内存在LOH上。 我真的不喜欢的代码里姆斯我正要写可能 消除剩余的0.4 MB将是值得的。
直到我发现我想要的例子为止。 我的工作量大大减少了; V3诞生了:-
V3只是一个实验,旨在了解可能的情况,因为所获得的收益和维护的代码量不大,这并不是我们在生产代码中实际使用的东西。 预签名URL的发现是这里的主要优势:-
同时,我的拉取请求已合并并发布到AWSSDK.Core的3.3.21.19版本中。 时间轴快速概述:-
2018–03–07 —在aws-sdk-net存储库上创建的问题 2018年3月13日-发送拉取请求 2018-03-29 —合并请求请求 2018–03–29 —新版本的AWSSDK.Core已发布到NuGet 我喜欢开源。
TLDR-给我好东西 低于3.3.21.19的AWSSDK.Core版本导致在AWS S3 .NET客户端上每次PutObject调用的固定成本为0.3 MB。 在3.3.21.19及更高版本中已对此进行了纠正。 对于特别热的代码路径,值得探索在AWS S3 .NET客户端上使用GetPreSignedURL,因为在我们的上下文和用例中,LOH分配减少了98%。
找到我Twitter , LinkedIn或GitHub 。
脚注 ¹另一个原因可能 是WinDbg仍然吓到我了。
²话虽如此, 最近的一次对话已经开始利用.NET Core的优点。
³确保不像某些 人那样发布版本-好的,是我
本来在发表 dev.to 。
翻译自: https://hackernoon.com/its-not-your-code-vol-i-c06fac8784df
aws s3 client