mirror of
https://github.com/devkitshq/notifkit.git
synced 2026-10-19 18:24:52 +02:00
202 lines
5.8 KiB
YAML
202 lines
5.8 KiB
YAML
# Driven by src/runner.ts through the docker compose CLI; every ${VAR} below is
|
|
# set per tier by the runner. Nodes run the image the runner builds as
|
|
# notifkit-prof-node from profiling/Dockerfile.
|
|
|
|
name: notifkit-prof
|
|
|
|
x-node-env: &node-env
|
|
NODE_ENV: production
|
|
DATABASE_URL: postgres://notifkit:password@db:5432/notifkit
|
|
REDIS_URL: redis://redis:6379
|
|
ADMIN_API_KEY: perf-admin-key
|
|
HOST: 0.0.0.0
|
|
LOG_LEVEL: ${LOG_LEVEL:-info}
|
|
WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-50}
|
|
DELIVERY_CONCURRENCY: ${DELIVERY_CONCURRENCY:-200}
|
|
DB_MAX_CONNECTIONS: ${DB_MAX_CONNECTIONS:-5}
|
|
PIPELINE_FUSED: ${PIPELINE_FUSED:-false}
|
|
RATE_LIMIT_PER_HOUR: ${RATE_LIMIT_PER_HOUR:-100}
|
|
# Far above any backlog a run builds: MAXLEN trims unread entries, and a
|
|
# trimmed notification is simply lost.
|
|
QUEUE_MAX_LEN: ${QUEUE_MAX_LEN:-5000000}
|
|
UNSUBSCRIBE_SECRET: profiling-unsubscribe-secret-0123456789
|
|
PUBLIC_URL: http://localhost:3000
|
|
PROVIDER_LATENCY_MS: ${PROVIDER_LATENCY_MS:-150-250}
|
|
PROVIDER_FAILURE_RATE: ${PROVIDER_FAILURE_RATE:-0}
|
|
# Share of delivered messages the runner later reports as opened through the
|
|
# profiling transport's webhook (tests/sustained.test.ts).
|
|
PROFILING_OPEN_RATE: ${PROFILING_OPEN_RATE:-0}
|
|
# Worker services wait for the runner to open this gate, so ingest can be
|
|
# measured on its own and the backlog it builds drained as a second phase.
|
|
WORKER_GATE: "1"
|
|
# Set by --cpu-prof: V8 profiles land in results/cpuprof on shutdown.
|
|
NODE_OPTIONS: ${NODE_OPTIONS:-}
|
|
|
|
x-node: &node
|
|
image: notifkit-prof-node:latest
|
|
volumes:
|
|
- ./results/cpuprof:/prof
|
|
# Long enough for workers to finish in-flight messages and flush a profile.
|
|
stop_grace_period: 60s
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/health >/dev/null || exit 1"]
|
|
interval: 2s
|
|
timeout: 2s
|
|
retries: 60
|
|
start_period: 5s
|
|
|
|
services:
|
|
db:
|
|
image: postgres:15-alpine
|
|
environment:
|
|
POSTGRES_DB: notifkit
|
|
POSTGRES_USER: notifkit
|
|
POSTGRES_PASSWORD: password
|
|
# Production settings scaled to the tier's memory, rather than the image's
|
|
# defaults (128MB shared_buffers, 1GB max_wal_size) that nobody deploys.
|
|
command:
|
|
- postgres
|
|
- -c
|
|
- shared_buffers=${PG_SHARED_BUFFERS:-256MB}
|
|
- -c
|
|
- effective_cache_size=${PG_EFFECTIVE_CACHE_SIZE:-768MB}
|
|
- -c
|
|
- maintenance_work_mem=${PG_MAINTENANCE_WORK_MEM:-64MB}
|
|
- -c
|
|
- work_mem=8MB
|
|
- -c
|
|
- max_connections=400
|
|
- -c
|
|
- wal_buffers=16MB
|
|
- -c
|
|
- min_wal_size=512MB
|
|
- -c
|
|
- max_wal_size=4GB
|
|
- -c
|
|
- checkpoint_completion_target=0.9
|
|
- -c
|
|
- random_page_cost=1.1
|
|
- -c
|
|
- effective_io_concurrency=200
|
|
- -c
|
|
- shared_preload_libraries=pg_stat_statements
|
|
- -c
|
|
- pg_stat_statements.track=all
|
|
- -c
|
|
- track_io_timing=on
|
|
shm_size: 512mb
|
|
ports:
|
|
- "${PROF_DB_PORT:-35432}:5432"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U notifkit -d notifkit"]
|
|
interval: 1s
|
|
timeout: 2s
|
|
retries: 60
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "${DB_CPUS:-1.0}"
|
|
memory: "${DB_MEMORY:-1G}"
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
# AOF with a once-a-second fsync is the durability production runs with;
|
|
# the streams are the only copy of a queued notification.
|
|
command:
|
|
[
|
|
"redis-server",
|
|
"--appendonly",
|
|
"yes",
|
|
"--appendfsync",
|
|
"everysec",
|
|
"--save",
|
|
"",
|
|
"--maxmemory-policy",
|
|
"noeviction",
|
|
]
|
|
ports:
|
|
- "${PROF_REDIS_PORT:-36379}:6379"
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 1s
|
|
timeout: 2s
|
|
retries: 60
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "${REDIS_CPUS:-1.0}"
|
|
memory: "${REDIS_MEMORY:-1G}"
|
|
|
|
# ── Vertical: one monolith running every service ──────────────────────────
|
|
server:
|
|
<<: *node
|
|
environment:
|
|
<<: *node-env
|
|
SERVICES: ${SERVER_SERVICES:-api,enricher,engine,delivery,scheduler,events}
|
|
ports:
|
|
- "${PROF_API_PORT:-35678}:3000"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "${SERVER_CPUS:-1.0}"
|
|
memory: "${SERVER_MEMORY:-1G}"
|
|
|
|
# ── Horizontal: API ingress nodes ─────────────────────────────────────────
|
|
api-1:
|
|
<<: *node
|
|
environment:
|
|
<<: *node-env
|
|
SERVICES: api
|
|
ports:
|
|
- "${PROF_API_PORT:-35678}:3000"
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "${API_CPUS:-1.0}"
|
|
memory: "${API_MEMORY:-1G}"
|
|
|
|
api-2:
|
|
<<: *node
|
|
environment:
|
|
<<: *node-env
|
|
SERVICES: api
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "${API_CPUS:-1.0}"
|
|
memory: "${API_MEMORY:-1G}"
|
|
|
|
# ── Horizontal: identical pipeline workers, scaled by count ───────────────
|
|
worker-1: &worker
|
|
<<: *node
|
|
environment:
|
|
<<: *node-env
|
|
SERVICES: ${WORKER_SERVICES:-enricher,engine,delivery,scheduler,events}
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "${WORKER_CPUS:-1.0}"
|
|
memory: "${WORKER_MEMORY:-1G}"
|
|
|
|
worker-2: *worker
|
|
worker-3: *worker
|
|
worker-4: *worker
|
|
|
|
# ── Load generator, inside the compose network ────────────────────────────
|
|
loadgen:
|
|
image: grafana/k6:1.0.0
|
|
volumes:
|
|
- ./k6:/scripts:ro
|
|
- ./results/.k6:/out
|
|
command: ["run", "--quiet", "--no-usage-report", "/scripts/notify.js"]
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
cpus: "${LOADGEN_CPUS:-4.0}"
|
|
memory: "2G"
|