ASP.NET Core service to pressure-test Traefik, EKS, and SQL Server (RDS SQL Web). Supports HTTP and gRPC writes with random/HL7/passthrough payloads (default 1 KB–50 MB), optional SQL disable, ring-based aggregation, OTLP telemetry (service.name = sql-stress), and a separate SQL read dashboard. Kestrel/gRPC limits are raised to 60 MB to allow 50 MB payloads.
- Three roles via
APP_ROLE:ingester: handles/writeand gRPC writes; can disable SQL to isolate LB/app behavior.aggregator: ring registry +/dashboard+/statsonly (no SQL writes/reads).messages: SQL reader only; serves/messagesJSON and/sqlreadmanual dashboard.
- HTTP
/writeand gRPCstress.StressTest/Writeinsert random/HL7/passthrough payloads (or simulate when SQL is disabled). - Auto-creates table
stress_writeson startup if missing. - Dashboard
/dashboard+ JSON/statsshows per-second totals, per-pod stats (HTTP/gRPC splits, SQL disabled flag), per-IP metrics; aggregator pod keeps a ring of active pods even when idle. - SQL read dashboard
/sqlread(messages role) has buttons to fetch:- Last 10 rows (size + per-row fetch ms, no payload),
- Last 5 rows (full payload, truncated to 5 MB per row to avoid OOM).
- OTLP tracing/metrics (AspNetCore, HttpClient, SqlClient + custom meters
sqlstress.http.*,sqlstress.grpc.*),service.name = sql-stress. - Ports: 8080 HTTP (write/status/health/dashboards/stats/messages/sqlread), 8081 gRPC (h2c). Traefik IngressRoute splits HTTP vs gRPC and buffers to 64 MB.
Required DB:
SQL_SERVER– RDS endpoint.SQL_DATABASESQL_USERSQL_PASSWORD
Optional DB/tuning (defaults in parentheses):
SQL_PORT(1433)SQL_TABLE(stress_writes)SQL_ENCRYPT(true)SQL_TRUST_SERVER_CERT(false)SQL_TIMEOUT_SECONDS(60) – command timeoutSQL_CONNECT_TIMEOUT_SECONDS(30)SQL_MAX_POOL_SIZE(200)SQL_MIN_POOL_SIZE(10)MAX_INFLIGHT_SQL(0 = unlimited) – cap concurrent SQL writes per pod; use 8–32 to protect RDS
Payload/behavior:
PAYLOAD_MIN_BYTES(1024)PAYLOAD_MAX_BYTES(52428800) – 50 MBDISABLE_SQL(false) – iftrue, skip DB writes but still count trafficSQL_INSERT_MODE(random) –randomgenerates payload;bodyuses HTTP POST body as payload (for full end-to-end tests);hl7generates synthetic HL7 textAPP_ROLE(ingester) –ingester|aggregator|messages
Ports and identity:
APP_PORT(8080) – HTTPAPP_GRPC_PORT(8081) – gRPCPOD_NAME(defaults to hostname)POD_NAMESPACE– required for peer discovery when using K8s
Ring/peer aggregation:
RING_ENDPOINT– URL to post heartbeat snapshots (e.g.,http://sql-stress-aggregator.stresstest.svc.cluster.local/register)PEER_SERVICE_NAME(for K8s endpoint discovery, e.g.,sql-stress)PEER_SERVICE_PORT(8080)PEER_DASHBOARD_URLS– optional comma-separated base URLs to pull peer/stats?scope=local
Telemetry:
OTLP_ENDPOINT– OTLP gRPC/HTTP endpoint (e.g.,http://otel-collector:4317)OTLP_HEADERS– extra headers (e.g.,authorization=Bearer abc123)OTLP_INSECURE(false) – skip TLS validation
Other:
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT(0in manifests to keep ICU)SQL_TABLE(stress_writes)
GET /healthz– DB ping (SQL required).GET /status– pod status snapshot.GET|POST /write– single write (or simulated ifDISABLE_SQL=true).GET /messages?limit=10– latest rows from SQL; size-only by default.GET /messages?limit=5&mode=full– full message payload (truncated to 5 MB per row) + per-row fetch duration.GET /sqlread– manual SQL read dashboard (messages role).GET /dashboard– HTML dashboard (aggregator role).GET /stats– JSON used by the dashboard (aggregated ring-aware).- gRPC
stress.StressTest/Write– inserts random payload. - gRPC
stress.StressTest/Healthz– DB health. POST /register– heartbeat ingestion (aggregator).
dotnet restore
SQL_SERVER="<rds-endpoint>" \
SQL_USER="user" \
SQL_PASSWORD="pass" \
SQL_DATABASE="dbname" \
dotnet rundocker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/techcrazi/sql-stress:latest . --pushbrew install trivytrivy image ghcr.io/techcrazi/sql-stress:latestbrew install docker-slim- Enable WSL on Windows Desktop
- Install Docker Desktop
- Install Ubuntu WSL image
wsl --install -d Ubuntu-
Update Docker Desktop Settings
-
Open Docker Desktop → Settings
-
Go to:
-
Resources → WSL Integration
-
Turn ON:
- Enable integration with my default WSL distro
- Ubuntu
-
Click Apply & Restart
-
SSH into Ubuntu WSL
-
Install Slim
curl -sL https://raw.githubusercontent.com/slimtoolkit/slim/master/scripts/install-slim.sh | sudo -E bash -docker run -d \
--name sql-test \
--platform linux/amd64 \
-e "ACCEPT_EULA=Y" \
-e "MSSQL_SA_PASSWORD=StrongPass123" \
-p 1433:1433 \
mcr.microsoft.com/mssql/server:2022-latestslim build \
--target ghcr.io/techcrazi/sql-stress:latest \
--tag ghcr.io/techcrazi/sql-stress:slim-amd64 \
--image-build-arch amd64 \
--publish-port 9080:8080 \
--publish-port 9081:8081 \
--http-probe-cmd 'GET:/healthz' \
--http-probe-cmd 'GET:/status' \
--http-probe-cmd 'GET:/write' \
--http-probe-cmd 'GET:/dashboard' \
--http-probe-cmd 'GET:/messages?limit=10' \
--http-probe-cmd 'GET:/messages?limit=5&mode=full' \
--http-probe-cmd 'GET:/sqlread' \
--include-path '/app' \
--include-path '/usr/share/dotnet/shared/Microsoft.NETCore.App' \
--continue-after=probe \
--copy-meta-artifacts ./slim-artifacts \
--env SQL_SERVER=sql-test \
--env SQL_DATABASE=stress \
--env SQL_USER=sa \
--env SQL_PASSWORD=StrongPass123 \
--env SQL_ENCRYPT=false \
--env SQL_TRUST_SERVER_CERT=true \
--env SQL_CONNECT_TIMEOUT_SECONDS=1-
--http-probe-cmd > Application endpoint that Slim will check against
-
--include-path '/app' > Retain OTEL DLLs
-
--include-path '/usr/share/dotnet/shared/Microsoft.NETCore.App' > Retain .NET DLLs
-
Original Image: 342.85 MB
-
Slim Image: 217.87 MB
slim build \
--target ghcr.io/techcrazi/sql-stress:latest \
--tag ghcr.io/techcrazi/sql-stress:slim-arm64 \
--image-build-arch arm64 \
--publish-port 9080:8080 \
--publish-port 9081:8081 \
--http-probe-cmd 'GET:/healthz' \
--http-probe-cmd 'GET:/status' \
--http-probe-cmd 'GET:/write' \
--http-probe-cmd 'GET:/dashboard' \
--http-probe-cmd 'GET:/messages?limit=10' \
--http-probe-cmd 'GET:/messages?limit=5&mode=full' \
--http-probe-cmd 'GET:/sqlread' \
--include-path '/app' \
--include-path '/usr/share/dotnet/shared/Microsoft.NETCore.App' \
--copy-meta-artifacts ./slim-artifacts \
--env SQL_SERVER=sql-test \
--env SQL_DATABASE=stress \
--env SQL_USER=sa \
--env SQL_PASSWORD=StrongPass123 \
--env SQL_ENCRYPT=false \
--env SQL_TRUST_SERVER_CERT=true \
--env SQL_CONNECT_TIMEOUT_SECONDS=1-
--http-probe-cmd > Application endpoint that Slim will check against
-
--include-path '/app' > Retain OTEL DLLs
-
--include-path '/usr/share/dotnet/shared/Microsoft.NETCore.App' > Retain .NET DLLs
-
Original Image: 372.56 MB
-
Slim Image: 226.60 MB
slim build \
--target ghcr.io/techcrazi/sql-stress:latest \
--tag sql-stress:slim \
--publish-port 9080:8080 \
--publish-port 9081:8081 \
--continue-after=enter \
--copy-meta-artifacts ./slim-artifacts \
--env SQL_SERVER=sql-test \
--env SQL_DATABASE=stress \
--env SQL_USER=sa \
--env SQL_PASSWORD=StrongPass123 \
--env SQL_ENCRYPT=false \
--env SQL_TRUST_SERVER_CERT=true \
--env SQL_CONNECT_TIMEOUT_SECONDS=1 \
--env DISABLE_SQL=truegrpcurl -vv -plaintext \
-proto Protos/stress.proto \
-d '{"sizeBytes":1024}' \
localhost:9081 stress.StressTest/Writedocker push ghcr.io/techcrazi/sql-stress:slim-amd64
docker push ghcr.io/techcrazi/sql-stress:slim-arm64
docker manifest create ghcr.io/techcrazi/sql-stress:slim \
--amend ghcr.io/techcrazi/sql-stress:slim-amd64 \
--amend ghcr.io/techcrazi/sql-stress:slim-arm64
docker manifest push ghcr.io/techcrazi/sql-stress:slim
- Edit
k8s/stress-app.yamlfor your image, host, and DB settings, then apply:
kubectl --kubeconfig k8s/multicare-dev-eks.config apply -f k8s/stress-app.yaml- Deployments/Services:
sql-stress-ingester(HTTP/write, gRPC) on ports 80->8080 and 81->8081;APP_ROLE=ingester.sql-stress-aggregator(ring +/dashboard+/stats) on 80->8080;APP_ROLE=aggregator,DISABLE_SQL=true.sql-stress-messages(SQL reader only;/messages,/sqlread) on 80->8080;APP_ROLE=messages,DISABLE_SQL=true(writes blocked; reads allowed).
- IngressRoute splits:
- gRPC
/stress.StressTest→ ingester (h2c, buffered). - HTTP
/write|/healthz|/status→ ingester. /dashboard|/stats→ aggregator./messages|/sqlread→ messages reader.- Traefik middleware
sql-stress-bufferallows 64 MB request bodies.
- gRPC
- Max request size/message size raised to 60 MB to permit 50 MB payloads; ensure Traefik and any proxies allow similar limits.
- Table schema (auto-created):
id UNIQUEIDENTIFIER,created_at DATETIME2,payload_size INT,payload VARBINARY(MAX). /messagesfull mode truncates per-row payload to 5 MB to avoid large in-memory blobs; size-only mode is lightweight.- Set
PAYLOAD_MIN_BYTES=PAYLOAD_MAX_BYTESfor deterministic size. For heavier loads, tuneMAX_INFLIGHT_SQL, pool settings, and RDS instance size.
-
Update
k6/full-loadtest.jsfile-
Test intensity
const VUS→ 50number of concurrent virtual usersconst TEST_DURATION→ "1h"how long to run the test
-
Common size units
const KB→ 1024file size in KBconst MB→ 1024 * 1024file size in MB
-
File size buckets (in bytes)
const SMALL_MIN→ 1 * KBconst SMALL_MAX→ 100 * KBconst MED_MIN→ 100 * KBconst MED_MAX→ 1 * MBconst LARGE_MIN→ 1 * MBconst LARGE_MAX→ 10 * MBconst HUGE_MIN→ 10 * MBconst HUGE_MAX→ 50 * MB
-
Bucket distribution (must roughly sum to 1.0)
const P_SMALL→ 0.4545% of messages in 1–100 KBconst P_MED→ 0.4545% in 100 KB–1 MBconst P_LARGE→ 0.0505% in 1–10 MBconst P_HUGE→ 0.0505% in 10–50 MB
-
Protocol split
const GRPC_RATIO→ 0.8;0.8 → 80% gRPC, 20% HTTP
-
Endpoint configuration - Point this to the right ingest FQDN
const BASE_HOST→ "stresstest.mc.dev.testme.com"
-
-
Run K6 load test:
- .\k6.exe run full-loadtest.js
- .\k6.exe cloud run full-loadtest.js