Apache Cassandra性能调优-混合工作负载压缩

 

这是我们关于使用Apache Cassandra进行性能调整的系列文章中的第三篇。在我们的第一篇文章中,我们讨论了如何使用火焰图直观地诊断性能问题。在第二篇文章中,我们讨论了JVM调优,以及不同的JVM设置如何影响不同的工作负载。

在本文中,我们将深入探讨通常被忽略的表级设置:压缩。可以在创建或更改表时指定压缩选项,如果未指定,则默认启用。当处理写入繁重的工作负载时,默认值很棒,但是对于读取繁重的混合工作负载可能会成为问题。

在进行优化之前,让我们退后一步来了解Cassandra中的压缩基础。建立知识基础后,我们将了解如何将其应用于现实世界的工作负载。

怎么运行的

当我们在Cassandra中创建表时,除了字段外,我们还可以指定各种表选项。除了诸如将TWCS用于我们的压缩策略,指定gc宽限秒数和缓存选项之类的选项外,我们还可以告诉Cassandra我们希望它如何压缩数据。如果未指定压缩选项,则将使用LZ4Compressor,以其出色的性能和压缩率而闻名。除了算法之外,我们还可以指定chunk_length_in_kb,这是写入数据之前将数据写入其中的未压缩缓冲区的大小。这是一个使用LZ4Compressor的表的示例,该表的块长为64KB:

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">create table sensor_data ( 
    id text primary key, 
    data text) 
WITH compression = {'sstable_compression': 'LZ4Compressor', 
                    'chunk_length_kb': 64};
</code></span></span>

我们可以通过检查tablestats以下内容来检查压缩在表级别的工作情况:

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">$ bin/nodetool tablestats tlp_stress

Keyspace : tlp_stress
	Read Count: 89766
	Read Latency: 0.18743983245326737 ms
	Write Count: 8880859
	Write Latency: 0.009023213069816781 ms
	Pending Flushes: 0
		Table: sensor_data
		SSTable count: 5
		Old SSTable count: 0
		Space used (live): 864131294
		Space used (total): 864131294
		Off heap memory used (total): 2472433
		SSTable Compression Ratio: 0.8964684393508305
		Compression metadata off heap memory used: 140544
</code></span></span>

SSTable Compression Ratio上面的行告诉我们有效的压缩方式。压缩率的计算公式如下:

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">compressionRatio = (double) compressed/uncompressed;
</code></span></span>

表示数字越小,压缩效果越好。在上面的示例中,我们的压缩数据几乎占据了原始数据的90%,并不是特别好。

数据如何写入

我发现深入研究代码库,进行概要分析以及与调试器一起使用是学习软件工作原理的最有效方法。

当数据被写入SSTables或从SSTables读取数据时,我们不处理方便的类型化对象,而是处理字节流。我们的压缩数据写在CompressedSequentialWriter该类中,该类可以扩展BufferedDataOutputStreamPlus。作者使用一个临时缓冲区。当数据写到磁盘时,缓冲区将被压缩,有关它的一些元数据将记录到CompressionInfo文件中。如果缓冲区中的数据多于可用空间,则将缓冲区写入缓冲区并刷新,然后重新开始重新写入缓冲区(并可能再次刷新)。您可以在中看到此内容org/apache/cassandra/io/util/BufferedDataOutputStreamPlus.java

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">@Override
public void write(byte[] b, int off, int len) throws IOException
{
    if (b == null)
        throw new NullPointerException();

    // avoid int overflow
    if (off < 0 || off > b.length || len < 0
        || len > b.length - off)
        throw new IndexOutOfBoundsException();

    if (len == 0)
        return;

    int copied = 0;
    while (copied < len)
    {
        if (buffer.hasRemaining())
        {
            int toCopy = Math.min(len - copied, buffer.remaining());
            buffer.put(b, off + copied, toCopy);
            copied += toCopy;
        }
        else
        {
            doFlush(len - copied);
        }
    }
}
</code></span></span>

此缓冲区的大小由确定chunk_length_in_kb

如何读取数据

Cassandra中的读取路径(或多或少)与写入路径相反。我们从SSTables中取出大块,对其进行解压缩,然后将其返回给客户端。完整的路径要复杂一些- 我们要经历一个aa ChunkCache(由咖啡因管理),但这超出了本文的范围。

在读取路径期间,必须读取并解压缩整个块。我们无法选择性地仅读取所需的字节。这样做的影响是,如果我们使用4K块,则仅读取磁盘4K就可以摆脱困境。如果使用256KB的块,则必须读取整个256K。这对于少数几个请求可能很好,但是当试图最大化吞吐量时,我们需要考虑当每秒有数千个请求时发生的情况。如果我们必须每秒读取256KB磁盘以处理一万个请求,那么我们将需要每秒读取2.5GB磁盘,而无论使用什么硬件,这都是一个问题。

那页面缓存呢?

Linux将自动利用应用程序未使用的任何RAM来将最近访问的文件系统块保留在内存中。通过使用该free工具,我们可以看到正在使用多少页面缓存:

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">$ free -mhw
              total        used        free      shared     buffers       cache   available
Mem:            62G        823M         48G        1.7M        261M         13G         61G
Swap:          8.0G          0B        8.0G
</code></span></span>

如果您有适合内存使用的工作数据集,那么页面缓存可以带来巨大的好处。对于较小的数据集,这非常有用,但是Cassandra旨在解决大数据问题。通常,这意味着拥有比可用RAM更多的数据。如果我们在每个节点上的工作数据集为2 TB,而我们只有20-30 GB的可用RAM,则很有可能我们几乎不会在缓存外处理任何请求。kes。

最终,我们需要确保使用的块长度可以使I / O最小化。较大的块可以更好地压缩,从而为我们提供较小的磁盘空间,但是最终需要更多的硬件,因此对于某些工作负载而言,节省空间变得毫无意义。没有完美的设置可应用于所有工作负载。通常,您执行的读取次数最多,块大小较小。即使这也不是统一适用的。较大的请求将命中更多的块,并将受益于更大的块大小。

基准测试

好吧-足够的细节!我们将运行一个简单的基准测试,以测试Cassandra在具有简单键值数据模型的混合读写记录中的性能。我们将使用压力工具tlp-stress(提交40cb2d28fde)进行此操作。我们将在以后的文章中详细介绍这个压力工具-现在我们需要讨论的是它包含了一个现成的关键价值工作负载,我们可以在这里利用。

对于此测试,我按照cassandra.apache.org上的说明在运行Ubuntu 16.04的AWS c5d.4xlarge实例上安装了Apache Cassandra 3.11.3,并使用来更新了所有系统软件包apt-get upgrade。我在这里只使用一个节点,以隔离压缩设置,而不从运行整个群集的网络开销中引入噪音。

临时NVMe磁盘正在使用XFS并将其安装在/var/lib/cassandra。我使用设置了预读,blockdev --setra 0 /dev/nvme1n1因此我们可以看到压缩对磁盘请求的影响,而不是将其隐藏在页面缓存中。

对于每个工作负载,我将以下命令放入外壳脚本中,并从单独的c5d.4xlarge实例运行tlp-stress(将块大小作为第一个参数传递):

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">$ bin/tlp-stress run KeyValue -i 10B -p 10M --populate -t 4 \
  --replication "{'class':'SimpleStrategy', 'replication_factor':1}" \
  --field.keyvalue.value='book(100,200)' -r .5  \
  --compression "{'chunk_length_in_kb': '$1', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}" \
  --host 172.31.42.30
</code></span></span>

这将在1000万个分区(-p 10M)上运行键值工作负载,预先填充数据(--populate),其中50%的读取(-r .5),从压力工具(--field.keyvalue.value='book(100,200)')中包含的一本书中选出100-200个单词。我们可以使用指定压缩策略--compression

在测试中,我使用了经过稍微修改的Cassandra配置文件,以通过增加总堆(12GB)和新一代(6GB)来减少GC暂停的影响。我花了很少的时间,因为没有必要对其进行完美的优化。我还将压缩吞吐量设置为160

在测试中,我使用Swiss Java Knife(sjk-plus)以及dstat对磁盘/网络/ cpu的使用情况监控了JVM的分配率。

默认64KB块大小

第一个测试使用默认的64KB块长度。我开始发出压力命令,然后走开和我的狗玩了一段时间。当我回来时,我遇到了大约3500万个请求:

 

您可以在上面的屏幕截图中看到我们的5分钟速率大约是22K次写入/秒和22K次读取/秒。查看这时dstat的输出,可以看到我们正在每秒读取500到600MB之间的数据:

DStat 64KB

内存分配有所波动,但徘徊在1GB / s左右:

sjk 4kb

不是世界上最惊人的结果。在磁盘读取中,某些吞吐量可以归因于压缩,而在现实世界中我们总是必须面对这种压缩。上限为160MB / s,剩下约400MB / s的读取速度。考虑到我们在网络上仅发送25MB,这很多。这意味着我们要做的磁盘I / O超过网络I / O的15倍。在此工作负载中,我们的磁盘空间很大。

4KB块大小

让我们看看4KB块大小是否更好。在测试之前,我关闭了Cassandra,清除了数据目录,然后开始备份。我使用上面的shell脚本在上面进行了相同的压力测试,将4作为块大小传递。我再次和我的狗玩了一段时间,然后在与之前的测试大约相同的时间回来。

从压力输出来看,显而易见的是,有了很大的改进:

 

在指标库报告的几乎每个指标中,具有4KB的测试都优于64KB的测试。我们的吞吐量更好(在1分钟内速率为62K ops /秒与44K ops /秒),而我们的p99读取更好(13ms vs 24ms)。

如果我们在每个请求上执行的I / O减少,这将如何影响我们的磁盘和网络I / O总数?

dstat 4kb

如您在上面看到的,有了很大的改进。磁盘I / O减少了对磁盘的较小(但更多)的请求,而响应更多的请求,网络I / O显着提高。

 

最初看到增加的堆分配率(因为我们正在将WAY的数据读入内存的数量减少)而感到意外,但这仅仅是执行更多请求的结果。为了满足请求而创建了许多对象。远远超过从磁盘读取数据而创建的数量。请求越多,分配就越高。我们要确保在进行JVM调整时这些对象不会进入OldGen

堆外内存使用情况

最后要考虑的是堆外内存使用情况。在每个压缩的SSTable旁边是压缩元数据。压缩文件的名称类似于na-9-big-CompressionInfo.db。压缩元数据存储在Cassandra堆之外的内存中。堆使用量的大小与所用块的数量成正比。更多的块=使用更多的空间。当使用较小的块大小时,将使用更多的块,因此,将使用更多的堆外内存来存储每个块的元数据。了解这种权衡很重要。使用4KB块的表将使用的内存是使用64KB块的表的16倍。

在我上面使用的示例中,内存使用情况可以看到如下:

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">Compression metadata off heap memory used: 140544 
</code></span></span>

更改现有表

既然您已经看到较小的块大小如何使读取繁重的混合工作负载受益,现在该尝试一下。如果您有要更改其压缩设置的表,则可以在cqlsh shell上执行以下操作:

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">cqlsh:tlp_stress> alter table keyvalue with compression = {'sstable_compression': 'LZ4Compressor', 'chunk_length_kb': 4};
</code></span></span>

应用此更改后写入的新SSTables将使用此设置,但现有的SSTables不会自动重写。因此,应用此设置后,您不应指望立即出现性能差异。如果要立即重写每个SSTable,则需要执行以下操作:

<span style="color:#2e3a33"><span style="color:#2e3a33"><code class="language-plaintext">nodetool upgradesstables -a tlp_stress keyvalue
</code></span></span>

结论

上面是一个测试,展示了调整压缩设置如何显着影响Cassandra性能。在读取繁重的工作负载或混合工作负载时使用开箱即用的压缩设置几乎可以肯定会对磁盘造成不必要的负担,同时又会损害读取性能。我强烈建议您花一些时间了解您的工作负载,并分析系统资源以了解瓶颈所在,因为没有绝对正确的设置可用于每个工作负载。

还要记住在内存和块大小之间进行权衡。当在内存受限的环境中工作时,似乎很想在任何地方使用4KB块,但重要的是要了解它将使用更多的内存。在这些情况下,从读取最多的较小表开始是个好主意。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值