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 equals12 800 * 5.7% ≈ 730 IOPS.- 10 503 191 002 bytes or ≈ 9.78 GiB storage used of 32 GiB.
| Metric | Average | Reality |
|---|---|---|
| CPU | 16% of 8 cores | ≈ 1.3 cores actually used |
| Memory | 52% of 32 GB | ≈ 16.6 GB actually used |
| Disk IOPS | 5.8% of 12800 | ≈ 750 IOPS |
| Storage | 10.5 GB | Very 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:
| SKU | vCores | RAM | IOPS | Suitability | Savings |
|---|---|---|---|---|---|
| GP_Standard_D8s_v3 (current) | 8 | 32 GB | 12 800 | Overkill | 0% |
| GP_Standard_D4s_v3 | 4 | 16 GB | 6400 | Borderline (RAM may be tight) | ~50% |
| GP_Standard_D2s_v3 | 2 | 8 GB | 3200 | Too small for RAM | risky |
| B_Standard_B2ms | 2 | 8 GB | Low | Not enough RAM | not recommended |
| GP_Standard_D4ads_v5 | 4 | 16 GB | 6400 | Looks interesting | ~55–60% cheaper |
| GP_Standard_D8ads_v5 | 8 | 32 GB | 12800 | Similar to now | little 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:
| Feature | Standard_D8s_v3 (current) | GP_Standard_D4ads_v5 (candidate) | E4ads_v5 (candidate) |
|---|---|---|---|
| Azure tier | General Purpose | General Purpose (AMD) | Memory Optimized (AMD) |
| vCores | 8 | 4 | 4 |
| Memory | 32 GiB | 16 GiB | 32 GiB |
| Max IOPS | 12 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 use | 10.5 GB | 10.5 GB | 10.5 GB |
| IOPS use | ~750 | ~750 | ~750 |
| Headroom (CPU) | Massive (unused) | Good | Good |
| Headroom (RAM) | Very good | Almost none | Very good |
| Risk level | Very low / overprovisioned | Medium / risky RAM-wise | Low |
| 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 verdict | Overkill / Waste | Aggressive / Risky | Best 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.
| SKU | vCores | RAM | Approximate Hourly Price | Approximate Monthly Price |
|---|---|---|---|---|
| Standard_E4s_v3 | 4 | 32 GiB | ~ USD $0.056/h = $0.304/h | ~$0.304 × 24 × 30 ~ $219 |
| Standard_E4ads_v5 | 4 | 32 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