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.
Application components that can be the source of chatty I/O include:
The following examples demonstrate the Antipattern.
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:
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 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.
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.
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.
Some of the remedies for fixing this Antipattern include:
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.
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.
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.
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.
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.
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.
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!