Table of Contents

Sizing Guideline

This guide provides recommendations for sizing an XMPro deployment on Azure Kubernetes Service (AKS): how large to make the cluster's node pools and how to size the databases. The AWS Kubernetes (EKS) 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 Pool Sizing

On Kubernetes, XMPro sizing means two things: making sure the cluster's node pools have enough capacity for the product containers, and sizing the databases (covered in Database Sizing below). The example AKS Terraform module ships with evaluation-grade defaults (a single small system node, a single small application node, and a small shared 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 pools 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. AKS 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 pools

An AKS deployment runs two node pools: a system pool that hosts cluster-critical add-ons (CoreDNS, metrics-server, the AKS management components) and the platform controllers (Envoy Gateway, cert-manager, External Secrets Operator, Istio), and an application pool that hosts the XMPro product pods. Sizing the application pool 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 pool Purpose Default VM SKU Default count
system Add-ons, cluster controllers Standard_B2s_v2 (2 vCPU, 8 GiB) 1
app XMPro product pods Standard_B2s_v2 (2 vCPU, 8 GiB) 1
Note

B- and D-class VM SKUs are for test, development, or evaluation only. The Standard_B2s_v2 default, or a Standard_D-series SKU, works for a test or evaluation cluster at lower cost. B-series banks CPU credits when idle and throttles when the credits run out, so do not run a production site on it.

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 AKS the default in-cluster Stream Host (SH) is on (enable_default_stream_host = true); further 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 pool must have room to schedule them alongside the products during the initial deploy.

Translating requests into node size and count

To choose app_node_vm_size 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 VM SKU in the Azure VM sizes documentation, then subtract the AKS 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 pool 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 pool. 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 pool'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 pool has redundancy. For production, run at least two system nodes (so the add-ons and controllers survive a node loss) and size the application pool to your measured load, adding capacity for each additional site on the cluster.

Node pool knobs

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

Variable Default Controls
system_node_vm_size Standard_B2s_v2 VM SKU for the system pool (B/D-class for test/dev/evaluation only)
system_node_count 1 System pool node count (2+ recommended for add-on redundancy)
app_node_vm_size Standard_B2s_v2 VM SKU for the application pool (B/D-class for test/dev/evaluation only)
app_node_count 1 Application pool node count (fixed size when autoscaling is off)
kubernetes_version 1.34 Kubernetes version of the cluster
aks_sku_tier Free AKS control-plane tier (Premium required for LTS K8s versions)

The root cluster module (_infra-k8s) also exposes app_autoscaling_enabled (default false) with app_min_count / app_max_count bounds for the AKS cluster autoscaler; the examples/layered/infra-k8s example does not pass these through, so add them to your own copy if you want the pool to autoscale.

To resize an application pool that already exists, change app_node_count and re-apply the cluster layer, or scale it directly:

az aks nodepool scale --resource-group <cluster-rg> --cluster-name <cluster> --name app --node-count 2

AI and RAG node pool

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 pool 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 pool rather than sharing the application nodes.

Variable Default Controls
ai_node_vm_size Standard_E4as_v5 VM SKU for the dedicated AI pool
ai_node_count 1 AI pool node count

AI persistent volumes use the managed-csi-premium storage class by default (storage_class_name). Size this pool 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 Azure Cache for Redis and outputs the connection string) 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 (for example the AKS built-in azurefile-csi) so they share their Data Protection key ring.

At the cluster level, the root module's app_autoscaling_enabled turns on the AKS cluster autoscaler to add and remove application nodes automatically within the app_min_count / app_max_count bounds (see Node pool knobs). 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

  • B- and D-class VM SKUs are for test, development, or evaluation only. For production use a memory-optimized Standard_E-series SKU sized from measured load.
  • One node per pool is no redundancy. Raise system_node_count to at least 2 and size app_node_count (or the autoscaler bounds) so a node loss does not take the site down. Spread the pools across availability zones in regions that support them.
  • aks_sku_tier = "Free" has no uptime SLA. Use Standard for production, or Premium if you need a long-term support Kubernetes version.

Database Sizing

XMPro uses Azure SQL Database. Each site gets its own SM, AD, and DS databases (plus an AI database when the AI product is enabled), on a SQL logical server created by the site infrastructure layer.

Variable Default Notes
db_use_elastic_pool true Place the SM, AD and DS databases in one shared Standard elastic pool
db_elastic_pool_edtu 50 Capacity of that pool (eDTU)
db_sku_name S0 Per-database tier when db_use_elastic_pool = false
db_max_size_gb 2 Storage cap per database

The following per-database sizing estimates use the Standard service tier and the DTU-based purchasing model (a blended measure of compute, storage, and IO resources).

Deployment size Azure SQL Database
(each of SM, AD, DS)
1
Small Standard – 20 DTUs
Medium Standard – 50 DTUs
Large Standard – 100 DTUs
Note

1 High volumes of recommendations may require additional compute and storage.

For production you have two levers, both in the site infrastructure layer (infra): keep the elastic pool and raise db_elastic_pool_edtu so the shared budget matches your workload, or set db_use_elastic_pool = false and give each database its own tier through db_sku_name when you want a guaranteed, isolated allocation rather than a shared burst budget. Raise db_max_size_gb for production data volumes either way.

MAGS Sizing

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

Additional Resources


Last modified: September 28, 2026