Performance patterns are blueprints to solve recurring performance problems. The pattern is a general concept for solving a particular situation. Don't confuse a pattern with an algorithm. The pattern is a high-level description of a solution, and the algorithm is a set of actions that can achieve a goal.
What are the elements of a performance pattern?
Intent - summary of the problem and the solution.
Motivation - explanation of the problem and benefits of the solution.
Structure - elements of this pattern.
Example - how you can apply this pattern.
What are the benefits of performance patterns?
Even if you have worked as a performance engineer for many years, you are unaware that you already use such patterns. The main idea behind performance patterns is to eliminate guesswork and introduce an easy-to-understand ruleset for performance engineering.
Performance patterns are a toolkit of tested solutions to common performance problems. If you encounter specific issues, you could look at these patterns and find guidance on how to solve them.
Performance patterns simplify communication and act as a common language. You can say, "Monitoring first," and everyone will understand the suggestion.
We distinguish between creational, structural, and behavioral performance patterns. In this post, I will outline the top 3 creational practices.
Creational patterns describe ways to implement applications with performance in mind. The awareness of these practices and proper implementation is a crucial step towards fast and reliable applications.
Based on my experience, most of our performance problems are related to design and implementation, which falls in creational patterns. The section below describes the N+1 query and the chatty service performance patterns.
Creational Pattern / N+1 query problem
Intend: Spamming the database with small queries instead of using one or two complex ones.
Motivation: N+1 problems are not always a performance killer because they heavily depend on the number of records you will load.
Imagine if you've implemented an online banking system, and on the statement page, you visualize all the current year's transactions. On testing stages with a low number of transactions and payment details, response times will be ok. However, as soon as the number of payments increases, you will see surprises in terms of slow response times.
You might try to load a list of all the transactions, then get all transaction details and display them on the overview page.
// To load the list of transactions SELECT id FROM transactions // To get all the transaction details SELECT * FROM details WHERE id = ?
Your code will execute N+1 queries to the database. So, for example, you have 5000 payment transactions; your program would run (N+1), 5001 database queries.
Table: TRANSACTIONS Fields: id: bigint title: varchar (255) Table: DETAILS Fields: id: bigint name: varchar (255)
You can use plain SQL or any other OR mapper to select these records. Nor does the former or the latter protect you from running into this N+1 query problem.
Use a query that joins the two tables, transactions and details, and gets the results in a single database call.
// To load everything in a single query SELECT de.id AS id de.name AS name tx.title AS txTitle FROM details de JOIN transactions tx ON de.id = tx.id
Keep in mind that reducing the database queries by using a join or a intelligent OR mapper configuration by using eager loading is always the best performance tuning option.
1. OR Mapper --> consider eager loading.
2. Plain SQL --> join the tables and use a single query.
Creational Pattern / Chatty Services
Intent: Requesting and broadcasting too much information instead of limiting the data in transmit to the necessary records
Motivation: We should only read and transmit data that is required. If you request and send too much data, it always results in performance problems increases the pressure on network and backend systems.
Structure: You implement an online banking system, and on the start page, you display the most recent 50 payment details. All transaction and payment details are stored in a relational database. You have a table of ACCOUNTS containing the summary of each payment and a table DETAILS containing the payment amount and all additional payment details.
Table: ACCOUNTS Fields: id: bigint title: varchar (255) Table: DETAILS Fields: id: bigint name: varchar (255)
Best Solution: The caller, in our case, the online banking webpage, selects the top 50 payments for all five accounts in a single request and displays them.
getAllPaymentsDetails() // 1 request * 20 ms = 200 ms
Worst Solution: The caller, in our case, the online banking webpage, loops through all the five accounts and requests the 50 payment details record by record.
getPaymentRecord() // 5 accounts * 50 payment details * 20 ms = 5 seconds
This example demonstrates that chattiness causes a performance delay of 4 seconds 800 milliseconds. Things worsen if the caller is located in remote geolocation with low bandwidth and a high round trip time.
Keep in mind that there are many such problems in business applications which are causing tremendous performance problems. Unfortunately, you can not fix issues such as by adding faster or more powerful hardware. Your only chance is to go back to the code and solve the explained bottlenecks.
Where can you get the complete list of performance patterns?
Our team is publishing a book about performance patterns, and you can get it on our webpage soon. So stay tuned, and keep up the great work!
Happy Performance Engineering!