Microservice architectures are trending. We need to consider how this impacts the performance of our applications and the databases we build to support them.
What is a Microservice?
Over the past decade or so, the Service Oriented Architecture (SOA) has been one of the prevalent application architectures used in large enterprises. Developing an SOA system is complex. Programmers spend significant amounts of energy focused on the interaction between services and the fulfillment of contracts. The use of XML is prevalent in an SOA environment, adding to application complexity.
With a microservice focus, programmers try to build small components that have a single area of concern. The idea is to keep the programs focused on doing one thing well and avoiding complexity where possible. The microservices are often developed using RESTful APIs to keep the interfaces simple and adaptable. This allows the applications and services to provide basic functionality early and evolve quickly. The use of HTTP/HTTPS and JSON keeps the API simple and adaptable while allowing each component to be implemented using technology suited to its requirements.
An API gateway may also be part of the architecture. This allows security, load balancing and other concerns to be implemented externally from the services. The less an individual service needs to do, the more focused it can be on doing its one thing well.
Microservice development teams want to choose the most appropriate technology for their solutions. This may put them at odds with the use of shared databases and DBMS technology. Some components will perform best using relational tables and others are better suited to using a document datastore.
How does it impact an application?
To design an application using a microservice architecture, we would first break it down into individual components and build services around those components. We’d possibly build APIs around Customers, Customer Preferences, Reference Data, Email Communication, SMS Text Communication, Robocall Communication, and many more.
The application would then need to call each of the needed components for a given situation. The communication-oriented components may be bundled into a Send Communication service in order to build a component focused on the sending of communications to a Customer. This abstraction (via an aggregated service) allows the focused client to spend less time thinking (and programming) around the individual communication preferences of the customer.
This means we now have an application that needs to interact with dozens of services to perform what might have been done in a single large complex application before. This isn’t really anything new. Mainframe-based applications have been organizing CICS transactions around the concept of modules for many years. We’ve had the ability to call programs locally and in remote CICS regions, even calling out to a service program in a remote data center owned by another organization.
How does it impact performance?
What is seriously impactful to our “modern” applications in a microservice architecture is the performance impact. Each of these simple RESTful API interactions takes time. Time needs to be taken to:
- serialize the request objects
- communicate over the network (including TCP/IP protocol overhead)
- parse the request
- deserialize the request objects
- validate the request
- access the database
- perform the requested action
- serialize the response
- communicate the response
- parse the response
- deserialize any response objects
- validate the response
Many of these time-consuming actions take only a few microseconds, but serialization/deserialization of complex objects can take 5-10 milliseconds in Java. If that has to happen on both the client and server, we’re now looking at 40 milliseconds plus another 10 milliseconds for each communication event. We’re at 60 milliseconds and we haven’t performed any logic yet. And that is for each call. Choice of language, infrastructure scalability, and network configuration can all impact the response of each component that is built.
Given this overhead, it is important to look at the overall performance of each component and any of the resources that component uses.
How does this impact our database?
Database calls will have tighter performance requirements than ever before. Assuming a database server that is not co-located with an application component, we have network communication added to the perceived overhead of using a database. When components are deployed into a cloud environment and the database is not, that network communication can be larger than we’ve been used to. This will lead to applications pointing at the database as being responsible for poor performance (not that this would be new, of course).
When an application component has been given 100 milliseconds to do its work and it is already using half that time for communication, the amount of time allowed for database work is going to be reduced. We already know how to fix this, add more memory to our buffer pools, tune our database servers, pay attention to the design of our tables and indexes, use document datatype (like XML columns) where it makes sense.
If the database servers are not properly tuned and managed, application teams will start trying to deploy caching solutions. This can improve their overall performance, but it can also lead to data integrity issues if they use cached data to make updates to databases. If your application teams are talking about using a caching solution, make sure it is the right solution rather than a workaround.
In order to succeed, microservice architectures will require the best database performance we have ever had. Ensure your database servers have the memory they need and use fast disks where possible. Although application teams will feel they can build their components without database administrators, they need knowledgeable DBAs more than ever.