Chapter 8. Application Performance

The earliest computing devices were built to perform automatic computations and, as computers grew in power, they became increasingly popular because of how much and how fast they could compute. Even today, this essence lives on in our anticipation that computers can execute our business calculations faster than before by means of the applications we run on them.

Compared to performance analysis and optimization at a smaller component level, as we saw in previous chapters, it takes a holistic approach to improve performance at the application level. The higher-level concerns, such as serving a certain number of users in a day, or handling an identified quantum of load through a multi-layered system, requires us to think about how the components fit together and how the load is designed to flow through it. In this chapter, we will discuss such high-level concerns. Like the previous chapter, by and large this chapter applies to applications written in any JVM language, but with a focus on Clojure. In this chapter, we will discuss general performance techniques that apply to all layers of the code:

  • Choosing libraries
  • Logging
  • Data sizing
  • Resource pooling
  • Fetch and compute in advance
  • Staging and batching
  • Little's law

Choosing libraries

Most non-trivial applications depend a great deal on third-party libraries for various functionality, such as logging, serving web requests, connecting to databases, writing to message queues, and so on. Many of these libraries not only carry out parts of critical business functionality but also appear in the performance-sensitive areas of our code, impacting the overall performance. It is imperative that we choose libraries wisely (with respect to features versus performance trade off) after due performance analysis.

The crucial factor in choosing libraries is not identifying which library to use, rather it is having a performance model of our applications and having the use cases benchmarked under representative load. Only benchmarks can tell us whether the performance is problematic or acceptable. If the performance is below expectation, a drill-down profiling can show us whether a third-party library is causing the performance issue. In Chapter 6, Measuring Performance and Chapter 7, Performance Optimization we discussed how to measure performance and identify bottlenecks. You can evaluate multiple libraries for performance-sensitive use cases and choose what suits.

Libraries often improve (or occasionally lose) performance with new releases, so measurement and profiling (comparative, across versions) should be an ongoing practice for the development and maintenance lifecycle of our applications. Another factor to note is that libraries may show different performance characteristics based on the use case, load, and the benchmark. The devil is in the benchmark details. Be sure that your benchmarks are as close as possible to the representative scenario for your application.

Making a choice via benchmarks

Let's take a brief look at a few general use cases where performance of third-party libraries are exposed via benchmarks.

Web servers

Web servers are typically subject to quite a bit of performance benchmarking due to their generic nature and scope. One such benchmark for Clojure web servers exists here:

https://github.com/ptaoussanis/clojure-web-server-benchmarks

Web servers are complex pieces of software and they may exhibit different characteristics under various conditions. As you will notice, the performance numbers vary based on keep-alive versus non-keep-alive modes and request volumeā€”at the time of writing, Immutant-2 came out better in keep-alive mode but fared poorly in the non-keep-alive benchmark. In production, people often front their application servers with reverse proxy servers, for example Nginx or HAProxy, which make keep-alive connections to application servers.

Web routing libraries

There are several web routing libraries for Clojure, as listed here:

https://github.com/juxt/bidi#comparison-with-other-routing-libraries

The same document also shows a performance benchmark with Compojure as the baseline, in which (at the time of writing) Compojure turns out to be performing better than Bidi. However, another benchmark compares Compojure, Clout (the library that Compojure internally uses), and CalfPath routing here:

https://github.com/kumarshantanu/calfpath#development

In this benchmark, as of this writing, Clout performs better than Compojure, and CalfPath outperforms Clout. However, you should be aware of any caveats in the faster libraries.

Data serialization

There are several ways to serialize data in Clojure, for example EDN and Fressian. Nippy is another serialization library with benchmarks to demonstrate how well it performs over EDN and Fressian:

https://github.com/ptaoussanis/nippy#performance

We covered Nippy in Chapter 2, Clojure Abstractions to show how it uses transients to speed up its internal computations. Even within Nippy, there are several flavors of serialization that have different features/performance trade-offs.

JSON serialization

Parsing and generating JSON is a very common use case in RESTful services and web applications. The Clojure contrib library clojure/data.json (https://github.com/clojure/data.json) provides this functionality. However, many people have found out that the Cheshire library https://github.com/dakrone/cheshire performs much better than the former. The included benchmarks in Cheshire can be run using the following command:

lein with-profile dev,benchmark test

Cheshire internally uses the Jackson Java library https://github.com/FasterXML/jackson, which is known for its good performance.

JDBC

JDBC access is another very common use case among applications using relational databases. The Clojure contrib library clojure/java.jdbc https://github.com/clojure/java.jdbc provides a Clojure JDBC API. Asphalt https://github.com/kumarshantanu/asphalt is an alternative JDBC library where the comparative benchmarks can be run as follows:

lein with-profile dev,c17,perf test

As of this writing, Asphalt outperforms clojure/java.jdbc by several micro seconds, which may be useful in low-latency applications. However, note that JDBC performance is usually dominated by SQL queries/joins, database latency, connection pool parameters, and so on. We will discuss more about JDBC in later sections.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.24.191