Written By:Aaron Lewing
October 15, 2016
Wouldn’t it be nice if all your work as a developer was something new? You would only work on something that hasn’t been done, you’d look at your other developer friends and think, “poor Scott, he has to support a legacy codebase.” I work on a lot of legacy codebases. I might get lucky and add a new feature, and feel the exhilaration of creating something new, but for me…I wipe a lot of noses, figuratively speaking…
On the one hand, I wish I worked on more new stuff, but I also accept that I have carved out a niche as someone who can help out when an existing codebase needs to be modernized, migrated, or otherwise fixed. I hear the A-Team theme playing in my head right now, and it feels good.
An inherited Rails application may be one of those apps that is used by a smaller number of users and they like it because it just works great at doing one or two things that they need, and it is reliable. These users may be annoyed or anxious when they hear a change to this app needs to happen because old infrastructure is being decommissioned, or maybe there is a security vulnerability that must be addressed, perhaps there is a need for cost savings because of changing budgets or priorities — either way, the thing they like works and it doesn’t matter much to them that the stuff they don’t see is in need of some TLC. Sometimes all this is true, but it is an app that is used by a lot of users and now you have a lot of stakeholders who see change as something worse than inconvenient and slightly less bad than apocalyptic. Congrats Mr. Developer, you now carry the weight of a user community’s worry and paranoia.
Consider this: a Rails 3 application comprised of mostly unchanged Rails 2 code coupled with a lot of data with Solr indexing. The goal is to host the web app in a cloud environment and along the way refactor code for performance improvements to match the resources available for the customer’s application hosting budget. A few key areas I am paying attention to:
1. Minimize cost by running the application on the least expensive cloud infrastructure possible. This means don’t pay for more CPU, memory, or uptime than we absolutely need to in order to make the app available to users.
2. Minimize the time it takes to migrate/stage the application data in the cloud infrastructure. We are moving data across the network and we need to be smart about moving and indexing it once we have it reposted.
3. Keep performance as good, but really, let’s just improve it in a new environment using less powerful resources. (If you listen closely, you can hear the voices in my head maniacally laughing and inconsolably crying.)
What is slow about the app?
Ok, so here are, time to see what’s what and decide what we want to address. Do pages render in timely manner? What a simple question, but one that is laden with all kinds of gotchas if you don’t pay attention. I will focus on a particular type of performance improvement in this example, and it is really simple…ActiveRecord. I came across this article today in my Feedly Sunday catch up: Tips for Writing Fast Rails: Part 1
Just this past week, I had the same change to an Active Record model to improve some response times. If you think a Select is as fast as a Where, hold on to your hat! Remember how I mentioned we needed to get the app onto the minimal hardware we could for the purposes of cost savings? Well, the infrastructure I have available is less than half the memory as the original platform, so I care a lot about not loading too much stuff into memory by slurping up data I don’t really need in a Select when I can be a little picky and use a Where instead. I get a little performance boost by not passing in a block that is processed on the greedy Select, I can just pull in the data I really need by calling Where and processing the data once it is memory rather than pull in more than I need and also processing it while loading it into memory.
Let me take a moment to pay homage to the elimination of N+1 queries. Every developer knows N+1 queries are costly and finding them and killing them with fire is a noble way to earn your pay. If you are not familiar with N+1, here you go:
Your data can have Parent-Child relationships, meaning that a database table may contain names of authors, and another table may contain a list of posts and the author is included in the posts data. You don’t want your code to make one query for the author name, then five more queries to build the list of posts the author is listed in from the posts table – aka Lazy Loading. Each query takes time for the database to respond and your users will get tired of waiting on a page to load. If you do nothing, Active Record will do lazy loading. We can fix this by engaging in Eager Loading, so when we write a Find or Where query, we also add an Include so Rails knows to get your posts for the author in the same query. The Bullet Gem is a nice tool to use for finding N+1 queries.
No customer is going to say “hey why don’t you just refactor the code so it will be easier to maintain.” If we can show performance improvements through refactoring, then you get paid for doing a thing they like, and maintainability is the lovely parting gift/doggie bag/lagniappe/party favor for being a good developer and making good decisions.