By Tyler Jefford
On January 4th, 2023
In October 2022 wanting to rapidly produce some new reports I felt constrained by the code I had mindlessly wrote over the last couple of years. So with a glass of whiskey in hand, I spent a few hours digging deep into my code and began to refactor large chunks of the application.
I had recently installed the performance monitoring from OhDear and having just switched to a fresh server on DigitalOcean, I was seeing an average of 700ms to load the site on a cached data layer. This was crazy since its a relatively simple website that pulls data from its own MySQL database, it can probably be faster.
After a few hours, I had improved the performance more than 90% and the site was now loading in about 100ms. The page weight from 8k+ models and thousands of queries to just dozens. The site is now blazing fast due to a few refactors and relying a lot more on the framework for things that its good at, like relationships.
I was introduced to the Laravel Debugbar by Barry in 2013 building some applications for a digital marketing agency and needing to see data on a page as I was learning Laravel. Over time this tool has become such a powerful item in the development of many of the sites I work on.
Im not going to get into the full gamut of things you can do with this tool, there are many many blogs that have gone in depth here. But I will talk about the tabs I kept checking for the performance improvements.
Using the Queries tab will show what your queries are in SQL. You can also click on a query to see a backtrace of where the query was executed in a file. It also tracks the time it took to execute the full load of queries. In the screenshot you can see the 8 queries loaded in 152ms from a cold load.
Models is a great tab to check what models you are loading underneath the view. Before the refactor I would see thousands of Result, Hospitals and Vaccine models being loaded leading to bloated page weight and load times.
On the right side of the bar, you can see the memory usage, here it’s 2MB - before the refactor memory usage was up to 49MB at times. Request Duration was a good metric to watch as I reduced queries and models the request time dropped from 17 seconds to 100 milliseconds.
This is where most of the performance gains came from. As the months went on, I kept stuffing more and more into the primary controller that fed all the views on the site. Adding a banner for delayed results required me to pass that data on each view from the controller. Without eager loading, I was making multiple trips to the database to get data and transform it into the contract for the screen.
Deciding to rip up the WebController and only let it handle the things a controller should handle made it really simple. Now, I just want to grab statements (formally reports, another overloaded model) and split them into this week and last week sets. Thats it.
Data Transforms and relational data is handled elsewhere. This simplifies the controller and only has it handle the data needed to pass to the views. Here on the index, it reduced the queries made from nearly 3k to just 15 and the request duration from 17 seconds to 100 milliseconds. There were almost 8k models being loaded on this one page - that was reduced significantly to just 82.
Before the refactor, I had created a massive model called Report that combined all the data models I used for the site data including daily results, hospital data, vaccine data, variant data, demographic data.
I had many methods in that Report model that had even more arguments for each. A strategy I followed during this refactor was to keep it simple and fall back on more framework methods where possible. Here is a clip from the Statement Model where I wrote a simple
from method where you give it s date and it returns a transformed statement to you in the format expected for the web views.
I also wrote some clear documentation for later Tyler, even though the code is pretty clean and readable now - When you spend months away from the code, this is a much faster way to onboard.
Believe it or not, a lot of the transforming of data was repeated in various methods within the WebController. I spent some time months ago to write some transformers for when I import the data from the IDPH API to map to my database but still had a lot of repeated code to transform for web views.
I spent a considerable amount of time cleaning up this code and taking care to not repeat too much of the work, building some helper functions in the Transformer for percent calculation and color selector indicators. This screenshot shows the primary method called after initializing a new StatementsTransformer. The return will be the array full of the data for web views in the correct format, so I didn’t have to change any contracts on the front end after this refactor.
After the rewrite of the WebController, clean up of the Transformer and the new streamlined Statement Model, cleaning up the relationships between the main results and the secondary data used on every page like hospital and vaccine data helped speed up the page time by allowing me to eager load the data using built in Laravel functions.
Now with the relationship solidified eager loading is super simple.
Spend a little time to refactor your code. Don’t be too tied to concepts or architectures and take small steps to break apart the application. Try to refactor a chunk til it works again, then work the next chunk. Using tools like Debugbar can help identify areas you can focus on, like too many queries or models being loaded. I think refactoring has been more fun than actually building the site itself.