Throttling ELK log ingestion pipeline

Published:

How to process bulk logs and still have hardware resources left for other tasks

Problem statement

Recently I had to send a sizeable amount of logs into our log pipeline, a whole 23781261 log lines to be exact. Our log pipeline is the standard ELK stack, plus Filebeat, a lightweight log shipper from Elastic that forwards logs from the central log server into Logstash.

Here’s a diagram to illustrate the entire flow of logs in our system:

Nodes -> Syslog-NG -> Central logserver

Filebeat -> Logstash -> Elasticsearch

Pushing this number of logs with pretty much standard configurations of Filebeat and ELK completely saturated disk IO on the server running Elasticsearch (this node is using spinning disks so not that difficult of a feat to achieve). Additionally, Logstash would often end up failing to publish logs into Elasticsearch as a result of Elasticsearch being slow to acknowledge received events. This was no way to proceed, so I started looking at options to throttle the pipeline, or in other words, I was happy to sacrifice the total time it would take to process these logs for a reduced load on the disk drives - I wanted some of that IO for other tasks.

Throttle before the bottleneck

Naturally, throttling is best done at the point preceding the bottleneck. When done correctly, this should at least reduce the contention of the bottleneck or make it disappear completely. The bottleneck in our case was Elasticsearch, the heaviest consumer of disk IO on the node, so I started looking for ways to slow down the rate at which Logstasn sends events into ES. (Well OK, the bottleneck are the disks, but we’re stuck with what we’ve got).

A quick look at Longstash documentation yields the throttle filter plugin, which sounds exactly like what I needed. However I wasn’t able to achieve a good configuration that actually worked, for the most part it seemed that the filter wasn’t working at all. This was more than likely my misunderstanding of the documentation, but either way, I set out to look for alternative options, this approach failing the simplicity test.

Throttle at the bottleneck

ES also has some options for reducing disk IO, either at a cost of either increased RAM usage or a delay in new events appearing in search results, both of which I was happy to accept. One parameter recommended by the official documentation and countless optimise ES indexing blog posts all over the shallow and deep webs, is index.merge.scheduler.max_thread_count, which apparently should be set to 1 for systems with spinning disks. Great, thats us! Unfortunately this either made zero difference or not enough difference, we were still saturated on disk IO, so the time spent figuring out how to import a template into ES to change a dynamic setting (someone please explain to me why dymanic settings even exist?) was a waste as well.

Next I experimented with available transaction log settings, index.translog.flush_threshold_size and index.translog.durability options specifically, again both are dynamic and can’t be set in the configuration file, but these too didn’t make any noticeable difference and our disks stayed at 100% saturation.

And the same for disabling the index refresh completely via the index.refresh_interval option.

OK, time to reflect on progress - I failed to get either Logstash or ES to become at least a bit more reasonable about the rate of work they were doing and had to look further, at Filebeat.

Tool that does one job well to the rescue

Filebeat is actually one of my favourite tools from the entire Elastic product family - it seems like they (Elastic.co), finally wrote a tool that only does one thing and does it well, take logs and ship them to a receiver. This resulted in a dramatic reduction in complexity of the end product, which in turn meant that complete documentation consisted of a handful of pages that were simple to understand and didn’t need someone writing a book about it.

So I set out reading through the docs, and let me quickly say that they’re rather good quality, not exactly at the supreme excellence of the BSD Unices, but certainly consise and clear enough, and it didn’t take long at all to come accross the harvester_limit option in prospector configuration section. harvester_limit sets the number of files that Filebeat will process and ship at once. The default configuration is 0, that’s a zero, so Filebeat will ship things as fast as it can (doesn’t exactly seem like a sane default, but that’s just my opinion). Anyway, I limited the number of harvesters to 3 for a test and the results were astounding - there’s no more disk IO saturation and we can do other useful work on the node! Pause for an applause.

In the end, this option, together with a fairly aggressive use of close_inactive timeout configuration allowed me to achieve the tradeoff I was looking for - be gentle on the disk IO and don’t stress too much about processing the 23 million log lines ASAP.

The final prospects configuration is thus:

filebeat:
    prospectors:
        - input_type: log
        harvester_limit: 10
        close_inactive: 10s

Conclusion

The simplest solution is, not always, but very often the correct one. From a systems engineering point of view, complex systems are almost never worth the trouble and things are best left as simple as possible. And with that, I KISS you all good night!