Chapter 2.  Measuring Performance on the JVM

In the previous chapter, we introduced and defined important concepts that are related to performance. While extremely valuable, our journey so far has been somewhat academic, and you may grow impatient to exercise your newly acquired knowledge. Luckily, this second chapter does just this! We will take a close look at a real-world scenario and dive head first into the code to profile the application and measure its performance characteristics. This hands-on chapter focuses on one of MV Trading's most successful products: the order book. This is a critical piece of software that was developed to deliver high throughput while maintaining low latency. In this chapter, we will cover the following topics:

  • Benchmarking the latency and throughput of an application
  • Profiling a system with Flight Recorder
  • Using JMH to microbenchmark our code

A peek into the financial domain

This month marks the one year anniversary for MV Trading (MVT). In the last year, the company delivered great returns to its clients by capitalizing on novel trading strategies. These strategies work most effectively when trades can be placed within milliseconds of receiving new price information. To support trading with low latency, the MVT engineering team directly integrated into a stock market exchange. Exchange integration involved datacenter work to collocate the trading system with the exchange and development effort to build the trading system.

A key component of the trading system, known as the order book, holds the state of the exchange. The goal of the exchange is to keep track of how many buyers and sellers have an active interest in a stock and at what price each side is willing to trade. As traders, such as MVT, submit orders to buy and sell a stock, the exchange determines when a trade happens and notifies the buyer and the seller about the transaction. The state managed by the exchange and by extension, MVT, is interesting because orders do not always execute when they reach the exchange. Instead, orders can remain in an open or pending state for up to the length of the trading day (on the order of six hours). This first version of the order book allows traders to place orders called limit orders. Limit orders include a constraint on the minimally-acceptable price. For buys, the limit represents the highest price that the trader wishes to pay, and for sells, this indicates the lowest price the trader wishes to receive in exchange for the stock. Another operation that is supported by the order book is the cancelation of an outstanding limit order, which removes its presence from the book. To help summarize the possible order states, the following table itemizes possible outcomes to support exchange actions:

Exchange action

Outcome

Limit order with a price worse than best bid or offer submitted.

The order rests on the book, meaning that the order remains in a pending state until an order from the opposing side generates a trade or the submitted order is canceled.

Limit order with a price better than or equal to best bid or offer submitted.

The order crosses the book. Crossing the book is industry jargon that indicates an order caused a trade to happen because its price matched one or more orders from the opposing side of the book. A trade consists of two executions, one per side.

Cancelation submitted for a resting order.

The resting order is removed from the book.

Cancelation submitted for an already executed or non-existent order.

The cancelation request is rejected.

Let's suppose that as a newly-hired MVT employee, you just joined the engineering team that is in charge of maintaining and improving the order book. Today is your first day, and you are planning to take most of the morning to calmly skim through the code and get familiar with the application.

After checking out the source code repository, you start with the domain model:

case class Price(value: BigDecimal) 
case class OrderId(value: Long) 
 
sealed trait LimitOrder { 
  def id: OrderId 
  def price: Price 
} 
 
case class BuyLimitOrder(id: OrderId, price: Price)  
  extends LimitOrder 
case class SellLimitOrder(id: OrderId, price: Price)  
  extends LimitOrder 
 
case class Execution(orderId: OrderId, price: Price) 

The class and trait definitions in the preceding code represent the business concepts. We especially notice the two kinds of orders (BuyLimitOrder and SellLimitOrder) that are identified by their unique ID and the associated price assumed to be in USD.

Note

You may wonder why we decided to create distinct class definitions for Price and OrderId while they only serve as mere wrappers for a unique attribute (respectively a BigDecimal for the price, and a Long for the unique ID). Alternatively, we can instead rely directly on the primitive types.

A BigDecimal could represent a lot of different things, including a price, but also a tax rate or a latitude on the globe. Using a specific type, named Price, gives contextual meaning to the BigDecimal and makes sure that the compiler helps us catch possible errors. We believe that it is good practice to always define explicit types to represent business concerns. This technique is part of the best practices known as domain-driven design, and we often apply these principles throughout the book. To learn more about this approach to software development, we recommend the excellent book Domain-Driven Design: Tackling Complexity in the Heart of Software (http://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) by Eric Evans.

The OrderBook module leverages the domain model to define the available commands and the resulting events that can be produced by the book:

object OrderBook { 
  // all the commands that can be handled by the OrderBook module 
  object Commands { 
    sealed trait Command 
    case class AddLimitOrder(o: LimitOrder) extends Command 
    case class CancelOrder(id: OrderId) extends Command 
  } 
 
  // events are the results of processing a command 
  object Events { 
    sealed trait Event 
    case class OrderExecuted(buy: Execution, sell: Execution) 
      extends Event 
    case object LimitOrderAdded extends Event 
    case object OrderCancelRejected extends Event 
    case object OrderCanceled extends Event 
  } 
 
  // the entry point of the module - the current book and  
  // the command to process are passed as parameters,  
  // the new state of the book and the event describing the  
  // result of processing the command are returned 
  def handle(book: OrderBook, command: Command): (OrderBook, Event) = // omitted for brevity 
} 

Let's suppose that you are about to look at the implementation of the handle function in detail when you notice a message in your IM client from your technical lead, Alice: "Everybody in the conference room. We have a problem in production!"

Note

Readers with financial domain expertise likely realize that the presented actions reflect a subset of the functionality of an actual financial exchange. One evident example is the absence of quantity from an order. In our examples, we assume each order represents a desire to buy an equal number of shares (for example, 100 shares). Experienced readers are aware that order volume further complicates order book state management, for example, by introducing the notion of partial executions. We are deliberately simplifying the domain to balance working on a realistic problem while minimizing the barrier to comprehension for readers who are new to the domain.

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

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