Entries in QDS (2)

Wednesday
Dec282011

QDS transport and networking

In the previous blog entry titled "Introduction into QDS architecture" I gave a short overview of the overall QDS design. Today I'll explain the basics of QDS transport and networking architecture.

QDS transport layer uses quite non-standard design. In a typical enterprise application that is built in layers, the presentation layer is built on top of a business logic layer, which is built on top of transport, communication, and storage layers. That is, logic typically depends on the transport layer. In QDS this dependency is inverted. Transport layer is built on top of QDS core logic and depends on it.

QDS transport and networking

The are various connectors that transfer data over the network. Connectors may come in different flavors depending on the underlying protocols, means of establishing connection, or technologies they use: server or client, synchronous or asynchronous, unicast or multicast, TCP/IP, UDP, HTTP, etc. There's even a file connector that does not actually work over the network, but reads pre-recorded messages from a file.

Each connector is responsible for establishing connections, and delivery/reception of the underlying messages over the network. Typically, there are two different roles in the communication. On one side data provider receives subscription and sends data while communicating with the data receiver on the other side. Connector on the data provider side creates agent in the QDS core to manage subscription and filter data that pertains to this subscription. Connector on the data receiver side creates distributor in the QDS core to feed the incoming data into the core and collect subscription from the core. This data will be ultimately delivered for processing to the core agents in the data receiver node.

The actual connector code works with agents or distributors though a special adapter whose purpose is to convert internal QDS core API into the message-based API that is suitable for network communication. Thus, there are agent and distributor adapters. For example, a combination of ServerSocketConnector with AgentAdapter creates a data provider node that listens for incoming TCP/IP connections. On the other side, ClientSocketConnector with DistributorAdapter creates a data receiver that can connect to the data provider via TCP/IP protocol. However, while data receiver node typically establishes connection to the data provider, it does not have to be always this way. The transport architecture is fully flexible from deployment perspective, so that, for example, data providers can establish connection to a central data receiver node instead.

This flexible transport and networking architecture makes it easy to deploy a multiplexor node.

QDS multiplexor node

QDS multiplexor node contains QDS core and at least two connectors — one for agents and one for distributors. Multiplexor works as data receiver and data provider at the same time. It collects subscription from multiple data receivers and collectors data from multiple data providers, delivering this data to each data receiver based on their subscription.

Monday
Dec052011

Introduction into QDS architecture

dxFeed Market Data services are built on top of QDS core — the open source, high-performance messaging solution that was designed from the ground up to deliver millions of quotes on hundreds of thousands financial instruments to virtually unlimited number of destinations in a scalable way, while supporting fully individual subscription for each recipient.

The QDS core is an integral part of dxFeed API, hidden behind top-level easy-to-use object-oriented API of dxFeed.

QDS/dxFeed Architecture Overview

The key function that is being performed by QDS core is message multiplexing. It collects messages from a number of data sources that are called distributors in QDS parlance, and forwards those messages to data consumers, that are called agents. Each agent has its own subscription to the data items that it is interested in. This subscription is collected by QDS core into a total subscription that is made available to distributors. QDS core is also known as collector.

QDS core data flow

The key to QDS core performance is very careful memory management that is incorporated into every aspect of its design. Modern computer architectures are notoriously known for multi-level cache hierarchies with a relatively slow main memory compared to the main CPU speed. The amount of memory consumed to encode the data and memory locality are very important for ultimate performance. QDS core address memory consumption and memory locality aspects with several design decisions.

First of all, data items are grouped into data records. Those data fields that typically change together are grouped together. A trade on the exchanges changes last trade price, last trade size, etc, so all those fields are grouped into a trade record. Best bid and best offer information are grouped into a quote record, and so on. Data records are not atomic, they are still composed from the number of individual fields. However, data records are atomic units of subscription and atomic units of data update. By tracking subscription and data update on the per record level, instead of per field level, the amount of bookkeeping is reduced multiple-fold, with a corresponding reduction in memory consumption for all the data structures that QDS core has to maintain.

Second, is the compact representation of most numerical values. QDS uses custom-designed decimal format that is build for the specific purpose of representing prices of financial instruments. This domain-specific representation is based on the observation that financial instruments are traded mostly in decimals with a limited number of significant digits. Volatility of financial instruments is mostly scale-free, so exchanges tend to limit price precision of expensive instruments, but allow more digits after decimal point for cheaper ones. Those observations had allowed to design a decimal representation in QDS that takes only 4 bytes per price.

Third, is the use of data buffers to transmit data from distributors to QDS core and to agents. While it is easier to design a system where each data item is be represented by an object and transfer collections of those objects around, this kind of design results in a poor memory locality and poor performance. QDS follows a different approach. Data transfers are performed via contiguous regions of memory also known as data buffers. Data buffers naturally map into data packages that has to be sent over the network, thus the process of serializing and deserializing data for network transfer requires only sequential access to memory — the fastest possible kind of memory access pattern.

Last, but not the least, is that all data transfers in QDS are garbage-free. Having dispensed with individual small objects to represent data items, it is easy to reuse data buffers. The overall result is that there are no object allocations in the normal data transfer path from distributors to agents. It means no garbage collection pauses and enables QDS to transfer millions of records per second.

This design for performance comes with the price of complex QDS API. That is why, for the end-users of dxFeed quote services, there is a layer on top of QDS API that is called dxFeed API. It creates Trade, Quote, and other event objects that have easy-to-use API, where values are represented with double precision floating point numbers and can be directly used via regular getter methods. This layer makes it easier to evolve the actual encoding of market data in QDS, tracing the changing trends in market data, while maintaining compatibility with existing code that uses dxFeed API. QDS records and their decimal-enconded fields serve as a low-level representation of market data, while dxFeed API provides a high-level API to it.