Azure Flexible Servers For PostgreSQL Downscaling

Table of Contents

Azure Flexible Servers For PostgreSQL

Getting Utilization Data

I have Standard_D2s_v3 SKU. To make a downscaling decision I should find out my average flexible server utilization.

List all available metrics:

az monitor metrics list-definitions \
  --resource "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.DBforPostgreSQL/flexibleServers/<name>"

PT1H is an ISO 8601 duration format, used by Azure Monitor and many time-based APIs. It means: Period (P) of Time (T) = 1 Hour (1H). Supported ones are: PT1M,PT5M,PT15M,PT30M,PT1H,PT6H,PT12H,P1D.

Check CPU metrics:

az monitor metrics list \
  --resource "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.DBforPostgreSQL/flexibleServers/<name>" \
  --metric cpu_percent \
  --interval PT24H

CPU average: 39.73%. Azure recommends scaling up only when CPU > 70–80% sustained. My ~40% means the server is not overloaded, scaling down will likely reduce CPU to ~60–70%, still acceptable. I still have headroom.

Check MEM metrics:

az monitor metrics list \
  --resource "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.DBforPostgreSQL/flexibleServers/<name>" \
  --metric memory_percent \
  --interval PT24H

Memory average: 31%. Memory is the most important resource for Postgres query performance and caching. If memory is < 50%, I can downscale without major risk.

Check IO consumption metrics:

az monitor metrics list \
  --resource "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.DBforPostgreSQL/flexibleServers/<name>" \
  --metric io_consumption_percent \
  --interval PT24H

IOPS consumed: 6.67%. Azure warns only when IOPS > 70%. Your workload is barely touching the storage layer.

Check storage used bytes:

az monitor metrics list \
  --resource "/subscriptions/<SUB_ID>/resourceGroups/<RG_NAME>/providers/Microsoft.DBforPostgreSQL/flexibleServers/<SERVER_NAME>" \
  --metric storage_used

Convert to GiB:

8765863808 / 1024 / 1024 / 1024 ≈ 8.16 GiB

For this particular metrics I think it is possible to downscale.

Decision Making

Let's reason downscaling for Standard_D8s_v3 wich has 8 vCores 32 GiB RAM and ~12 800 max IOPS.

Lets assume my utilization is:

  • average CPU = 29.60 % and actually using 8 vCores * 29.6% ≈ 2.37 vCores.
  • average Memory = 51.83 % and real usage will be 32 GiB * 51.83% ≈ 16.6 GiB used.
  • disk_iops_consumed_percentage = 5.72 % this equals 12 800 * 5.7% ≈ 730 IOPS.
  • 10 503 191 002 bytes or ≈ 9.78 GiB storage used of 32 GiB.
MetricAverageReality
CPU16% of 8 cores≈ 1.3 cores actually used
Memory52% of 32 GB≈ 16.6 GB actually used
Disk IOPS5.8% of 12800≈ 750 IOPS
Storage10.5 GBVery small

Avg CPU, Avg MEM and IOPS utilization are very low for a production DB. So my production Postgres needs around: ~17 GiB RAM on average. 8 GiB would be too small for production. 16 GiB is borderline. 32 GiB has headroom. The perfect size for me is 16–24 GiB, but Azure doesn’t give 24 GiB option though.

So in real terms my workload is roughly:

  • Needs 1.5–2 vCores
  • Needs 18–20 GB RAM
  • Needs < 1000 IOPS
  • Needs ~15–20 GB disk
az postgres flexible-server list-skus \
  --location germanywestcentral \
  --output table

SKU comparison based on my load:

SKUvCoresRAMIOPSSuitabilitySavings
GP_Standard_D8s_v3 (current)832 GB12 800Overkill0%
GP_Standard_D4s_v3416 GB6400Borderline (RAM may be tight)~50%
GP_Standard_D2s_v328 GB3200Too small for RAMrisky
B_Standard_B2ms28 GBLowNot enough RAMnot recommended
GP_Standard_D4ads_v5416 GB6400Looks interesting~55–60% cheaper
GP_Standard_D8ads_v5832 GB12800Similar to nowlittle saving

Actually you can't downscale to SKU which has 50% RAM when you have average RAM utilization > 50% even if the difference is 1-2%. The better solution is to stay 32 GiB RAM (AZ has no 24 GiB RAM SKU for PostgreSQL) but switch to memory optimized SKU.

Standard_E4ads_v5 has 4 vCores, 32 GiB RAM. Much cheaper than D8. No memory risk. I would save ~50% of cost while staying safe on RAM. This changes general purpose SKU to memory optimized SKU.

Comparing GP_Standard_D4ads_v5 candidate with memory optimized Standard_E4ads_v5 candidate:

FeatureStandard_D8s_v3 (current)GP_Standard_D4ads_v5 (candidate)E4ads_v5 (candidate)
Azure tierGeneral PurposeGeneral Purpose (AMD)Memory Optimized (AMD)
vCores844
Memory32 GiB16 GiB32 GiB
Max IOPS12 800~6 400~6 400
Your avg CPU load~16% of 8 = 1.3 cores~33% of 4~33% of 4
Your avg RAM load~52% of 32 = 16.6 GiB~104% of 16 (tight)~52% of 32
Disk use10.5 GB10.5 GB10.5 GB
IOPS use~750~750~750
Headroom (CPU)Massive (unused)GoodGood
Headroom (RAM)Very goodAlmost noneVery good
Risk levelVery low / overprovisionedMedium / risky RAM-wiseLow
Typical monthly cost~650 - 750 €~280 - 350 €~450 - 550 €
Monthly savings-~350 - 450 €~150 - 300 €
Yearly savings-~4 200 - 5 400 €~1 800 - 3 600 €
My verdictOverkill / WasteAggressive / RiskyBest balance

I will downscale to Standard_E4ads_v5.

Backup

Check the on-demand backup using AZ CLI:

az postgres flexible-server show \
  --name pg-dev-server \
  --resource-group <RG> \
  --query "backup" \
  --output table

Create full logical backup using psql:

export PGPASSWORD='YOUR_PASSWORD'

pg_dump \
  -h pg-dev-server.postgres.database.azure.com \
  -U your_admin_user \
  -d database \
  -p 5432 \
  -Fc \
  -b \
  -v \
  > pg-devv-database-$(date +%F_%H-%M).sql

Downscaling - GP_Standard_D2s_v3 > B_Standard_B2s

HA is not supported for Burstable SKUs.

There WILL BE DOWNTIME at least 8-10 minutes! Azure PostgreSQL Flexible Server does NOT support zero-downtime compute scaling.

You can not reduce storage size.

Scale to 0 any production deployments which are writing to database:

kubectl scale deployment <deployment-name> --replicas=0 -n <namespace>

Change SKU using Terraform:

  #sku_name               = "GP_Standard_D2s_v3"
  sku_name               = "B_Standard_B2s"

  # high_availability {
  #   # mode = "ZoneRedundant"
  #   mode = "SameZone"
  #   standby_availability_zone = "1"
  # }

Enjoy your small burstable development PostgreSQL Flexible Server.

Downscaling - GP_Standard_D8s_v3 > Standard_E4ads_v5

Sometimes there is no such SKU in current region. I set Standard_E4s_v3 for Germany West Central region.

SKUvCoresRAMApproximate Hourly PriceApproximate Monthly Price
Standard_E4s_v3432 GiB~ USD $0.056/h = $0.304/h~$0.304 × 24 × 30 ~ $219
Standard_E4ads_v5432 GiB~ USD $0.262/h~$0.262 × 24 × 30 ~ $189 — older data

Newer AMD CPU has better efficiency - lower price.

Scale to 0 any production deployments which are writing to database:

kubectl scale deployment <deployment-name> --replicas=0 -n <namespace>

Change SKU using Terraform:

  #sku_name              = "GP_Standard_D8s_v3"
  sku_name               = "MO_Standard_E4s_v3"

This will take approx 15 minutes.

Diagnostics

Inspect application reaction using kubectl:

kubectl -n farel-stage logs -f -l app.kubernetes.io/instance=application-name

November 20, 2025