Design-Prinzipien – Performance Efficiency
Design-Prinzipien beschreiben konkrete technische Leitlinien für Architektur- und Implementierungsentscheidungen. Sie ergänzen die 7 Performance-Prinzipien um technische Spezifität.
DP1 – Trenne Workloads nach Lastprofil
Unterschiedliche Lastprofile erfordern unterschiedliche Skalierungsstrategien. Synchrone APIs, asynchrone Background-Jobs, Batch-Processing und Event-Driven-Workloads SOLLTEN architektonisch getrennt werden, um optimale Skalierungskonfigurationen pro Komponente zu ermöglichen.
Implikationen:
-
API-Server: Stateless, horizontal skalierbar, Auto-Scaling auf Request-Rate
-
Background-Jobs: Queue-basiert, skaliert auf Queue-Depth (SQS, Azure Service Bus, Pub/Sub)
-
Batch-Processing: Spot/Preemptible Instances, zeitgeplante Skalierung
-
Echtzeit-Pipelines: Streaming-Services (Kinesis, Event Hubs, Dataflow)
# Anti-Pattern: Alles auf einem EC2
resource "aws_instance" "monolith" {
instance_type = "m5.8xlarge" # Groß genug für alle Workloads?
}
# Besser: Getrennte Skalierungsdomänen
resource "aws_autoscaling_group" "api" { min_size = 2; max_size = 20 }
resource "aws_autoscaling_group" "workers" { min_size = 0; max_size = 50 }
# Workers skalieren auf SQS-Queue-Depth
DP2 – Externalise State Before Scaling
Vor der Implementierung von Auto-Scaling muss jeder in-process State externalisiert werden. Sessions, Caches, temporäre Dateien und Locks dürfen nicht lokal auf dem Instance gespeichert werden, da sie beim Scale-In verloren gehen und beim Scale-Out nicht verfügbar sind.
State-Externalisierung:
| State-Typ | Anti-Pattern | Korrekte Lösung |
|---|---|---|
HTTP-Session |
In-Memory Session Store (Standard in vielen Frameworks) |
Redis/ElastiCache (aws_elasticache_replication_group) |
Datei-Uploads |
Lokaler Dateisystem-Pfad |
S3/Azure Blob/GCS direkt vom Client oder Pre-Signed URLs |
Distributed Locks |
File-Lock oder In-Memory Mutex |
Redis SETNX, DynamoDB Conditional Writes |
Temporäre Berechnungsdaten |
Lokale Datei für Multi-Step-Prozesse |
S3 für Stage-Output, Queue für Stage-Koordination |
DP3 – Verwende latenzoptimiertes Routing
Netzwerklatenz addiert sich bei jedem Service-Hop. In einer Microservices-Architektur kann eine Anfrage 10+ interne Service-Calls auslösen. Jeder Hop muss latenzoptimiert sein.
Routing-Entscheidungen:
-
AZ-Affinity: Service-A auf AZ-a kommuniziert bevorzugt mit Service-B auf AZ-a
-
VPC Endpoints: Alle Cloud-Service-APIs (S3, DynamoDB, SSM, ECR) über Gateway/Interface-Endpoints
-
Service-Mesh: Für hochfrequente Service-zu-Service-Kommunikation (Istio, AWS App Mesh)
-
gRPC statt REST: Für interne APIs mit hohem Call-Volumen (binäres Protokoll, HTTP/2)
# VPC Endpoints für alle major AWS Services
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [aws_route_table.private.id]
}
resource "aws_vpc_endpoint" "ecr_api" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.api"
vpc_endpoint_type = "Interface"
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
}
DP4 – Konfiguriere Connection Pools explizit
Connection-Pool-Erschöpfung ist eine der häufigsten Ursachen für Performance-Degradation unter Last. Jeder Service, der eine Datenbank oder ein externes API aufruft, MUSS explizit konfigurierte Connection-Pool-Größen haben.
Pool-Sizing-Formel (Datenbankserver):
Optimale Pool-Größe = (CPU-Kerne * 2) + Anzahl aktiver Disks
Beispiel: 4-Core RDS + 2 Disks = 10 Connections pro Application-Instanz
Bei 10 App-Instanzen: max_connections_RDS = 100 (10 * 10)
# SQLAlchemy – explizite Pool-Konfiguration
engine = create_engine(
DATABASE_URL,
pool_size=10, # Immer offene Connections
max_overflow=5, # Temporäre Zusatz-Connections
pool_timeout=30, # Sekunden warten auf freie Connection
pool_recycle=3600, # Connection nach 1h erneuern
pool_pre_ping=True, # Verbindung testen vor Reuse
)
DP5 – Definiere SLOs vor dem ersten Deployment
SLOs MÜSSEN definiert werden, bevor ein Service produktiv geht – nicht danach. Ein Service ohne SLO hat kein objektives Kriterium für "gut genug Performance".
SLO-Template:
# docs/slos/payment-api.yml
service: "payment-api"
slos:
- name: "availability"
sli: "success_rate"
target: 99.9 # 99.9% of requests succeed
window: "30d"
- name: "latency_p95"
sli: "request_latency_p95"
target: 200 # P95 < 200ms
unit: "ms"
window: "30d"
- name: "latency_p99"
sli: "request_latency_p99"
target: 500 # P99 < 500ms
unit: "ms"
window: "30d"
error_budget:
period: "30d"
policy: "feature_freeze_on_exhaustion"
DP6 – Isoliere I/O-intensive Workloads
I/O-intensive Workloads (Datenbankzugriffe, Datei-I/O, externe API-Calls) MÜSSEN von CPU-intensiven Workloads isoliert werden. Async-I/O und non-blocking Patterns sind für I/O-bound Services wichtiger als horizontales Scaling allein.
I/O-Isolation-Patterns:
-
Async/Non-Blocking: asyncio (Python), async/await (Node.js, .NET), Reactive (Java)
-
Bulkhead Pattern: Separate Thread-Pools für interne und externe Calls
-
Circuit Breaker: Verhindern von Cascading Failures bei langsamen Downstream-Services
-
Timeout-Pyramid: Outer-Timeout > Inner-Timeout > DB-Timeout
# Bulkhead: Separate Executor für externe API-Calls
internal_executor = ThreadPoolExecutor(max_workers=50)
external_executor = ThreadPoolExecutor(max_workers=10) # Begrenzt externe Calls
async def get_payment(payment_id: str):
# Interne DB-Abfrage
db_result = await loop.run_in_executor(internal_executor, db_query, payment_id)
# Externe API-Call (begrenzt)
ext_result = await loop.run_in_executor(external_executor, external_api, payment_id)
return merge(db_result, ext_result)
DP7 – Performance-Validierung im CI/CD
Performance-Validierung ist ein First-Class-Citizen im Deployment-Prozess. Performance-Regressions sind genauso ernst zu nehmen wie Funktions-Bugs.
CI/CD-Performance-Gate-Aufbau:
# .github/workflows/deploy.yml
jobs:
performance-validation:
needs: [build, unit-test, integration-test]
runs-on: ubuntu-latest
steps:
- name: Run k6 Load Test
run: |
k6 run \
--vus 50 --duration 5m \
--env BASE_URL=${{ env.STAGING_URL }} \
--out json=results.json \
tests/performance/payment-api.js
- name: Check Acceptance Criteria
run: |
# P95 < 200ms, P99 < 500ms, Error Rate < 0.1%
python scripts/validate-perf-results.py results.json \
--p95-threshold 200 \
--p99-threshold 500 \
--error-rate-threshold 0.001
- name: Compare to Baseline
run: |
# Fail wenn P99 > 110% der letzten erfolgreichen Baseline
python scripts/compare-to-baseline.py results.json \
--regression-threshold 0.10
DP8 – Dokumentiere Performance-Entscheidungen in ADRs
Jede Architekturentscheidung mit Performance-Implikationen MUSS eine Performance-Sektion im zugehörigen Architecture Decision Record (ADR) enthalten.
ADR-Performance-Sektion-Template:
## Performance Impact
### Expected Throughput
- Design target: 1000 req/s at P95 < 200ms
- Load test result: 1200 req/s at P95 = 145ms ✅
### Scaling Strategy
- Auto-scaling: Target tracking on ALBRequestCountPerTarget
- Min instances: 2 (no cold start), Max instances: 20
### Performance Debt Created
- None identified at this time
### Performance Debt Accepted
- CDN not configured in Phase 1 (estimated +50ms for static assets)
- Registered in Performance Debt Register as PERF-DEBT-2026-003
- Target resolution: Q3 2026