Microservices vs Monolithic

May 22, 2023

Overview

The term microservice suggests a service that is very small. But, how small? Does a microservice become a monolith after a certain size? Is a service something that is in between? The answer: there is indeed a spectrum between nano-services and monoliths. Once we reach monolith in this spectrum, there is only one (if the context is software architecture). However, within the software industry, it is not uncommon to refer to a very large service in a distributed network of services as a monolith. Conversely, microservice can be a misnomer that simply refers to a service in a distributed system.

Trade-Offs

Below is a comparison of the trade-offs between monoliths and microservices. Only the positives (pros) are listed for both architectures -- a positive for one architecture is a negative for the other.

MonolithMicroservices
Resource UsageAll components of the system share the same pool of resources since they're in the same machine.
  • This is advantageous in use cases where different components experience spikes in resource usage at unpredictable times.
  • It can be useful for cost optimization since there is no overhead to run each service in its own separate runtime, container, or virtual machine.
  • There is no CPU and memory overhead for network I/O calls between microservices (which would travel through the entire network stack).
Components of the system are separated in different machines, virtual machines, containers, or a combination of all of these.
  • Any problem that impacts the resources of a system are isolated to that one component's container or virtual machine (rather than affecting the entire system).
Scalability-Scaling a distributed system can be done surgically by scaling up just the components that require scaling, rather than scaling up the entire system.
Infrastructure ComplexityThe infrastructure of a monolith is much simpler than a distributed system of microservices:
  • No need for a service discovery mechanism.
  • No need for per-component load balancing or traffic balancing of each component.
  • No need to secure inter-component communication.
-
Development ComplexityDeploying a monolith is simpler as there's only a single application to build, test, and deploy. However, deploying changes can be riskier as a bug in any part of the application can potentially bring down the entire system. It's also harder to deploy changes frequently and independently.-
Development Flexibility-Different components can use different technology stacks, which is beneficial for different use cases.
Data ManagementEither monoliths or microservices can have a single or multiple databases. Depending on the architecture of the database schemas of multiple databases, there may be a need to keep data consistent between them via a transactional events-based system, like the Saga Pattern.

Conclusion

A common way to deploy a microservice is in a container. Languages that compile and don't require a VM (like Java's JVM) to run require the least memory to work. For example, a minimal Golang program running in a container could run with only 10MB. A microservice running a server with only a few API endpoints and minimal routing logic could work with less than 50MB. This size of service would be referred to as a nanoservice (on the spectrum referenced in the Overview).

When a service is this small, it creates a lot of redundant overhead of the following for each container:

  • The operating system's userspace (non-Kernel set of libraries, utilities, and more) that needs to be duplicated per-container, which includes:
    • Container runtime (such as Docker).
    • System services (process management, logging, networking, and more).
    • Operating system scheduler.
    • Application runtime (rough estimate in MB: single-digit for compiled languages, double-digits for interpreted, and triple-digits for those requiring a VM like Java).
  • Overhead of free space to prevent OOM (Out Of Memory crashes).

There is also redundant CPU overhead for all of the above.

No one-size-fits-all solution exists for the perfect size of a microservice. The perfect size is often discovered over several iterations of refactorings and versions of services over the lifetime of a system.