It's an oldie but a goodie. If you look on any top 10 list of software engineering Release It! will be somewhere near the top. Release It! focuses on designing robust software applications and some of the pitfalls that come with distributed complex systems.
I like this book because it takes a deep dive on those nasty, crippling production bugs that can bring down even the most well thought out system. I've seen my fair share of these types of bugs, where the actual problem is an innocuous line of code that was successfully code reviewed, unit-tested, and automated, yet it wasn't enough to prevent the debilitating application crash. This book gives good advice on how to deal with this reality and design systems that can withstand the rigors of production.
Here are my main takeaways so far.
Be cynical!: After reading this book we are taught to be cynical, pessimistic, and distrustful of our code. Danger lurks at every integration point and we must write our code expecting it to fail. Think deeply about the consequences of our failing code and design your architecture with that in mind.
Be anti-fragile: Unit tests and automation are wonderful, but we need our software to be anti-fragile. Beat up your software with in testing. Stress your system with load tests and keep your application running in test for long periods of time to try and uncover those bugs usually only found in production.
Know your design requirements: Should the checkout process be frozen because we can't talk to a third party affiliate? Probably not. Know the requirements of your system and design your failure scenarios accordingly.
Deploy early and often: The deployment process is one of the first things you should iron out. Deployments should be simple and low risk, if it is too hard then you need to rethink your design. Having an easy deployment process will make the team more likely to refactor code and make changes, which is a good thing. I have seen codebases where the team is afraid to make changes because of the difficulty and risk of deploying. This is demoralizing and a lesson that pushing our code needs to be easy.
I would recommend this book to a software engineer with any level of experience. It is a good lesson for all.
Showing posts with label Production. Show all posts
Showing posts with label Production. Show all posts
Saturday, February 20, 2016
"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached"
An email inbox full of this error message is not the best way to start a morning. Our service had been running in production smoothly for months, but software engineering is a cruel profession and bad things are bound to happen.
Luckily only one of our three production servers was down so we quickly pulled the problem child out of the load balancer rotation and began to investigate. We used perfmon to monitor the open connections on the server and were easily able to fill up the connection pool with only a few hundred concurrent requests from our tests scripts. After waiting a few minutes the connections were reclaimed and the service would become responsive again.
At this point it was rather obvious we were leaking connections, but how? This error message is an indication that connections were being taken from the pool but not released again. So the first obvious thing to do was check to make sure all our SQL clients were being properly disposed. This would be an easy fix if somehow we missed putting our SQL clients in using blocks, but after looking through the code all of our clients were in using blocks and should be disposed properly.
Connection pooling
Garbage collection
Summary
Be weary of sequences of items returned from a database that are lazily evaluated. It is best practice to materialize the sequence to an array before returning it out of the function. Also, make sure your load tests are run before EVERY release in a QA environment that exactly mirrors production. Hopefully that will save you headaches in the future.
Luckily only one of our three production servers was down so we quickly pulled the problem child out of the load balancer rotation and began to investigate. We used perfmon to monitor the open connections on the server and were easily able to fill up the connection pool with only a few hundred concurrent requests from our tests scripts. After waiting a few minutes the connections were reclaimed and the service would become responsive again.
At this point it was rather obvious we were leaking connections, but how? This error message is an indication that connections were being taken from the pool but not released again. So the first obvious thing to do was check to make sure all our SQL clients were being properly disposed. This would be an easy fix if somehow we missed putting our SQL clients in using blocks, but after looking through the code all of our clients were in using blocks and should be disposed properly.
Connection pooling
SQL connections are expensive to create. It consists of creating a socket, doing an initial handshake, authenticating the credentials, doing transaction checks, etc. Connection pooling is used as an optimization technique used to minimize the number of times a connection must be established. So, when a SQL client wants to connect to the database a connection will be created or one taken from the existing pool of connections. Once the SQL client is finished with the connection it will close the connection when Dispose() is called and the connection will go back to the connection pool. Each connection pool is associated with a distinct connection string. If the maximum number of connections in the pool has been reached and there are no more available connections in the pool then a timeout will occur when a connection is requested.
Duplicating a production problem locally is the holy grail of fixing production bugs. Once local duplication has been reached fixing the bug is usually straight-forward. In this case I was not able to reproduce this problem locally even when sending 10,000 concurrent requests at my local service. Am I hitting the same endpoint? Yes. Similar CPU/memory specs as production? Yes. After running through many of these questions we tried setting <gcServer enabled="true"/> on my local dev machine and YES!! Duplication. This server setting is used in production and was required to duplicate this problem.Garbage collection
The gcServer element specifies whether or not to use server or workstation garbage collection. Workstation garbage collection collects on the same user thread that triggered the garbage collection, so it must compete with other threads when garbage collecting. Server garbage collection dedicates threads specifically to garbage collection at high priority, so this setting will perform better on multi-core CPU machines.After being able to reproduce the problem locally it was easy to narrow down the offending code (F#).
use cmd = new GetRecords(connectionString)
let! dbResult = cmd.AsyncExecute(contactID)
let records =
dbResult
|> Seq.map Convert.convertRecords
That doesn't look so bad right? The SQL command is in a using and Dispose() should be called on it when it goes out of scope. Well, we had our suspicions about the fact that the database call was retuning a sequence and we were curious how this code would perform:
use cmd = new GetRecords(connectionString)
let! dbResult = cmd.AsyncExecute(contactID)
let records =
dbResult
|> Seq.toArray
|> Array.toSeq
|> Seq.map Convert.convertRecords
Voila! Materializing the sequence to an array before returning actually fixed the problem. But why? Sequences are not computed until it is traversed, so returning a sequence of objects retrieved from a database will keep open the database connection until the sequence is fully traversed. This is bad because we want that database connection returned back tot he pool as quick as possible so it can be used by other threads.Summary
Be weary of sequences of items returned from a database that are lazily evaluated. It is best practice to materialize the sequence to an array before returning it out of the function. Also, make sure your load tests are run before EVERY release in a QA environment that exactly mirrors production. Hopefully that will save you headaches in the future.
Subscribe to:
Posts (Atom)
