Azure Monitoring & Scaling Guide
Azure Monitoring & Scaling Guide for Huitzo¶
Production-ready monitoring, alerting, and auto-scaling strategies for Huitzo deployments on Azure.
Part 1: Monitoring Architecture¶
Overview¶
┌─────────────────────────────────────────────────────────────┐
│ Data Collection Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ Application Metrics Container Metrics VM Metrics │
│ (Prometheus) (Docker stats) (Azure Monitor)│
│ • Request latency • CPU usage • CPU % │
│ • Error rate • Memory usage • Memory % │
│ • Queue depth • Disk I/O • Disk % │
│ • Command execution • Network I/O • Network │
│ • Cache hit rate • Container restarts • Load │
│ │ │
│ └────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Aggregation & Storage Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Azure Monitor │ │ Log Analytics │ │
│ │ (Metrics Storage) │ │ Workspace │ │
│ │ • Time-series DB │ │ • Kusto Query │ │
│ │ • 93 days retention │ │ • Full-text search │ │
│ │ • 1-min granularity │ │ • Custom dashboards │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ Visualization & Alerting Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Dashboards │ │ Alerts │ │ Logging │ │
│ │ • Real-time │ │ • Threshold │ │ • CloudWatch│ │
│ │ • Historical │ │ • Anomaly │ │ • App Logs │ │
│ │ • Custom KPIs │ │ • Composite │ │ • Audit │ │
│ └──────────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Part 2: Key Metrics¶
Application-Level Metrics¶
These are collected from the Huitzo backend using Prometheus.
Request Performance¶
# apps/backend/src/backend/observability/metrics.py
from prometheus_client import Counter, Histogram, Gauge
import time
# Latency (all requests)
http_request_duration = Histogram(
'http_request_duration_seconds',
'HTTP request latency in seconds',
buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 2.5, 5.0],
labelnames=['method', 'endpoint', 'status']
)
# Error rate
http_requests_total = Counter(
'http_requests_total',
'Total HTTP requests',
labelnames=['method', 'endpoint', 'status']
)
# Active requests
http_requests_in_progress = Gauge(
'http_requests_in_progress',
'HTTP requests in progress',
labelnames=['method', 'endpoint']
)
Thresholds & Alerts:
Critical (Page):
- p95 latency > 1000ms
- Error rate > 5% (50+ requests)
- p99 latency > 5000ms for >5 min
Warning (Email):
- p95 latency > 500ms
- Error rate > 2%
- p99 latency > 2000ms
Command Execution Metrics¶
# Command execution tracking
command_execution_duration = Histogram(
'command_execution_duration_seconds',
'Command execution time',
buckets=[0.1, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0, 300.0],
labelnames=['namespace', 'command', 'status', 'executor']
)
command_execution_total = Counter(
'command_executions_total',
'Total command executions',
labelnames=['namespace', 'command', 'status']
)
command_queue_depth = Gauge(
'command_queue_depth',
'Number of pending commands in queue',
labelnames=['queue_name'] # fast, medium, long
)
Thresholds:
Critical:
- Fast queue depth > 100 (backlog building)
- Command execution failure rate > 10%
- p95 execution time > 30s (for fast queue)
Warning:
- Medium queue depth > 50
- p95 execution time > 10s (for fast queue)
- Slow commands taking >5x expected time
Cache Performance¶
# Redis cache metrics
cache_hits = Counter(
'cache_hits_total',
'Cache hits',
labelnames=['key_prefix']
)
cache_misses = Counter(
'cache_misses_total',
'Cache misses',
labelnames=['key_prefix']
)
cache_hit_rate = Gauge(
'cache_hit_rate',
'Cache hit rate (0-1)',
labelnames=['key_prefix']
)
redis_memory_bytes = Gauge(
'redis_memory_bytes',
'Redis memory usage in bytes'
)
Thresholds:
Critical:
- Hit rate < 50% (indicates poor caching strategy)
- Redis memory > 90% (risk of eviction)
Warning:
- Hit rate < 70%
- Redis memory > 80%
Database Performance¶
# Database query metrics
db_query_duration = Histogram(
'db_query_duration_seconds',
'Database query duration',
buckets=[0.001, 0.01, 0.05, 0.1, 0.5, 1.0],
labelnames=['operation', 'table', 'status']
)
db_connections = Gauge(
'db_connections',
'Active database connections'
)
db_slow_queries = Counter(
'db_slow_queries_total',
'Queries exceeding 1 second',
labelnames=['operation', 'table']
)
Thresholds:
Critical:
- p95 query time > 500ms
- Slow queries > 5/min
- Connections > 80 (of 100 max)
Warning:
- p95 query time > 200ms
- Slow queries > 2/min
- Connections > 60
Infrastructure-Level Metrics¶
Collected by Azure Monitor from the VM and services.
VM Metrics¶
CPU Usage:
- Current: % of cores used
- Alert Critical: > 80% sustained (5+ min)
- Alert Warning: > 60% sustained (10+ min)
Memory Usage:
- Current: % of available
- Alert Critical: > 85% sustained
- Alert Warning: > 75% sustained
Disk Usage:
- Root partition: % used
- Data partition: % used
- Alert Critical: > 90%
- Alert Warning: > 80%
Network:
- Inbound bytes/sec
- Outbound bytes/sec
- Packet loss: should be 0%
- Comment: Alert only if sustained packet loss > 0%
PostgreSQL Metrics¶
Connection Count:
- Current active connections
- Alert Critical: > 90 (of 100 max)
- Alert Warning: > 70
Query Performance:
- Avg query time
- Max query time
- Alert Warning: p95 > 200ms
Replication Lag (if HA enabled later):
- Lag in seconds
- Alert Critical: > 30 seconds
- Alert Warning: > 10 seconds
Disk Usage:
- Storage percent used
- Alert Warning: > 80%
- Alert Critical: > 95%
IOPS and Throughput:
- Read IOPS
- Write IOPS
- Alert if sustained max (adjust based on SKU)
Redis Metrics¶
Memory Usage:
- Current bytes used
- % of max memory (alert if evicting)
- Alert Warning: > 80%
- Alert Critical: > 95% (risk of eviction)
Connection Count:
- Current connections
- Alert Critical: > 1000
- Alert Warning: > 500
Key Eviction Rate:
- Evicted keys/sec
- Alert Critical: > 0 (any eviction is bad)
- Alert Warning: > 1
Expiration:
- Items with TTL
- Items expired/sec (should be normal)
- Alert if expiration rate very high
Blob Storage Metrics¶
Ingress/Egress:
- Bytes written (upload)
- Bytes read (download)
- Alert if sustained max bandwidth
Request Count:
- Successful requests
- Failed requests (5xx errors)
- Alert Critical: > 1% error rate
Availability:
- Should be 99.9%+
- Alert Critical: < 99%
Latency:
- E2E latency (should be <100ms)
- Server latency (should be <50ms)
- Alert Warning: p95 > 100ms
Part 3: Azure Monitor Configuration¶
Setup Log Analytics Workspace¶
# Create Log Analytics workspace
az monitor log-analytics workspace create \
--name huitzo-logs \
--resource-group huitzo-prod-eastus-rg \
--location eastus
# Get workspace ID
WORKSPACE_ID=$(az monitor log-analytics workspace show \
--name huitzo-logs \
--resource-group huitzo-prod-eastus-rg \
--query id -o tsv)
# Get workspace key
WORKSPACE_KEY=$(az monitor log-analytics workspace get-shared-keys \
--name huitzo-logs \
--resource-group huitzo-prod-eastus-rg \
--query primarySharedKey -o tsv)
Enable VM Monitoring¶
# Install Azure Monitor Agent on VM
az vm extension set \
--resource-group huitzo-prod-eastus-rg \
--vm-name huitzo-app-prod \
--name AzureMonitorLinuxAgent \
--publisher Microsoft.Azure.Monitor \
--enable-auto-upgrade true
# Configure agent to collect metrics
az monitor data-collection rule create \
--name huitzo-dcr \
--resource-group huitzo-prod-eastus-rg \
--location eastus \
--rule-file- << 'EOF'
{
"kind": "Linux",
"properties": {
"dataSources": {
"performanceCounters": [
{
"name": "Unix Performance",
"counterSpecifiers": [
"\\Processor(_Total)\\% Processor Time",
"\\Memory\\% Used Memory",
"\\LogicalDisk(/)\\% Used Space"
],
"samplingFrequencyInSeconds": 60
}
],
"syslog": [
{
"name": "syslog",
"streams": ["Microsoft-Syslog"],
"facilityNames": ["*"],
"logLevels": ["Notice", "Warning", "Error", "Critical"]
}
]
},
"destinations": {
"logAnalytics": [
{
"name": "huitzo-logs",
"workspaceResourceId": "$WORKSPACE_ID"
}
]
},
"dataFlows": [
{
"streams": ["Microsoft-Syslog"],
"destinations": ["huitzo-logs"]
}
]
}
}
EOF
Create Dashboards¶
# Create custom dashboard for v0.0.0 deployment
az portal dashboard create \
--resource-group huitzo-prod-eastus-rg \
--name huitzo-dashboard \
--input-path - << 'EOF'
{
"properties": {
"lenses": {
"0": {
"order": 0,
"parts": {
"0": {
"position": {"x": 0, "y": 0, "colSpan": 4, "rowSpan": 3},
"metadata": {
"inputs": [{
"name": "ResourceId",
"value": "/subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/Microsoft.Compute/virtualMachines/huitzo-app-prod"
}],
"type": "Extension/Microsoft_Azure_Compute/PartType/VirtualMachinePart"
}
},
"1": {
"position": {"x": 4, "y": 0, "colSpan": 4, "rowSpan": 3},
"metadata": {
"inputs": [{
"name": "ResourceId",
"value": "/subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/huitzo-db-prod"
}],
"type": "Extension/Microsoft_Azure_OSS_Databases/PartType/PostgresqlServerPart"
}
},
"2": {
"position": {"x": 8, "y": 0, "colSpan": 4, "rowSpan": 3},
"metadata": {
"inputs": [{
"name": "ResourceId",
"value": "/subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/Microsoft.Cache/redis/huitzo-cache-prod"
}],
"type": "Extension/Microsoft_Azure_Cache/PartType/RedisCachePart"
}
}
}
}
}
}
}
EOF
Create Alert Rules¶
Critical: High Error Rate¶
az monitor metrics alert create \
--name huitzo-high-error-rate \
--resource-group huitzo-prod-eastus-rg \
--scopes /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/Microsoft.Compute/virtualMachines/huitzo-app-prod \
--condition "avg ErrorRate > 5" \
--evaluation-frequency 1m \
--window-size 5m \
--action /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/microsoft.insights/actionGroups/huitzo-alerts \
--severity 1 \
--description "Alert when error rate exceeds 5%"
Critical: High CPU Usage¶
az monitor metrics alert create \
--name huitzo-high-cpu \
--resource-group huitzo-prod-eastus-rg \
--scopes /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/Microsoft.Compute/virtualMachines/huitzo-app-prod \
--condition "avg Percentage CPU > 80" \
--evaluation-frequency 1m \
--window-size 5m \
--action /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/microsoft.insights/actionGroups/huitzo-alerts \
--severity 2 \
--description "Alert when CPU exceeds 80% for 5+ minutes"
Warning: High Memory Usage¶
az monitor metrics alert create \
--name huitzo-high-memory \
--resource-group huitzo-prod-eastus-rg \
--scopes /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/Microsoft.Compute/virtualMachines/huitzo-app-prod \
--condition "avg Available Memory < 20" \
--evaluation-frequency 5m \
--window-size 10m \
--action /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/microsoft.insights/actionGroups/huitzo-alerts \
--severity 2 \
--description "Alert when available memory < 20%"
Critical: Database Connection Pool Exhausted¶
az monitor metrics alert create \
--name huitzo-db-connections-high \
--resource-group huitzo-prod-eastus-rg \
--scopes /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/huitzo-db-prod \
--condition "avg active_connections > 90" \
--evaluation-frequency 1m \
--window-size 5m \
--action /subscriptions/{subscriptionId}/resourceGroups/huitzo-prod-eastus-rg/providers/microsoft.insights/actionGroups/huitzo-alerts \
--severity 1 \
--description "Alert when active database connections exceed 90"
Critical: Slow Queries¶
# Setup via Log Analytics query
az monitor log-analytics workspace query \
--workspace-name huitzo-logs \
--resource-group huitzo-prod-eastus-rg \
--analytics-query "
AzureDiagnostics
| where ResourceProvider == \"MICROSOFT.DBFORPOSTGRESQL\"
| where executionTime_d > 1000
| summarize Count=count() by bin(TimeGenerated, 5m)
| where Count > 5
"
Part 4: Scaling Strategy¶
Phase 1: Single-Instance (v0.0.0)¶
When to Scale to Phase 2: - CPU consistently > 70% - Memory > 75% - Database connections > 80 of 100 - Command queue depth regularly > 50 - Response time p95 > 500ms
# Monitor command queue depth
docker compose exec worker celery -A backend.celery inspect active_queues
Phase 2: Multi-Instance Load Balancing (v0.1+)¶
Architecture:
┌──────────────────────────────┐
│ Azure LoadBalancer │
│ (or Application Gateway) │
└──────────────────────────────┘
↓
┌──────────────────────────────┐
│ VM Scale Set (3x) │
├──────────────────────────────┤
│ Instance 1: Backend + Worker │
│ Instance 2: Backend + Worker │
│ Instance 3: Backend + Worker │
└──────────────────────────────┘
↓ (shared)
┌──────────────────────────┐
│ PostgreSQL (Standard) │
│ Redis (Premium) │
│ Blob Storage │
└──────────────────────────┘
Scaling Rules (Auto-Scale Policy)¶
# Create scale set from single VM
# (Migrate existing data first via backup/restore)
# Configure auto-scale based on CPU
az monitor autoscale create \
--resource-group huitzo-prod-eastus-rg \
--resource-type Microsoft.Compute/virtualMachineScaleSets \
--resource-name huitzo-vmss \
--min-count 2 \
--max-count 5 \
--count 2
# Scale-out rule: If CPU > 70% for 5 minutes
az monitor autoscale rule create \
--resource-group huitzo-prod-eastus-rg \
--autoscale-name huitzo-autoscale \
--condition "avg Percentage CPU > 70 during 5m" \
--scale action increase percentage 50%
# Scale-in rule: If CPU < 30% for 10 minutes
az monitor autoscale rule create \
--resource-group huitzo-prod-eastus-rg \
--autoscale-name huitzo-autoscale \
--condition "avg Percentage CPU < 30 during 10m" \
--scale action decrease count 1
Considerations: - Minimum 2 instances for HA (but cost increases 2x) - Deploy in different availability zones if possible - Share PostgreSQL and Redis (single shared instance) - Session affinity recommended (sticky sessions)
Database Scaling¶
# Upgrade PostgreSQL from B4ms to D4s (scale up)
az postgres flexible-server update \
--name huitzo-db-prod \
--resource-group huitzo-prod-eastus-rg \
--sku-name Standard_D4s \
--tier GeneralPurpose
# This causes brief downtime (~2-3 minutes)
# Plan for evening or maintenance window
Upgrade Path: - v0.0.0: Standard_B4ms (burstable, ok for single customer) - v0.1+: Standard_D4s (general-purpose, multiple customers) - v2.0+: Standard_D8s with read replicas (enterprise scale)
Redis Scaling¶
# Upgrade from Standard (2GB) to Premium
az redis update \
--name huitzo-cache-prod \
--resource-group huitzo-prod-eastus-rg \
--sku Premium \
--capacity 1
# Or increase capacity within tier
az redis update \
--name huitzo-cache-prod \
--resource-group huitzo-prod-eastus-rg \
--size 6gb # Upgrade to 6GB (if Standard too slow)
Part 5: Operational Dashboards¶
Real-Time Operations Dashboard¶
// Save in Azure Monitor: Queries > "Huitzo Operations"
// Request latency trend
AzureDiagnostics
| where ResourceProvider == "Microsoft.Compute"
| where TimeGenerated > ago(1h)
| summarize AvgLatency=avg(executionTime_d), MaxLatency=max(executionTime_d), P95=percentile(executionTime_d, 95)
| by bin(TimeGenerated, 5m)
// Error rate
StdMetrics
| where MetricName == "HttpRequestDuration"
| where status_code >= 500
| summarize ErrorCount=count()
| by bin(TimeGenerated, 1m)
Daily Health Report¶
Create automated report that runs at 9 AM daily:
# Schedule: Create Logic App for daily digest
# Sends email to ops team with:
# - Previous 24h uptime %
# - Peak CPU/Memory
# - Command execution success rate
# - Top errors
# - Database size growth
# - Storage usage trend
Part 6: Incident Response Playbooks¶
High Error Rate (>5%)¶
Diagnosis:
# SSH into VM
docker compose logs --tail 100 backend | grep -i error
# Query slow requests
curl http://localhost:8000/metrics | grep http_
# Check database
docker compose exec backend psql -c "select count(*) from command_execution where status='failed' and created_at > now() - interval '5 min';"
Response: 1. Page on-call engineer immediately 2. Check application logs for common error pattern 3. If database issue: Check connection count 4. If queue issue: Increase worker instances or reset queue 5. If external API issue: Check integration logs 6. If unknown: Roll back to last known good version
High CPU (>80%)¶
Diagnosis:
# Check resource usage
docker stats
# Check top CPU-consuming process
top -b -n 1 | head -15
# Check Celery worker load
docker compose exec worker celery -A backend.celery inspect stats | grep pool
Response: 1. Check if legitimate load increase (high traffic) 2. If yes: Scale up VM size or add worker replicas 3. If no: Profile code for inefficiency 4. Identify slow queries: Enable slow query log
Database Connection Exhaustion¶
Diagnosis:
# Check active connections
docker compose exec backend psql -c "select count(*) from pg_stat_activity;"
# Check connection by client
docker compose exec backend psql -c "select client_addr, count(*) from pg_stat_activity group by client_addr;"
Response: 1. Increase connection pool size in application config 2. Identify connection leak: Are idle connections being released? 3. Scale up database if baseline connection count growing 4. Consider connection pooler (PgBouncer) if many connection clients
Part 7: Capacity Planning¶
Baseline v0.0.0 (1 Customer)¶
Peak Load: 100 concurrent users
- API Latency p95: ~200ms
- Database connections: 20-30 active
- Redis hit rate: ~85%
- CPU usage: 15-25%
- Memory usage: 40-50%
- Disk I/O: Minimal
Monthly Growth:
- Users: +50% (100 → 150 → 225)
- API latency stable (load distributed to workers)
- Database size: +500MB (archive old command logs weekly)
- Storage usage: +100MB (file uploads)
Upgrade Triggers¶
| Metric | Current | Upgrade |
|---|---|---|
| Concurrent Users | 100 | 200+ → Larger VM |
| DB Connections | 30 | 80+ → Scale up DB |
| Redis Memory | 512MB | 1500MB+ → Upgrade Redis |
| Disk Usage | 50GB | 150GB+ → Larger disk |
| P95 Latency | 200ms | 500ms+ → Add worker |
| CPU Average | 25% | 70%+ → Scale |
Part 8: Monitoring Checklist¶
Daily¶
- Check Azure Monitor dashboard
- Review error logs (should be minimal)
- Verify daily backup completed
Weekly¶
- Review scaling metrics and trends
- Check disk usage and cleanup if needed
- Test at least one alert rule
- Review slow queries and optimize if needed
Monthly¶
- Full disaster recovery test (restore from backup)
- Capacity planning review
- Cost analysis (Azure Cost Management)
- Security audit (NSG rules, access logs)
Related Documentation¶
Version History¶
| Date | Version | Changes |
|---|---|---|
| 2026-02-11 | 1.0.0 | Initial monitoring and scaling guide |