Tuesday, 18 June 2019

Troubleshooting AWS lambda too many open files

Some time ago - in the not so distant past - I was offered the opportunity to assist a colleague with troubleshooting a system that was facing some performance issues.

Problem 1:  Dedicated caching server running out of memory and constantly swapping

This cache had been put in place to reduce the need to call out to other services for data that might be needed several times in a given time window.

The developers who had set up this system had moved on to other teams or companies, so we didn't have much context to go by as to whether there was any pattern to the distribution of requests for the data that could have hinted at a sensible expiry policy.

Solution 1:  Get a bigger boat cache

Left to rely on the hit rate metric for the cache to tell us whether or not it is actually fit for purpose, we decided to replace the caching server with the next larger instance size - and also to specify the recommended parameters for reserved memory which seemed to have been missed in the existing setup.

With the new cache in place swapping did not return as an issue.  However, errors were still showing up in the logs for the lambda that was utilising the cache. Experience says that we should never leave a job half-done.

Problem 2: Lambda running out of available file handles / sockets

The logs were showing a range of different types of errors that may or may not have been related:
  • Unable to perform a DNS lookup
  • Unable to load a class
  • Unable to connect to the cache
One of the error messages provided a vital hint: "too many open files".

This was my first experience of properly getting my hands dirty with AWS lambdas, so it was interesting to learn all about the way that it can keep an instance of the lambda around for much longer than the duration of the execution of a single call.

Our lambda was being called several times per minute which was enough to keep some instances of it around for long enough to run out of resources.

A bit of reading of the documentation revealed that we should be able to utilise over a thousand file handles / sockets in our lambda - which should be plenty.

Diagnosing the issue: Measuring open file handles at runtime

I had a theory at this point that the "Unable to load a class" issue may have been triggering the other issues, so I introduced some diagnostic logging that would output the total number of open file descriptors on each invocation of the lambda. Once this was in place it appeared that around three more files / sockets were open after each invocation - so the leak had nothing to do with the occasional class loading failure.

The next revelation came when I noticed that the lambda would load in a config file for each invocation, but was not showing any sign of an attempt to close that file afterwards.

Tidying up just that particular resource handling didn't make the problem go away.

Solution 2: Properly clean up resources

After some more digging around I realised that the caching client had its own connection pool that is set up as part of each call, but not shut down afterwards. This was not trivial to change, as the setup for the caching client was nested within a few layers of Builder classes, but some refactoring enabled us to hook this into the lifecycle of the lambda invocation.

Monitoring of the logs showed that the open files count was now stable.


Conclusion

Lambdas are just like any other code we write, we need to pay attention to the lifecycle and ensure that resources are cleaned up when they have finished being used.

Next steps

Cache entry expiry and cache right-sizing

At the time of writing this post the new, larger cache is growing steadily and showing no sign of steadying off. So, there is no reason to expect that the swapping issue may not return.

The current cache logic is lacking a default expiration for entries that become stale.  For some of the entries involved there is no reason to expect that the values being encountered will be re-used very often (some might not be encountered more than once a week / month / name your favourite time unit).

Cache connection pooling optimisation
If AWS Lambdas supported shutdown hooks or some other mechanism for detecting when the instance is being abandoned then we could update the lambda to set up the cache connection pool at initialisation and the corresponding closing on shutdown - not today.

Thursday, 21 March 2019

The names have changed, the mistakes are the same

I decided not to stay on the monthly IT department catch up call this afternoon.  Luckily I had plenty to keep me occupied a few minutes later.

> "Hey, does anyone know why the site's down?"
< "It's not down", "Ooh, it's slow, what's that about"
....
Long story short, somebody updated a security group config so one of the apps couldn't reach its cache.  A few years ago it would have been a firewall, or iptables, or ipchains now we're in the cloud.

Before we could get around to identifying and fixing that we had a bigger problem - users started to see server errors instead of just slow responses and timeouts.

Second long story short - there was an expired SSL certificate on another back end service.

This is 2019, and we're still making the same types of mistake as I used to see in 1990s.

Wednesday, 23 May 2018

Am I an accidental software architect?

I have an opinion about the role of software architects, they should actively contribute code that will go into production for the systems that they provide oversight for.

Recently I have found myself in the peculiar situation of being so involved in technical evaluations, troubleshooting of production issues, code reviews and meetings that I haven't actually contributed any of my own code.

As a "developer" I am not particularly comfortable with this situation.

Saturday, 23 July 2016

Referendum petition data examined

You may have noticed that on 23rd June 2016 the UK held a referendum about whether to remain as a member of the European Union.

When the result didn't go quite the way they wanted, some people participated in an online petition with the goal of having parliament consider re-running the referendum to try to obtain a more significant resulting margin.

Link to petition

The apparent popularity of the petition attracted attention on social and conventional media, so I decided to take a look at the data.

There isn't a lot of information that is exposed, basically a breakdown of numbers by country of residence and a breakdown of numbers by constituency.

By loading the data into an application and sorting it and printing it out I noticed something conspicuous - Vatican City showing up as one of the most popular countries of residence.

Hold on, isn't that one of the least populated countries in the world?

Sure enough, the numbers involved exceeded the public stats of the total population - that literally didn't add up.

Myself and a few others flagged this up on Twitter, so the relevant parties did some checks and figured out how to filter out dodgy data and block later attempts at automated contributions.

My code is available on github.  By looking into the Git history you can see for yourself how the data for country counts changed over time.

Petition Analysis github repo

The code for the petition site itself (not by me) is also on GitHub:

https://github.com/alphagov/e-petitions

Tuesday, 19 January 2016

A post about a post - Java performance for obtaining an array from a Collection

Avid readers may recall that a while back I posted something about creating StringBuilders with the correct initial size to avoid wasted memory allocation and subsequent garbage collection.

Today I came across a blog post that covered a similar topic - creating an array from a Collection - but analysing why advice that had become conventional wisdom may no longer be valid.

It goes a little deeper than my day to day work requires me to understand, but I think it is well worth a read.

http://shipilev.net/blog/2016/arrays-wisdom-ancients/

I'm now contemplating finally getting around to using JMH to evaluate whether my approach to StringBuilders is sensible or programming by superstition.

Monday, 3 August 2015

Continuous improvement

Every few years I like to take a couple of weeks out of the working world to learn about techniques and technologies that are approaching the mainstream.

When I first moved to London back in 2008 I had to learn all about Spring and Hibernate as the local job market was mainly fixated on those technologies.

After the recession cooled down a bit I found my way into a development role based on Hybris - an e-commerce system held together by Spring.

Fast forward to 2012, before joining Springer I dabbled in accessing some youtube APIs to see what would be involved in establishing a degrees of separation relationship between music videos via their related videos.  That was a bit of fun and gave me some insight into some simple performance optimisation options in distributed systems:
  • divide the work up and allocate it to a pool of workers
  • have the workers share a small piece of information to prevent duplication of effort (checking videos that had already been visited).
At around the same time I took an interest in nosql databases.  After attending the inaugural London Data Bar meetup group, I won a ticket to a two day hands on NoSQL databases conference which gave me a chance to dabble with neo4j and MongoDB.

For 2015 I'm taking a dabble in open source products, seeing how they work, contributing some code and improving some documentation along the way.