AWS Architecture Blog

How OrderServ achieved up to 64% cost savings with multi-tenant ECS

When your food-ordering platform doubles its brand count, dedicated clusters become your biggest cost problem, and every failed request during a traffic surge is a lost sale.

Here is how Cognizant and AWS Professional Services solved it.

Cognizant is a global services company that helps organizations modernize technology, reimagine processes, and transform customer experiences. Its OrderServ service is an omnichannel ordering solution for the food services industry that supports ordering across devices and integrates with point-of-sale, kitchen display, payment, loyalty, and other enterprise systems. Cognizant is an AWS Premier Tier Services Partner with multiple AWS Competencies and a member of the AWS Managed Service Provider (MSP) and AWS Well-Architected Partner Programs.

In 2022, Cognizant published how OrderServ 2.0 reimagined digital food ordering by migrating from a monolith to microservices on AWS Fargate, achieving 30 percent cost savings over traditional hosted architectures.

Since then, the service grew from serving a single quick-service restaurant (QSR) brand to powering multiple brands across markets. Each brand ran as a dedicated Amazon Elastic Container Service (Amazon ECS) cluster with 21 microservices. This provided hard isolation, but cost scaled linearly with every brand onboarded, and scaling speed could not keep up with promotional traffic surges that spike 5–10× within minutes.

In this post, we describe how AWS Professional Services partnered with Cognizant to transform OrderServ into a multi-tenant software as a service (SaaS) offering, achieving 47–64 percent compute cost reduction, scaling time reduced from 5–7 minutes to 30–60 seconds, and authentication costs cut by 80 percent.

The problem: Dedicated clusters don’t scale with brands

OrderServ 2.0 ran 21 microservices per brand on dedicated AWS Fargate clusters. For five brands, this meant:

  • 105 ECS services (21 × 5), each with idle capacity during off-peak hours.
  • 315 sidecar containers for observability, configuration, and log routing, duplicated per brand.
  • Linear cost growth, because onboarding a new brand required provisioning an entire cluster.
  • 5-minute scaling gap, because large container images increased image-pull and task startup times on AWS Fargate, delaying the addition of capacity during promotional traffic surges.

In QSR, every failed request during a surge is a lost sale and users may switch to competitor services immediately. The service needed to serve over 10 brands cost-effectively while handling unpredictable traffic patterns: lunch rushes, promotional events, and festival surges.

Solution overview

We addressed these challenges through four architectural changes:

  • Shared ECS cluster with a tenancy library: Application-level isolation replaces per-brand infrastructure.
  • EC2 warm pools with Bottlerocket: Sub-minute scaling replaces AWS Fargate cold starts.
  • Crash observability pipeline: Zero-loss logging and 90-day forensics at $10/month per service.
  • Passkeys for multi-tenant authentication: Biometric login reduces recurring SMS costs.

Solution 1: Shared cluster with application-level isolation

As OrderServ scaled from one to five brands, the cost of replicating 21 microservices per tenant made the economics unsustainable. We needed to consolidate infrastructure while preserving the data isolation guarantees each brand required.

Why we moved away from dedicated clusters

The dedicated-cluster model provided strong isolation but no economy of scale. With the platform growing from five brands, onboarding a new brand needed to become a configuration change, not an infrastructure provisioning exercise.

Model Pros Cons
Dedicated cluster per brand (status quo) Hard isolation, simple AWS Identity and Access Management (IAM) Linear cost, idle capacity, slow onboarding
Shared cluster with opt-out Aggregate scaling, config-only onboarding Requires application-level isolation, wider IAM scope

We chose the shared cluster as the default, with the ability to place high-volume brands on dedicated clusters when needed.

Figure 1 illustrates this transformation, from five dedicated ECS clusters (105 services, 315 sidecar containers) to a single shared cluster with 21 services and an opt-out path for high-volume brands. The tenancy library handles tenant resolution, resource routing, and context propagation, while per-tenant data stores (Amazon DynamoDB, Amazon Aurora, Valkey) maintain strict data isolation.

Evolution from dedicated clusters per brand to a shared cluster model with opt-out for high-volume brands

Figure 1: Evolution from dedicated clusters per brand to a shared cluster model with opt-out for high-volume brands

The service uses AWS Key Management Service (AWS KMS) keys to encrypt every data store at rest, and the architecture implements Transport Layer Security (TLS) to protect all service-to-service and client-to-service communication. Amazon DynamoDB automatically encrypts tables by default. Customers enable encryption when creating Amazon Aurora Clusters and the Amazon Elastic File System (Amazon EFS) file system, and customers configure both at-rest and in-transit encryption for the Amazon ElastiCache for Valkey cluster.

The tenancy library as primary isolation

Most multi-tenant architectures enforce isolation at the infrastructure level: separate accounts, separate IAM roles, or separate compute. We took a different approach: a shared .NET Core library as the primary enforcement point for tenant isolation. IAM and resource policies are the backstop that contains the blast radius when the library has a defect.

Every request carries an X-Tenant-Id header injected by the Amazon API Gateway authorizer. The tenancy library:

  • Resolves tenant context per request using request-scoped storage (AsyncLocal<T>), never static or singleton state.
  • Routes all data access: Amazon DynamoDB table names, Amazon ElastiCache for Valkey key prefixes, Amazon Aurora schema selection, and S3 paths.
  • Refuses to construct resource IDs without a tenant prefix, so a missing tenant context fails closed instead of reading another tenant’s data.
  • Propagates tenant context on service-to-service calls and asynchronous messages (SQS/SNS attributes).
  • Enriches every log line and distributed trace with the tenant ID.

This design means the same service binary runs identically in a shared cluster or a dedicated cluster. The compute model becomes a deployment decision, not an architectural constraint.

Application-level isolation moves the isolation boundary into code, and code could potentially have defects. Because an Amazon ECS task role is granted per task rather than per request, a task in the shared cluster necessarily holds permissions spanning every brand it serves. To narrow that scope, the tenancy library acquires tenant-scoped credentials at run time: it calls sts:AssumeRole with the tenant ID as a session tag, and the role’s policies reference that tag so the credentials reach only that tenant’s DynamoDB tables, S3 prefixes, and Aurora schema. Amazon ElastiCache for Valkey enforces its own layer, with per-tenant role-based access control (RBAC) users restricted to the tenant’s key pattern, so the cache engine itself refuses a cross-tenant key access rather than trusting the library to have built the key correctly.

Impact

The following table compares key operational metrics (service count, sidecar overhead, onboarding time, and capacity utilization) before and after the migration to a shared cluster model.

Metric Before (dedicated) After (shared + library)
ECS services to deploy 21 × N brands 21 total
Sidecar containers 315 (for 5 brands) 63 total
Onboarding a new brand Provision full cluster (days) Configuration change (minutes)
Idle capacity Per-brand (wasted) Aggregate (efficient)

Solution 2: EC2 warm pools with Bottlerocket for sub-minute scaling

With all brands now sharing a single cluster, traffic spikes from any one brand could impact the others. We needed a scaling strategy that could absorb sudden surges in seconds, not minutes.

Why we moved away from AWS Fargate

AWS Fargate removes infrastructure management, but OrderServ required additional capacity in under a minute during unpredictable demand surges. New tasks took 5–7 minutes to become healthy, primarily because Fargate had to download and extract the workload’s large container image before starting the application. Task provisioning, application initialization and health checks added to the delay, but image retrieval was the largest contributor. During this interval, existing ECS tasks become saturated, latency increased and some orders started failing.

The solution: EC2 Auto Scaling groups with warm pools

We migrated to Amazon EC2 Auto Scaling groups with Bottlerocket (the AWS purpose-built container OS) and warm pools:

  • The system pre-initializes and stops warm pool instances, and you pay only for Amazon Elastic Block Store (Amazon EBS) storage (~$3/month per instance).
  • When ECS needs capacity, the Auto Scaling group (ASG) starts a stopped instance (image pre-cached, ECS agent registered) in 30–60 seconds.
  • We set capacity headroom (target utilization at 80 percent), so that the first wave absorbs instantly.
  • Bottlerocket provides atomic auto-updates, eliminating the Amazon Elastic Compute Cloud (Amazon EC2) patching burden.

Five-layer scaling strategy

Rather than relying on a single scaling mechanism, we implemented five complementary layers, each targeting a specific traffic pattern to remove gaps between detection and response.

Layer Mechanism Traffic pattern solved
Predictive ECS predictive scaling (machine learning, 14-day history). Scheduled scaling for specific known events (New Year, Christmas) was already handled manually via ASG scheduled actions Daily lunch/dinner peaks, scheduled scaling
Reactive Target tracking on ALBRequestCountPerTarget (60s) Unexpected moderate spikes
Headroom EC2 ASG target capacity at 80% First wave of any spike
Downstream ECS Service Connect RequestCount metric Bottleneck shifting
Throttling API Gateway per-tenant rate limits via AWS Lambda authorizer Extreme surges (noisy neighbor)

Figure 2 illustrates how the five scaling layers work together with the EC2 warm pool. Pre-initialized warm pool instances launch in 30–60 seconds versus 5–7 minutes for an AWS Fargate cold start, at a cost of approximately $3/month per stopped instance.

Five-layer scaling strategy. Predictive scaling handles daily patterns, reactive scaling triggers warm pool activation, capacity headroom absorbs initial spikes, and per-tenant throttling protects system integrity

Figure 2: Five-layer scaling strategy

Cost and performance impact

In addition to improving scaling performance, the move from AWS Fargate to Amazon EC2 reduced the projected monthly cost of running 21 services in the Asia Pacific (Mumbai) AWS Region (ap-south-1). Two instruments do different jobs here: On-Demand Capacity Reservations is designed to make sure capacity is available in the Availability Zone when a surge activates the warm pool, and Compute Savings Plan supply the discount. The reservation carries no term commitment and can be resized or released as the fleet changes, so the following one-year and three-year terms apply to the Savings Plan alone.

Approach Monthly cost (21 services) versus AWS Fargate
AWS Fargate On-Demand (before) ~$1,568 Baseline
EC2 ASG with 1-year Compute Savings Plan and warm pool ~$832
  • ~47%
EC2 ASG with 3-year Compute Savings Plan with warm pool ~$568
  • ~64%

The reservation covers the steady-state fleet, while you stop warm pool instances, adding approximately $30 per month in Amazon EBS storage costs. Scaling time falls from 5–7 minutes to 30–60 seconds.

Solution 3: Crash observability pipeline

When containers crash in production, the support team needs three things: what happened, the root cause, and the container’s execution state at the time of failure. In the prior state, OrderServ used the awslogs driver to send container output to CloudWatch Logs. Although this captured normal application output, it did not provide durable task-stop metadata, cross-service request correlation or persistent crash artifacts.

  • Potential loss of final logs records during crash: When a task terminates abruptly, records still in the log driver’s delivery buffer might not reach Amazon CloudWatch Logs.
  • Short lived task metadata: Amazon ECS makes stopped-task information available through the API for a limited period of 1 hour, reducing the investigation window.
  • No cross-service correlation: Plain-text logs lacked trace IDs and consistent task, container, and service identifiers needed to follow a failed request across services.
  • No persistent crash artifacts: The ephemeral task storage of AWS Fargate disappeared when the task stopped, taking local crash dumps and diagnostic files with it.

The solution: FireLens sidecar + Amazon EventBridge persistence

Figure 3 shows the crash observability pipeline, where the application container emits structured JSON logs with X-Ray trace IDs to a FireLens (Fluent Bit) sidecar. The sidecar survives container termination and routes logs to CloudWatch Logs with Infrequent Access class and 30-day retention, as well as to Amazon Simple Storage Service (Amazon S3) with gzip partitioning and 365-day retention. Amazon EventBridge captures task-stop events with full metadata, while the AWS Distro for OpenTelemetry (ADOT) collector provides auto-instrumented traces to X-Ray and EFS persists crash dumps across availability zones, all at approximately $10/month per service.

Crash observability pipeline. FireLens operates at the container runtime level, surviving application container crashes. Amazon EventBridge persists task metadata for 90 days

Figure 3: Crash observability pipeline. FireLens operates at the container runtime level, surviving application container crashes. Amazon EventBridge persists task metadata for 90 days

FireLens (Fluent Bit) as a sidecar is the key architectural choice. ECS redirects the application container’s stdout at the container runtime level. If the app container receives an out-of-memory (OOM) kill, Fluent Bit still has the logs.

For hard crashes where application logs are not sufficient (stack overflow, native code fault), the .NET Core runtime automatically writes a mini dump to an Amazon EFS mount at /tmp/dumps/<container-id>. Because Amazon EFS writes are immediately durable and the mount survives container death, the process state (thread call stacks, heap objects, loaded modules) helps preserve diagnostic information after a SIGKILL. An AWS Lambda function copies dumps to S3 for analysis without VPC access. Cost: ~$0.02/month.

Amazon EventBridge to Amazon CloudWatch Logs persists the complete Amazon ECS task state change event with 90-day retention. No AWS Lambda function is required.

Every log line is structured JSON with the AWS X-Ray trace ID. The support workflow: search for an error, find the trace ID, open X-Ray, and see the full request flow across all 21 services.

We also use CloudWatch Logs Infrequent Access class with FireLens dual output (CloudWatch + S3), reducing observability costs by 62 percent per service.

Cost: approximately $10/month per service for the complete pipeline.

Solution 4: Passkeys for multi-tenant authentication

QSR mobile apps need instant login. Every second of friction is a lost order. The service previously relied on SMS one-time password (OTP) for every login, which at millions of monthly active users meant significant costs and user friction.

Passkeys with per-brand cryptographic isolation

We implemented passkeys (WebAuthn) using the native support in Amazon Cognito:

  • First-time registration: Phone number + OTP verifies identity, then the device registers a passkey bound to the Secure Enclave.
  • Every subsequent login: Face ID or fingerprint, which completes in about 2 seconds with no SMS cost.
  • New device or stale profile: Because carriers reassign phone numbers, possession of a number is treated as a claim rather than proof of identity. Registering a passkey on a new device repeats the OTP verification, and a profile whose last verification predates our revalidation window is re-verified before any registered credential is honoured.
  • Per-brand isolation: Each brand operates its own Cognito User Pool with a distinct Relying Party ID that is cryptographically isolated at the OS level.

A Post-Authentication Lambda (about 60 lines) maintains a customer authentication profile in DynamoDB, keyed by a hash of the brand/tenant id and the customer’s phone number. Because the brand ID is part of the key, the same customer registering with two brands produces two unrelated records, and the system cannot locate any record without the brand context. The profile stores only authentication state, such as which devices were registered and when each was last verified. It holds no order history, loyalty balance, or other brand-specific data. Passkey credentials are isolated by the same principle: because each brand uses a distinct Relying Party ID, the authentication flow prevents presenting a credential registered for one brand to another brand, and the device enforces that boundary. The implementation required about 95 lines of custom code to integrate with Amazon Cognito’s authentication logic.

Metric Before After
Cost per login ~$0.03 (SMS OTP) $0 (passkey)
Login latency 15–30 seconds ~2 seconds
SMS cost reduction — 80%+ at steady state
Phishing resistance None Cryptographic (device-bound)

Overall results

The architectural evolution delivered compounding savings:

Dimension Before After Improvement
Compute cost AWS Fargate On-Demand EC2 + Warm pools + ODCR + Compute Savings Plan 47–64% reduction
Scaling speed ~ 5-7 minutes 30–60 seconds 5–10× faster
Observability cost Standard CW Logs IA + FireLens + S3 62% reduction
Authentication cost SMS OTP every login Passkeys 80%+ reduction
Tenant onboarding Manual, multi-day Configuration-driven Days → minutes
Crash diagnosis “Exit Code: 1” Full trace + stack trace Blind → full visibility

For a service serving over 5 brands, the combined annual savings exceed 60 percent of the previous infrastructure cost, while simultaneously improving scaling speed, operational visibility, and user experience.

Conclusion

In this post, we showed how Cognizant OrderServ evolved from a single tenant microservices service to a multi-tenant SaaS service by making four architectural changes: shared compute with a tenancy library, Amazon EC2 warm pools for sub-minute scaling, a crash observability pipeline, and passkeys for frictionless authentication. The architecture addresses key failure modes through tenant validation, capacity headroom, per-tenant throttling and dedicated-cluster for high-volume brands. These capabilities introduce trade-offs, including greater application-level isolation, EC2 capacity management and compute Savings Plan commitments.

The key insight is that multi-tenancy does not require infrastructure isolation for every tenant. A well-designed tenancy library with strict resource-routing conventions provides application-level isolation while sharing compute, making the isolation model a deployment decision rather than an architectural constraint.

If you operate a multi-brand or multi-tenant service on Amazon ECS and face similar challenges with cost scaling or surge handling, consider these patterns:

  • Evaluate whether your isolation requirements truly need per-tenant infrastructure, or whether application-level isolation with per-tenant data stores is sufficient.
  • For workloads with unpredictable surges, compare the total cost of AWS Fargate over-provisioning against EC2 warm pools with compute Savings Plan.
  • Use FireLens sidecars for log delivery in any environment where container crashes are possible.

To learn more, explore these resources:


About the authors