DEEPFLOW JOURNAL

Enterprise Software Should Stop Defaulting to Microservices

Modern hardware and mature infrastructure are changing the economics of microservices. A look at operating costs, request latency, and ticketing capacity explains why enterprise software should evaluate modular monoliths first.

Cover image for Enterprise Software Should Stop Defaulting to Microservices

A ticketing system serves a few hundred people, yet needs a dozen services, service discovery, configuration management, and distributed tracing. Operations is already busy before the first ticket arrives.

Private deployments expose this problem particularly well: the user base shrinks, but the service topology stays. Business traffic is modest; the architecture's fixed overhead remains.

For enterprise software with closely related business functions, a moderately sized engineering team, and both SaaS and private deployment requirements, a modular monolith should be the first option to evaluate. The benefits of scaling, releasing, or isolating a service independently should cover its long-term costs.

Microservices had clear reasons to emerge over the past decade and more. Growing internet businesses needed multiple servers to share the load. Larger engineering teams needed independent releases. Uneven workloads called for scaling individual components.

Service decomposition then became part of enterprise software selection. Accounts, customers, permissions, tickets, and workflows could each become a separate process.

Modules need clear interfaces. Independent deployment needs an additional justification. Those are two separate decisions.

The conditions behind those decisions have changed.

A single server now offers much more parallel computing capacity. AMD's EPYC 9965 provides 192 physical cores; the EPYC 9006 series announced in 2026 specifies up to 256 cores and 512 threads per socket. Workloads once spread across several smaller servers have more room to run together.

Applications use threads and processes to make use of multiple cores. Actual throughput still depends on memory bandwidth, the database, storage I/O, and lock contention. Measure single-server capacity and identify the real bottleneck before choosing an architecture.

Databases and storage systems also offer mature replication, backup, and failover capabilities. Aurora, for example, provides storage fault tolerance through six copies across three Availability Zones. Applications access these capabilities through the database interface while retaining a compact internal structure. Transactions, tenant isolation, and business correctness remain the application team's responsibility.

Hardware has expanded single-server capacity, and infrastructure handles more common capabilities. Every application-level split deserves another look.

Start with resource costs.

Suppose a system has 12 services, each with an assumed runtime and baseline memory footprint of 512 MiB. Those processes alone require roughly 6 GiB. Run two replicas of every service, and that becomes about 12 GiB—before the database, cache, business data, and supporting components.

Actual usage varies by language and implementation, but every independent process has a running cost. Sharing hosts does not eliminate processes, connection pools, or version dependencies.

A SaaS platform can spread this overhead across many tenants. A private deployment might have only a few dozen operators while still requiring the full feature set and all its dependencies.

There is also the cost of people's time. Installation must account for startup order. Upgrades require compatibility checks. Troubleshooting follows requests across services. More services mean greater reliance on automation and operational expertise, both of which need ongoing investment.

Private deployments should track a concrete metric: the minimum operating cost of the complete application. Provision for the actual workload and keep the architecture's fixed overhead low.

Then consider the response cost of an everyday operation.

Submitting a ticket typically involves checking permissions, reading customer information, validating fields, saving records, and triggering a workflow. If these capabilities live in separate services, one operation can become several remote calls. Serialization, network transfer, server-side queuing, and deserialization all contribute to response time.

text
Serial request time ≈ Business computation + Data access
                    + Communication and queuing across remote calls

Remote calls also require handling partial failures. A request succeeds but its response never arrives, so a retry may repeat the operation. An update fails halfway across services, requiring compensation and recovery. All of this adds engineering work.

A modular monolith lets related modules collaborate through in-process interfaces. Updates covered by the same database transaction can commit atomically. External notifications and expensive tasks can run asynchronously where appropriate. Troubleshooting can focus more directly on queries, computation, and transaction waits.

Reducing waits along internal call paths is a direct way to improve responsiveness. Missing indexes, inefficient permission queries, and repeated data fetching still need individual attention.

What does “tens of thousands of users” actually mean for enterprise software?

Twenty thousand accounts might translate into only two thousand people actively using the system at once, with reading and typing between actions. Capacity planning must convert people into requests, then examine the computation and data access behind those requests.

For a lightly interactive ticketing application, assume each active user generates one business API request every 10 seconds on average:

text
2,000 active users × 0.1 requests/second = 200 RPS
20,000 active users × 0.1 requests/second = 2,000 RPS

Count every API call made by a page, along with polling and background synchronization. At one request every two seconds per user, traffic is five times higher.

Using this workload model, with ticket lists, details, creation, replies, and status updates at an 80:20 read/write ratio, the following capacity budget provides starting configurations and user targets. It assumes effective use of multiple cores, suitable indexes, and pagination. Attachments, bulk imports, complex reports, and AI inference require separate resource budgets.

ScenarioStarting configurationTarget active usersBusiness request rate
Small private deploymentOne 4-vCPU / 16-GB server running the application, database, and Redis20–502–5 RPS
Standard private deploymentApplication: 8 vCPU / 16 GB; database: 8 vCPU / 32 GB; Redis: 2 GB200–50020–50 RPS
Large private deployment or initial SaaS deploymentApplication: 32 vCPU / 64 GB; database: 16 vCPU / 64 GB; Redis: 4–8 GB1,000–3,000100–300 RPS
High-capacity SaaSApplication: 128 vCPU / 256 GB; database: 64 vCPU / 256 GB; Redis: 16 GB5,000–20,000500–2,000 RPS

The table uses cloud-instance vCPU specifications; their relationship to physical cores depends on the instance architecture. Redis figures specify memory. SSD storage, file capacity, background computation, and spare resources for high availability must be included in the full configuration.

At a 10% simultaneous activity rate, the standard private deployment corresponds to roughly 2,000–5,000 accounts, and the high-capacity SaaS configuration to roughly 50,000–200,000. Dedicated support agents have higher activity rates and request frequencies, so plan around their actual working patterns.

The database needs its own calculation. At 2,000 RPS and an average of five SQL statements per request, it must handle roughly 10,000 SQL executions per second. A primary-key lookup and a large-table aggregation have very different costs. Adding application servers may simply make more requests wait for the database.

Load tests should use data at the intended scale, real permissions, and workflows covering complete read/write paths. An initial target for ordinary endpoints might be P95 latency no higher than 300 milliseconds and an error rate below 0.1%. Increase load while watching lock waits, connection pools, and task backlogs, then derive supported user counts from the stable workload.

Once these costs are understood, the application's basic structure can be straightforward:

text
Users
  |
Entry point / Load balancer
  |
Modular application (one or more replicas)
  |-- Relational database
  |-- Redis, as needed
  `-- File storage / Background tasks / Specialized computation, as needed

For ordinary business workloads that need caching and shared state, the core combination is an application server, a cloud database, and cloud Redis. Private deployments use equivalent local or private-cloud components. High availability requires application replicas, database failover, backup and recovery, and verification of the capacity remaining after a failure.

The same monolithic application can run multiple replicas behind a load balancer, provided shared state, task coordination, and idempotency are handled. SaaS and private deployments can reuse core code and module boundaries, adjusting resources, replica counts, and tenant management to the workload.

Unified deployment requires a clear internal structure: modules have defined responsibilities and public interfaces, cross-module dependencies can be checked, and data changes follow the owning module's business rules. Boundaries must be enforced in code.

DeepFlow's OpenDesk is one implementation of this approach. Its modular monolith organizes online conversations, customer records, and tickets around the complete conversation-to-ticket path. Configurable fields, layouts, and workflows accommodate customer differences, allowing business capabilities to grow while keeping deployment compact.

Dedicated resources for GPU inference, latency requirements for real-time voice, isolation for batch analytics, and independent releases for large teams are concrete reasons to introduce separate runtime boundaries.

Architecture should evolve by first establishing data models and module interfaces, then improving queries and transactions. Once capacity limits are measured, decide whether to increase resources, add replicas, or separate a capability.

Complex architecture needs measurable benefits. Every additional service should solve an identified problem. Progress in enterprise software should ultimately mean better resource efficiency, more reliable delivery, and less waiting for users.

Back to blog

Latest articles

View all articles