If an application isn’t responding quickly, it’s unusable.
It can be the best application ever created, but if it’s not responding fast enough, the users won’t stay.
But the changes to make an application performant are not always easy. We have to make some trade-offs to obtain a high performance application.
In this article I will describe 4 trade-offs I use to pursuit the high-performance of an application.
Increase the Memory Consumption
To compute the results, I need CPU resources. But to speed up the response time, I can consume more memory to obtain the results faster.
There are several ways to do it. Let see a simple one.
Let’s say I have to compute the vehicle prices when displaying the search page.
Instead of fetching all the promotions in the loops, I fetch them all at the beginning and store them by Id in a hash table.
Same for the extras, same for the premium levels.
This way, in the loops, I access the hash tables instead of the database.
public List<VehicleDto> allVehicles() {
Map<Long, Promotion> promotionsById = new HashMap();
Map<Long, Extra> extrasById = new HashMap();
Map<Long, PremiumLevel> premiumLevelsById = new HashMap();
List<Vehicle> allVehicles = vehicleRepository.findAll();
List<VehicleDto> vehicles = new ArrayList();
for(Vehicle vehicle : allVehicles) {
VehicleDto vehicleDto = vehicleMapper.toDto(vehicle);
appendPromotions(vehicleDto, promotionsById);
appendExtras(vehicleDto, extrasById);
appendPremium(vehicleDto, premiumLevelsById):
}
return vehicles;
}
This solution stores a lot of data in memory, with data structures which consumes more memory than a simple list.
But having this trade-off, I reduce my response time significantly.
Duplication in Database
Fetching data from the database is time consuming. The more rows I have, the slower it is. And even worst when I have to join many tables.
To speed up the response time of the database, I can duplicate some of the data in the database. All of this to avoid joining too many tables.
Let say I have a table for the vehicle brands. Another one for the models. And a third one for the premium levels.
When I need to display the vehicle information, I have to fetch 3 tables.
The information about the brand and model do not change frequently. I can duplicate the name of the brand and the name of the model in the premium level table.
I will have the brand and model names duplicate in many rows. But this trade-off eliminates the join of three tables.
Additional Servers
Ok, I’ve optimized my database denormalizing some tables. But it’s still slow. I have too many requests.
The only solution should be to have less requests. I can use pre-loaded resources or batch loads but this won’t reduce the amount of requests my servers receives.
But for more complicated applications or when the load is very high, the best solution is to have many servers where to distribute the load.
I will need a Load Balancer in front of my servers to distribute the load to all servers.
Choosing this solution to optimize the response time is more complicated than the previous solutions. As it requires more resources, configure more servers, maintain more servers, and configure a Load Balancer.
But this solution offers the possibility to handle a big load. At any scale.
Once configured for a second server, I can easily scale it to 3, 4 or more servers depending on the incoming load.
This solution is called horizontal scaling.
This solution can also be applied only to the database. To have a more performant database, I can add several read replicas.
Additional Services
Let’s say the previous solution can’t be adopted because my application can’t run in several servers.
This can be due to the impossibility of using a Load Balancer, or the impossibility of distributing the requests across several servers.
An alternative is to use additional services which help me to optimize the response time.
One solution can be adding a cache server, like Redis. The cache server will store some responses to avoid the application calculate them again the next time.
Nevertheless, having a cache system can be hard to configure. I need to invalidate the stored values when they are too old. I need to configure the keys to minimize the number of entries.
Another solution can be to use another database optimized for read operations. It can be MongoDb. The goal with that is to denormalize the data into this database to avoid making too many joins or operations when computing a single request.
Those solutions require knowledge about a new technology. In addition, it also requiers more servers. Additionally, it requiers more changes in the application to use the new services to update or invalidate the old data.
But the result will also reduce significantly the response time.
Conclusion
When I go down in the list, I increase the complexity of the application, I use additional server or additional services which require some new knowledge. But the response time is optimized exponentially.
When the applications I’m working on slow down, I always start by applying the first two solutions. It it’s not enough, I continue with the two last solutions.



Leave a comment