我们压缩发布/订阅消息等内容,节省大量资金

2021-01-04 20:28:24

压缩是可以解决大量问题的技巧。通常,您的工具会透明地压缩内容:大多数现代浏览器要求使用gzippedHTTP有效负载,并且某些文件系统可以配置为压缩块而无需用户询问。

在众所周知的用例之外,还有多种机会可以通过利用压缩来提高效率或节省大量资金。留意常见用例很有用,因此您可以在机会出现时抓住这些机会。

作为最近的示例,我的团队正在将日志从一个Elasticsearch集群迁移到另一个集群。尽管不是Big Data™,但该集群有100亿个日志条目,或大约60TB的原始JSON。

拥有解决此类长期运行的大型迁移的经验,您想建立一个流程,使您可以尽可能频繁地“保存游戏”。这意味着您可以构建并运行导出过程,处理将发生的所有问题(我保证,它们都会发生),然后干净地移至importprocess。与出口一样,您的导入过程也会很麻烦:因此,它也应该易于重新运行。

由于GooglePub / Sub可以在众多GoCardless系统中使用,因此很自然地可以解决此问题。甚至听起来Google的营销标语就是用来描述我们理想的,分离的过程的:

发布/订阅是异步消息传递服务,该服务将产生事件的服务与处理事件的服务解耦。

在发布/订阅中,您将消息发布到主题。每个主题可以拥有许多订阅,消费者可以从中订阅消息。用最简单的术语来说,迁移将:

配置发布/订阅订阅以保留事件(设置retain_acked_messages,请参阅:重播和清除消息),以便在导入错误的情况下重播它们

那么,压缩与这有什么关系呢?与大多数Cloud服务一样,使用/发布会收取一定的费用,这意味着我们将收取与通过该服务推送的数据成比例的费用。

在最好的情况下,如果我们第一次尝试成功导入/导出(不会,也没有发生),我们将收取2 x $ 40 x 60TB = $ 4,800的Formessage交付费用,因为这将同时适用于发布和订阅。如果我们在迁移进行期间将邮件保留2周,则需要为邮件存储收取0.5x $ 0.27 x 60,000GB = $ 8,100的费用。

现在,GoCardless并不贫穷。根据经验,您通常需要优化工程时间而不是基础架构成本。

为此,我们对迁移工具(elastic-toolbox)进行了小改动,以支持压缩发布到Pub / Sub的消息。

//发布会收到一条消息,并将其发布到Pub / Sub主题。如果启用//压缩,则将压缩消息有效负载,并使用compress = true属性标记//消息。 func(f * pubsubExportTarget)发布(ctx上下文。上下文,msg消息)错误{数据,_:= json。如果f则为元帅(味精)。选择。压缩{数据,_ = f。 compress(data)} //将消息标记为可发送,并将其//传递给Pub / Sub客户端f。入队(ctx,& pubsub。消息{数据:数据,属性:映射[字符串]字符串{" compress":fmt。Sprintf("%v",f。 ),},})返回nil}

var(exportPubsubWriteCompressionRatio = promauto。NewHistogram(prometheus。HistogramOpts {名称:" elastic_toolbox_export_pubsub_write_compression_ratio",帮助:"压缩比的分布",桶:prometheus。LinearBuckets(0.1, // 0.0-> 1.0},)exportPubsubWriteCompressDurationSeconds = promauto。NewHistogram(prometheus。HistogramOpts {名称:" elastic_toolbox_export_pubsub_write_compress_duration_seconds",帮助:"分配时间来压缩打击次数" prometheus。ExponentialBuckets(0.0625,2,8),// 0.0625-> 16s},))// compress将gzip压缩应用于传入的数据,并提高//压缩效率。 func(f * pubsubExportTarget)压缩(数据[]字节)([]字节,错误){延迟普罗米修斯。 NewTimer(prometheus.ObserverFunc(func(vfloat64){exportPubsubWriteCompressDurationSeconds.Observe(v)}))。 ObserveDuration()var缓冲区字节。缓冲区zw:= gzip。 New_Writer(& buffer)如果_,err:= zw。写(数据); err!= nil {返回nil,err}如果err:= zw。关 (); err!= nil {return nil,err}压缩:=缓冲区。字节()exportPubsubWriteCompressionRatio。观察(float64(len(压缩(len))/ float64(len(数据)))返回压缩,nil}

由于我们的节省将与压缩率(压缩/原始字节)成正比,因此我们非常关心数据的可压缩性。

使用修改后的elastic-toolbox运行三个不同索引的并发导出,我们可以使用elastic_toolbox_export_pubsub_write_compression_ratio Prometheus度量标准(请参见上面的compress方法)来构建压缩比的热图:

此热图显示所有消息最多压缩为原始大小的30%。在整个原木语料库中进行测量时,我们的平均压缩率约为12%,这意味着1GiB的原木仅为120MiB。

最明显的下一步是始终将其应用于我们的日志记录管道。假设我们将容器日志直接发送到Pub / Sub中,然后将它们从订阅中拉出到Elasticsearch中,我们可以轻松编写应用相同压缩策略的流利过滤器。

我的同事本(Ben)整理了一个很棒的仪表板来跟踪我们节省了多少钱,每月可以节省几千:

如果您在云环境中工作,那么有很多机会可以通过压缩数据来节省金钱。

除日志外,另一个GoCardless示例是一个名为draupnir的工具。该服务托管生产数据库的副本,以进行负载测试和取证分析(查询计划预测等)。 Google SSD存储的价格为每个TiB /月187美元,这意味着我们5TB Postgres的每个副本的价格为1,000美元/月。

Draupnir可能一次使用多个副本,具体取决于用例。通过启用btrfscompression透明地压缩文件系统块,我们可以节省大量资金,这使我们可以比其他方式少使用约70%的SSD容量。

如果您认为压缩只限于节省成本,那将是错的!当人们运行大型回填或建立新的数据库索引时,偶尔会遭受微故障的困扰,我们通过启用Postgres WALcompression解决了该问题(请参阅Postgres WusCompression): ,或Postgres预先写日志文档)。

中断是由数据库操作导致的大量WALchurn造成的,在将WAL写入磁盘时,副本将停滞。通过压缩WAL流,我们大大减少了IO尖峰,使副本可以毫无问题地处理该流。

压缩是一种折衷,这是我们决定将CPU换成另一种可能更昂贵或更不可用的资源的决定。分配给CPU,内存或网络带宽的值会不断变化,因此您需要根据具体情况进行计算。

这篇文章旨在介绍一种方案,在这种方案中,无论是在计算资源还是在构建时间上,压缩成本都将大大节省其成本。并非所有情况都具有相同的经济学原理,但是要用哪种方式来决定哪种方式,需要花费几分钟的时间。

我希望这个案例研究能促使人们考虑在标准,无聊的用例之外进行压缩,并有助于找到机会将其应用于自己的系统。