Prioritizing Application Performance Throughout The Project LifecycleJanuary 22, 2020
Modern web applications are sophisticated programs that facilitate meaningful, intuitive interactions with data and provide a satisfying experience for their users. It’s critical that these applications remain performant regardless of their size or complexity. A poorly performing application can cause a wide range of unwanted issues, such as low engagement and retention rates, end user dissatisfaction, system unreliability, and application failure. Ensuring proper performance across all common use cases is vital to an application’s success.
Establishing the ideal level of performance for an application requires a thoughtful, balanced approach. Users will always have expectations for how well an application should perform, based on their past experiences. Meeting or exceeding those expectations will always contribute to a positive user experience. But falling short of performance expectations, even by a modest amount, can lead to a poor user experience and have far reaching consequences.
It is important to understand, however, that although improved performance is always a desirable feature, optimizing an application for performance beyond a certain point can introduce additional complexities into a project. Highly optimized code can increase costs and extend project timelines. Over time, it can become more difficult to maintain, and more resistant to future extension or versioning. Resisting over-optimization, or premature optimization, is key to establishing the proper level of performance for an application.
To ensure that an application is performing optimally, it’s imperative that performance considerations are addressed throughout the entire lifecycle of a project. Far too often, performance considerations are treated as an afterthought, and are only addressed in detail when a deployed application begins to suffer from data bottlenecks, scaling issues, or rapidly expanding error logs. Our solution is to treat performance as a priority consideration throughout the entire project lifecycle. This approach is centered around three key phases, discovery/conceptualization, development, and hosting/support.
During this phase we strive to gain a comprehensive understanding of all of the features and functionality that will be included in an application. By considering performance implications throughout this process, we can establish the baseline requirement of acceptable application performance. For most applications, there may be certain features that require a higher level of performance than others. Understanding a feature’s role within an application is the key to establishing an acceptable level of performance.
For example, while 5 seconds may be an acceptable amount of time to export data to a csv file, this would be an unacceptable amount of time for a web page to load. On a smaller scale, by analyzing the complexity of a query we can estimate an appropriate amount of time it should take to complete. Generally, more complex queries will take longer to complete, but by optimizing the query and the data it retrieves, we can greatly reduce it’s processing time. Identifying these critical areas allows us to devise a solution and implement a custom approach that is effective, efficient, and fulfills the needs of the client and their users.
During discovery, we also begin to model data and visualize relationships and associations between these models. Accurate data modeling unlocks the ability to work with large, complex datasets in a performant manner. By visualizing the relationships between data, we can begin to lay the groundwork for highly efficient queries, and prevent performance degrading antipatterns such as N + 1.
For a better understanding of how N + 1 can send query times skyrocketing, let’s look at a small example, where we retrieve all the student records from a single school. Without optimizing our query for performance, we might make one request for the school record, and then one additional query for each student at that school. This could result in hundreds or thousands of queries, each adding milliseconds onto the total query time. Conversely, if we associate our data properly, we could retrieve the same amount of data in as little as two queries, one for the school and another for all the students belonging to it. This reduces processing time by hundreds, if not thousands, of milliseconds.
Finally, we must also consider the impact an application’s user experience/user interface (UX/UI) will have. Performance is irrelevant if an application is difficult to interact with or navigate through. Intuitive and practical user interfaces will compliment a highly performant system and lead to a much higher degree of user satisfaction. Aesthetics aside, a well designed user interface will also incorporate performance enhancing techniques through the use of well constructed background requests and efficient client-side data management. UX/UI considerations can have a huge impact on both performance, and the perception of performance, and are a vital component of the conceptualization phase of a project.
During the development phase, we take an iterative approach to ensure that we are implementing features in a manner that aligns with our previously determined performance requirements. Thorough testing of features as they are being constructed allows us to gain insight into how features are performing, both as an independent entity and as they are integrated into a larger system. Decisions made during the conceptualization process provide us with a clear blueprint for implementing application features in an organized manner.
To achieve our performance goals, it’s crucial that we properly manage the request/response cycles of each feature. This starts by regulating what data is transferred. Unused or unneeded data is eliminated to reduce the size of database queries, which helps to improve loading times, decrease memory usage, and leads to a shorter request/response cycle. Utilizing eager loading techniques help us to eliminate N + 1 issues. Understanding when to leverage language specific tools, such as optimized sorting techniques, or database specific features, such as indexing, is also vital. By constructing an application in such a way that we responsibly manage data, utilize the tools available to us by our system components, and adhere to programming best practices, we can ensure that data performance remains a priority throughout this phase.
Another method we can use to improve application performance is through the use of AJAX. AJAX allows us to perform precise interactions with a web server, without the disruption of a full page reload. This allows us to perform client-side interactions in a crisp, clean, and highly performant manner. In addition to the real performance advantages AJAX provides, there is also a perceived performance gain, often experienced by users who can see real time changes in data without the usual overhead of a full page reload.
Similarly, we can delegate resource intensive tasks on the backend to a background job. This technique allows us to offload tasks, such as sending emails or exporting data, to a separate worker thread that runs independent of the main system. This allows for complex tasks to be executed without affecting the performance of the main system. Similar to AJAX, there is a gain in both real and perceived performance here, as users can continue to interact with an application while a task runs in the background. Removing the element of waiting while data is processing greatly enhances the user experience and allows a time-intensive and otherwise slow task to not be regarded as such.
Starting in development, and continuing into the deployment and hosting phase of a project, we gain access to a variety of resources that allow us to monitor an application’s overall performance. By analyzing metrics such as memory usage, throughput, and response times, we can gain insight into how much traffic is passing through the system, how fast requests are being handled, and how many resources are being consumed throughout this process. These tools allow us to identify performance trends, analyze the effectiveness of our implementations, identify ineffective or inefficient allocations of resources, and diagnose errors by providing us with detailed information about issues as they arise in real time.
In addition to monitoring the application itself, it is also important that we manage the performance of the infrastructure that is hosting the application. Periods of extremely high traffic, or excessive strain placed on the system by heavy usage of some features, can cause an overload of the server configuration that supports an application. During these periods, a rapid deterioration in performance is to be expected. In order to combat this, we can utilize scaling tools available to us through our hosting platforms.
At LLT, we take full advantage of the scaling resources available to us through Heroku, a cloud based platform used for hosting and maintaining applications. Depending on an application’s specific needs, we can quickly scale system infrastructure both horizontally and vertically. Horizontal scaling allows us to create multiple, identical, replications of our system’s configuration. Using horizontal scaling, we can quickly respond to fluctuations in traffic and throughput. Vertical scaling, which increases CPU and memory thresholds, allows us to navigate periods where an application simply needs to be more powerful. Rapid scaling, both horizontal and vertical, allows us to sustain our performance goals with minimal to no disruption in user experience.
Achieving an optimal level of performance for an application requires a thoughtful balance of several considerations. This approach necessitates that performance concerns be addressed throughout the entirety of the project lifecycle. Working closely with clients, we can gain valuable insights into the exact performance needs of an application, down to a feature level. The result is a finely tuned application that is performant, robust, maintainable, and successful.