Table of Contents

Sizing Guideline

This guide provides recommendations for sizing an XMPro deployment on Amazon Elastic Kubernetes Service (EKS): how large to make the cluster's node groups and how to size the databases. The Azure Kubernetes (AKS) Sizing Guideline follows the same structure; only the cloud-specific offerings differ.

Overview

Small, medium, and large sizing estimates are provided. The small option starts with the minimum recommended resources and, generally, each subsequent size doubles the number of CPU cores and available RAM. Not all components experience the same increase in load, so the estimates may not increase at the same rate for all components.

Many factors influence the number of Apps and Data Streams a deployment can effectively run. These factors include:

  • the number of data streams,
  • how frequently the streams process data,
  • the size of the data payload,
  • the number of recommendations to be monitored,
  • the number of apps and event boards being served,
  • the complexity of apps and event boards (the number of elements and integration points),
  • and the number of concurrent users accessing the apps and event boards.

As a rough guide, an example workload for a Medium-sized deployment would be:

  • ~200 Data Streams running across ~15 Stream Hosts,
  • serving data and triggering recommendations for ~10 Apps

Node Group Sizing

On Kubernetes, XMPro sizing means two things: making sure the cluster's node groups have enough capacity for the product containers, and sizing the databases (covered in Database Sizing below). The example EKS Terraform module ships with evaluation-grade defaults (a single small system node, a single small application node, and a small database), sized for demos, trials, and development, not production load. The variables below are how you scale up from there.

Two Kubernetes terms are used throughout this section:

  • A request is the amount of CPU or memory the Kubernetes scheduler reserves for a container. A node must have at least this much unreserved capacity free before a pod can be placed on it.
  • A limit is the ceiling a container may consume. A container that exceeds its memory limit is terminated (OOM-killed); the XMPro chart deliberately sets memory limits but no CPU limits, so .NET startup is not CPU-throttled.

Requests drive scheduling (can the pod fit?); limits drive worst-case sizing (how much can everything consume at once?). The node groups need capacity for both.

Note

Node capacity is smaller than the advertised size. A node advertised as 8 GiB does not offer a full 8 GiB to workloads. EKS reserves memory and CPU for the kubelet and system processes, and what remains is the node's allocatable capacity. Budget roughly 85-90% of the advertised figure as usable.

Node groups

An EKS deployment runs two managed node groups: a system group that hosts cluster-critical add-ons (CoreDNS, kube-proxy, the VPC CNI) and the platform controllers (Envoy Gateway, cert-manager, External Secrets Operator, AWS Load Balancer Controller, Istio), and an application group that hosts the XMPro product pods. Sizing the application group means giving its nodes enough allocatable capacity for the sum of the product container requests, with headroom for the products to grow toward their limits under load.

Node group Purpose Default instance type Default count
system Add-ons, cluster controllers m6i.large (2 vCPU, 8 GiB) 1
app XMPro product pods m6i.large (2 vCPU, 8 GiB) 1 (min 1, max 3)
Note

t-class instances are for test, development, or evaluation only. Burstable t3 instances (t3.medium for the system group, t3.large for the application group) work for a test or evaluation cluster at lower cost. They bank CPU credits when idle and throttle when the credits run out, so do not run a production site on them.

Per-product resource requests and limits

The table below lists the CPU and memory each XMPro product container reserves (request) and its memory ceiling (limit), taken from the Helm chart defaults. There are no CPU limits by design. These are the same chart resources regardless of whether you run on AKS or EKS. Subscription Manager (SM), Identity Server (IS), App Designer (AD), and Data Stream Designer (DS) are always deployed. On EKS the default in-cluster Stream Host (SH) is off (enable_default_stream_host = false); Stream Hosts are deployed per Collection through the stream-hosts layer, which uses the streamhost chart. Its defaults are higher than the default Stream Host's and include a 500m CPU limit. The AI product is optional.

Product CPU request Memory request Memory limit
Subscription Manager (SM) 100m 512Mi 1Gi
Identity Server (IS) 100m 1Gi 2Gi
App Designer (AD) 150m 1Gi 4Gi
Data Stream Designer (DS) 150m 2Gi 4Gi
Stream Host (SH), default 25m 256Mi 512Mi
Stream Host (SH), layer 4 100m 256Mi 1Gi
AI 25m 384Mi 768Mi

The standard product set (SM, IS, AD, DS) requests 500m CPU and 4.5Gi of memory, with a combined memory limit of 11Gi. The default Stream Host adds 25m / 256Mi (limit 512Mi), and each layer 4 Stream Host adds 100m / 256Mi (limits 500m / 1Gi). AI adds 25m / 384Mi (limit 768Mi).

Note

Add the Istio sidecar to every pod. enable_istio defaults to true, so every product pod and every Job pod gets an istio-proxy sidecar that requests a further 25m CPU and 128Mi (the cluster layer sets the proxy CPU request to 25m; memory is the Istio default). Istio runs the proxy as a native sidecar, so Kubernetes adds its request to the pod's own rather than absorbing it. For the standard product set that is another 100m / 512Mi, taking the steady-state request to 600m CPU and 5Gi; the default Stream Host adds 50m / 384Mi with its sidecar, each layer 4 Stream Host 125m / 384Mi, and AI 50m / 512Mi. Setting enable_istio = false in the application layer removes this overhead.

Note

Jobs run during install. Database-migration and licensing steps (ad-dbmigrate, ds-dbmigrate, sm-dbmigrate, ai-dbmigrate, licenses) run as short-lived Helm Jobs during install, each requesting 100m CPU / 256Mi (plus a sidecar) and capped at 200m CPU / 512Mi. They finish before the products reach steady state, so they do not count toward the always-on total above, but the application group must have room to schedule them alongside the products during the initial deploy.

Translating requests into node size and count

To choose app_node_instance_type and app_node_count:

  1. Start from the request total above (~600m CPU, ~5Gi memory for the standard product set with sidecars; add each additional Stream Host and AI as enabled).
  2. Look up the vCPU and RAM of a candidate instance type in the Amazon EC2 instance types documentation, then subtract the kubelet and system reservation to get its allocatable capacity.
  3. Size for the install peak, not the steady state. During an install the database-migration and licensing Jobs run concurrently with the products, each with its own sidecar. The Helm release is atomic: if one pod cannot be scheduled, the whole install rolls back after the 30-minute timeout, leaving nothing to inspect. Make sure the group can hold the products and the Jobs at the same time.
  4. Because the product memory limits (~11Gi combined) are far higher than the memory requests, memory pressure under real load, not the request total, is what sizes a production group. Size the application nodes toward the limit envelope, and confirm the true figure by measuring actual pod memory usage under representative load rather than trusting a static estimate.
  5. Set app_node_count so the group's combined allocatable capacity comfortably exceeds the workload, with headroom for node maintenance and pod rescheduling. Memory runs out before CPU does; if you are deciding what to grow, grow memory.

The defaults are a single application node and a single system node, which is why they are evaluation-grade: neither group has redundancy. For production, run at least two system nodes (so the add-ons and controllers survive a node loss) and size the application group to your measured load, adding capacity for each additional site on the cluster.

Node group knobs

All node group sizing lives in the EKS module's cluster layer (infra-k8s, deployed once per cluster). The table introduces each variable and its default.

Variable Default Controls
system_node_instance_type m6i.large Instance type for the system group (t3-class for test/dev/evaluation only)
system_node_count 1 System group node count (2+ recommended for redundancy)
app_node_instance_type m6i.large Instance type for the application group (t3-class for test/dev/evaluation only)
app_node_count 1 Desired application node count
app_node_min_count 1 Lower bound for the application group
app_node_max_count 3 Upper bound for the application group
node_az_count 1 How many AZs the node groups span
gateway_eip_az_count 0 Public AZs the gateway NLB spans (0 = match node_az_count)
single_nat_gateway true One shared NAT gateway instead of one per AZ
kubernetes_version 1.34 Kubernetes version of the cluster

To resize an application group that already exists, change its scaling configuration directly and set maxSize in the same command when the new desired size would exceed the current upper bound:

aws eks list-nodegroups --cluster-name <cluster>

aws eks update-nodegroup-config --cluster-name <cluster> --nodegroup-name <ng> \
  --scaling-config minSize=1,maxSize=3,desiredSize=2

AI and RAG node group

When the AI product's RAG feature is enabled, its backing services (Milvus vector store, Neo4j graph database, TimescaleDB, and the Mosquitto MQTT broker) run on a dedicated node group provisioned by the optional AI infrastructure layer (infra-ai), applied once per cluster and shared by all sites. These services are memory-heavy, which is why they get their own group rather than sharing the application nodes.

Variable Default Controls
ai_node_instance_type r6i.xlarge Instance type for the dedicated AI group
ai_node_count 1 AI group desired node count
ai_node_min_count 1 Lower bound for the AI group
ai_node_max_count 2 Upper bound for the AI group
ai_node_disk_size 128 Node root disk (GiB)

AI persistent volumes use the gp3 storage class by default (storage_class_name). Size this group against the data volume you intend to embed and index; the single-node default is an evaluation starting point, not a production figure.

Scaling out

Beyond making nodes bigger, XMPro can scale horizontally by running more replicas of a product, but this requires shared-state coordination. The application layer (app) exposes enable_autoscale (default false), which turns on the Redis backplane (SignalR and distributed cache) so multiple replicas can coordinate. Redis can come from the site infrastructure layer (create_redis_cache = true provisions ElastiCache and outputs the connection string as a Secrets Manager secret) or from an existing Redis you supply through redis_connection_string. Per-product replica counts (ad_replica_count, ds_replica_count, sm_replica_count, is_replica_count, ai_replica_count) default to 1, and enforce_single_replica (default false) hard-blocks any product from exceeding one replica when set. Products running more than one replica also need the *_persistence blocks enabled on an RWX-capable storage class (the cluster layer's enable_efs provisions efs-sc for this) so they share their Data Protection key ring.

At the cluster level, app_node_min_count and app_node_max_count bound the application group; a cluster autoscaler, if you install one, moves the desired size within those bounds. Per-product Horizontal Pod Autoscalers (HPAs) are still a chart follow-up, so horizontal scaling today means setting replica counts explicitly rather than relying on CPU-driven autoscaling of the pods themselves.

Production notes

  • t-class instances are for test, development, or evaluation only. For production use general-purpose (m6i, m7i) or memory-optimized (r6i, r7i) classes sized from measured load.
  • node_az_count = 1 is single-AZ. The default keeps cost down by confining nodes to one availability zone, which means an AZ failure takes the site down. Raise it for real availability, and raise gateway_eip_az_count with it so the gateway NLB spans the same AZs.
  • AZ width alone does not spread the workload. node_az_count only widens where nodes may be placed. With the defaults of system_node_count = 1 and app_node_count = 1 there is still exactly one node of each type, and single-replica products still all land in one AZ. Raise system_node_count to at least 2 and app_node_count to at least one node per AZ you want covered.
  • single_nat_gateway = true is a single point of failure and a cost saving. Set it false for production.

Database Sizing

XMPro uses Amazon RDS for SQL Server. The site infrastructure layer creates one RDS instance per site, holding the SM, AD, and DS databases (plus an AI database when the AI product is enabled).

Variable Default Notes
db_engine sqlserver-ex SQL Server Express
db_engine_version 15.00 SQL Server 2019
db_instance_class db.t3.small 2 vCPU, 2 GiB (db.t3-class for test/dev/evaluation only)
db_allocated_storage 20 GiB
rds_backup_retention_period 1 Days
rds_deletion_protection false Dev default - enable for production
rds_skip_final_snapshot true Dev default - disable for production
Warning

SQL Server Express has a hard 10 GB per-database cap and is limited to roughly 1.4 GB of RAM and 4 cores regardless of the instance class you pick. It is suitable for evaluation only.

For production, set db_engine to sqlserver-se (Standard) or higher, set db_instance_class to a db.m6i or db.r6i class sized for your workload (db.t3 is test, development, or evaluation only), raise db_allocated_storage and rds_backup_retention_period to your data volume and retention policy, and set rds_deletion_protection = true and rds_skip_final_snapshot = false. High volumes of recommendations may require additional compute and storage.

MAGS Sizing

MAGS (Multi-Agent Generative Systems) is sized separately, across its database, application and GPU knowledge tiers. The node group figures on this page do not include it.

Additional Resources


Last modified: September 28, 2026