Thursday 5 January 2012

Performance Fundamentals

There is no magic bullet or one-size-fits all architecture for creating a high-performance enterprise application, but there are some pretty basic fundamentals that will certainly assist you along the way.

General Performance Fundamentals:

The following points are focused around avoiding what is usually the slowest part of the deployment topology - the network infrastructure.
  • Minimise network traffic - every machine-to-machine "hop" that your code traverses while fulfilling a request (eg. browser to web-server to app-server to database) can add enormously to the time to complete an operation. Aside from the physical network hop, there are often other overheads involved like serialisation and compression. All of this time adds up and can affect the responsiveness of your application making it feel somewhat sluggish (even if every operation executed on each tier was lightning fast).
  • Avoid SQL calls within loops - if we're trying to minimize network traffic, we sure as heck don't want to add extra calls to the DB for every record being processed in a collection. It's oh so easy to fall into this trap by accident. Wherever possible, get all the data you need in one hit from the database rather than issuing separate calls to get an additional related value or description. This same advice holds for other remote method calls like web services.
  • Reduce unnecessary serialisation - the point of serialisation is (usually) to copy data or state from point A to point B so that some action can occur. However, there's no point lugging a bunch of data over to point B if the processing there isn't going to look at it.

Database Performance Fundamentals:
  • Use bind variables - A bind variable is a place-holder variable in a SQL statement that must be replaced with a valid value before the statement can successfully execute.  Using bind variables in your sql statements can improve performance as the database doesn't have to parse the execution plan for every sql statement that comes along with a different value in the where clause.
  • Use appropriate indexes - A database index is a data structure that improves the speed of data retrieval operations on a database table at the cost of slower writes and increased storage space. You could write a book about database index design. Knowing how and where to apply indexes can sometimes be more of an art than a science and is very particular to the specific application and environment you are working with. My advice is to read-up as much as possible to understand what is going on under the covers.
  • Be pragmatic about schema normalisation - Much has been written about database design strategies, and there is no one-size-fits-all "best" approach for all situations. However, in my experience with business applications, some form of redundancy is preferred over dogmatic adherence to a particular higher-level normalisation scheme. The reason for this is that the number and expense of joins on data retrieval (for complex systems) can easily outweigh the cost of redundant data storage. The level of redundancy you choose will no doubt be unique for your situation.

.Net Rules of Thumb
  • Use database connection pooling - Establishing and cleaning up connections to the database can be very time-consuming. Instead of creating new connections as needed, it is better to use an existing pool of connections that are held open and available at all times. Most database flavours (eg. Oracle and SqlServer) support connection pooling via their respective .Net connection libraries.
  • DirectCast outperforms CType -Especially useful to know if you need to serialise to/from XML. Note that this only applies to VB.Net.
  • Compiled Lambdas outperform Activator.CreateInstance - my experience has largely encountered problems with Activator.CreateInstance in framework code that I'm unable to (easily) modify, but it's still useful to note as the benefits can be huge.
  • Use StringBuilder for concatenating strings - a well known tip that is still worth remembering. StringBuilder almost always outperforms a bog-standard string object when concatenating.
Summary
Diagnosing and improving performance problems can be extremely difficult. Hopefully the above tips and fundamentals will help, but inevitably you'll need to go deeper. There are a bunch of performance profilers available that can assist with this. I also strongly recommend building in some level of telemetry directly into the application (that can be turned on/off when needed). It's easy to go overboard, so start with the best bang-for-buck areas and go from there. Alternatively, rather than build it yourself, use a service such as New Relic or Google Analytics to get info quickly. These initiatives will allow you to gather performance data from production systems and avoid "works on my machine"-esque scenarios.

So, if in doubt, MEASURE IT! Building a culture whereby developers know and understand the performance metrics of the code pays dividends, and if the performance metrics can be automated, so much the better.

No comments:

Post a Comment