An Antipattern describes a common response to a problem that is ineffective and creates negative consequences. Alternatively, Design Patterns are common approaches to common problems which have been formalized and are generally considered a good practice. Antipatterns are the opposite and are undesirable.
The Chatty Input / Output (I/O) Anti pattern occurs when the cumulative effect of a large number of Input and Output requests has a significant negative impact on the performance and responsiveness of a service. Network calls and other I/O operations are inherently slow compared to compute tasks. Each I/O request typically has significant overhead, and the cumulative effect of numerous I/O operations can slow down the service.
Potential Sources
Application components that can be the source of chatty I/O include:
- Databases: Can become chatty when a single use case or business transaction results in multiple queries to the same set of data.
- APIs: Requiring multiple network calls for a single user operation will slow down an application as each call contains data overhead (i.e. sender information, headers, authentication).
- File Operations: An application that continually reads and writes small amounts of information to a file will generate significant I/O overhead. Small write requests can also lead to file fragmentation, slowing subsequent I/O operations still further.
Examples of Chatty I/O
The following examples demonstrate the Antipattern.
Reading and writing individual records to a database as distinct requests
The following example reads from a database of products. There are three tables, Product, ProductSubcategory, and ProductPriceListHistory. The code retrieves all of the products in a subcategory, along with the pricing information, by executing a series of queries:
- Query the subcategory from the ProductSubcategory table.
- Find all products in that subcategory by querying the Product table.
- For each product, query the pricing data from the ProductListPriceHistory table.
The application uses Entity Framework, a layer of software that enables developers to work with data using objects without focusing on the underlying database where this data is stored.
The N+1 Problem
The example above shows the problem explicitly, but sometimes an Object-Relational Mapping (ORM) can mask the problem, if it implicitly fetches child records one at a time. This is known as the “N+1 problem”.
The N+1 query problem happens when the data access framework executed N additional SQL statements to fetch the same data that could have been retrieved when executing the primary SQL query. The larger the value of N, the more queries will be executed and the larger the performance impact.
Implementing a single logical operation as a series of HTTP requests
This often happens when developers try to follow an object-oriented paradigm and treat remote objects as if they were local objects in memory. This can result in too many network round trips. For example, the following web API exposes the individual properties of User objects through individual HTTP GET methods.
While there’s nothing technically wrong with this approach, most clients will probably need to get several properties for each User, resulting in client code like the following.
Reading and writing to a file on disk
File I/O involves opening a file and moving to the appropriate point before reading or writing data. When the operation is complete, the file might be closed to save operating system resources. An application that continually reads and writes small amounts of information to a file will generate significant I/O overhead. Small write requests can also lead to file fragmentation, slowing subsequent I/O operations still further.
The following example uses a FileStream to write a Customer object to a file. Creating the FileStream opens the file, and disposing it closes the file. (The using statement automatically disposes the FileStream object.) If the application calls this method repeatedly as new customers are added, the I/O overhead can accumulate quickly.
How to Fix the Problem
Some of the remedies for fixing this Antipattern include:
- Fetch data from a database as a single query, instead of several smaller queries.
- Follow REST design principles for web APIs.
- Buffer data in memory for file operations and then write data once
Database: Fetch Data in a Single Query
Fetch data from a database as a single query, instead of several smaller queries. Here’s a revised version of the code that retrieves product information.
API: Use REST Principles
Follow REST design principles for web APIs. Here’s a revised version of the web API from the earlier example. Instead of separate GET methods for each property, there is a single GET method that returns the User. This results in a larger response body per request, but each client is likely to make fewer API calls.
File: Buffer Data in Memory
For file I/O, consider buffering data in memory and then writing the buffered data to a file as a single operation. This approach reduces the overhead from repeatedly opening and closing the file and helps to reduce fragmentation of the file on disk.
Service Granularity
The Service Granularity Principle is a software design concern that specifies the scope of business functionality and the structure of the message payload in a service operation. Achieving optimal service granularity can be difficult. Too granular and the service can become chatty while too course can be inefficient by retrieving more data than is required.
The Use Case for the service must be matched with the proper service granularity to ensure an efficient design which avoids the Chatty IO AntiPattern.
Detecting the Problem
Symptoms of chatty I/O include high latency and low throughput. End users are likely to report extended response times or failures caused by services timing out, due to increased contention for I/O resources.
Process Monitoring
- Perform process monitoring of the production system to identify operations with poor response times.
- Perform load testing of each operation identified in the previous step.
- During the load tests, gather telemetry data about the data access requests made by each operation.
- Gather detailed statistics for each request sent to a data store.
- Profile the application in the test environment to detect possible, I/O bottlenecks
Look for any of these symptoms:
- large number of small I/O requests made to the same file.
- large number of small network requests made by an application instance to the same service.
- large number of small requests made by an application instance to the same data store.
- Applications and services becoming I/O bound.
Monitor the Application Using App Dynamics
Using an Application Performance Monitoring (APM) package such as App Dynamics to capture and analyze key metrics that can identify chatty I/O. The key performance metrics will vary depending on the I/O workload. For example, database, file or API.
Summary
Chatty I/O is an Anti pattern that can occur within several parts of an application or service including the database, file operations and inter-process communications with APIs. The cumulative effect of a large number of I/O requests can have a significant impact on performance and responsiveness. Several monitoring techniques can be used to find the root cause of the chatty component. Software designs should consider the Service Granularity Principle. That is, scoping data retrieval to match the Use Case so the appropriate amount of data is retrieved in each request.
Thanks & Happy reading!