Elasticsearch写入速度优化

批量操作

批量写入比单条写入能够提供更好的性能。BulkProcessor可以通过三个阈值控制批量大小,分别是数据条数,数据size和时间间隔。通常来讲更大的批量会带来更好的性能,但也并不尽然,如果单个批次数据量过大,高并发的情况下会导致集群内存压力增大,官方建议是不要一次发送几十兆的数据,从我测试的结果来看,这个说法过于乐观,建议不超过5M

多线程


单线程批量发送数据无法达到最大效率。使用多线程或者多进程能够更充分地利用集群资源,同时也能减少fsync的开销,因为数据的集中发送可以减少磁盘交互

但要注意响应码TOO_MANY_REQUESTS (429),在Java Client中会返回EsRejectedExecutionException异常,这说明数据总发送速度已经超过了ES集群的处理能力,这时就需要发送端限流,比如加入时间补偿机制。或者也可以调大ES的bulk queue size,通常并不建议这么做,但某些版本的bulk queue size确实过于小,比如5.1的默认值是50,而5.5是200,所以如果你使用的是5.1版本,则可以在elasticsearch.yml加入以下配置thread_pool.bulk.queue_size: 200

增大refresh interval


默认的index.refresh_interval是1秒,使用默认配置的index会每隔一秒钟创建一个新的segment,这会给ES带来比较大的merge压力,因此如果对延迟没有特别高的要求,可以适当调大index.refresh_interval,比如10秒

离线批量导入时关闭refresh和replica


如果你需要一次性从外部系统导入数据到ES,你可以通过设置index.refresh_interval为-1来关掉refresh,设置index.number_of_replicas为0来去掉备份,这样可以大大提升数据的导入速度。refresh关掉能够极大的减少segments,也减少了merge,而去掉备份之后数据只需要写入一次。导入完成之后可以再把refresh和replica打开。值得一提的是,增加备份只是简单的数据传输,这个过程很快,但如果是向一个有备份的index写入数据,实际上在primary shard和replica上都需要进行索引,因此开销会大很多

但要注意,虽然这可以提升导入性能,但风险也提高了,因为导入过程中如果有节点挂掉,那么这个节点上的shards就不可用了,但如果是离线导入就还好,大不了重新导入就行

关闭swap


稍微了解一下操作系统就知道swap对ES简直就是灾难,性能低还是其次,最重要的是会很容易把机器给爆掉,可以使用以下方法关闭swap
1
2
3
4
5
6
7
1. 临时生效
swapoff -a
2. 重启生效
vi /etc/sysctl.conf
添加或修改vm.swappiness = 0
3. 在elasticsearch.yml中配置
bootstrap.memory_lock: true

给pagecache预留memory


官方说了,一半内存给ES,另外一半内存给lucene,也就是文件系统缓存。这很好理解,在缓存中的操作肯定比磁盘IO要快得多

使用自动分配的id


如果你在index request中指定了id,那么ES会先去检查这个id是否存在,如果不存在,就新增一条记录,如果存在,则覆盖,检查id的动作是非常耗时的,特别是数据量特别大的情况下。如果index request中不指定id,ES会自动分配一个唯一的id,省去了id检查的过程,写入速度就得到提高

但如果你希望使用es的id来保证幂等性的话就没别的选择了,只能自己指定id

使用更好的硬件


主要是磁盘,ES是重度依赖磁盘IO的软件

最好是SSD,这没什么好说的,就是快

然后是多块盘做raid0,由于数据是分散在多块盘上,所以如果一块盘坏掉可能会导致所有index不能用,但可以通过replica来规避风险

index buffer size


如果你有重度写入需求,那么最好保证在一个节点上indices.memory.index_buffer_size / shards_count > 512MB(超过这个值索引性能并不会有太明显提高)。indices.memory.index_buffer_size的默认值是10%,也就是说如果机器内存是64G,你把一半32G分配给了ES,那么buffer size为3.2G,能够支撑6.4个疯狂写入的shards