diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 58c4af44..21113978 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -1,5 +1,6 @@ # Test stack: PostGIS for integration tests; optional nginx/fswatch for stack markers; # optional Alloy + LGTM for observability smoke (profile: observability). +# App path uses PgBouncer; host :5432 remains direct PostGIS for CI pytest. services: postgis: image: postgis/postgis:15-3.3 @@ -23,16 +24,53 @@ services: networks: - test-network + pgbouncer: + image: edoburu/pgbouncer:v1.25.1-p0 + depends_on: + postgis: + condition: service_healthy + environment: + - DB_USER=tagbase + - DB_PASSWORD=tagbase + - DB_HOST=postgis + - DB_PORT=5432 + - DB_NAME=tagbase + # Plaintext in userlist so PgBouncer can SCRAM to postgis/postgis (PG15 default). + - AUTH_TYPE=scram-sha-256 + - POOL_MODE=transaction + - SERVER_RESET_QUERY=DISCARD ALL + - MAX_CLIENT_CONN=100 + - DEFAULT_POOL_SIZE=10 + - RESERVE_POOL_SIZE=2 + - RESERVE_POOL_TIMEOUT=3 + - MAX_DB_CONNECTIONS=20 + - MAX_USER_CONNECTIONS=20 + - IGNORE_STARTUP_PARAMETERS=extra_float_digits + - ADMIN_USERS=tagbase + - STATS_USERS=tagbase + expose: + - "5432" + healthcheck: + test: + ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U tagbase -d tagbase"] + interval: 5s + timeout: 5s + retries: 20 + start_period: 10s + hostname: pgbouncer + networks: + - test-network + tagbase_server: profiles: ["stack", "observability"] build: context: ./tagbase_server depends_on: - postgis: + pgbouncer: condition: service_healthy environment: - PGADMIN_DEFAULT_EMAIL=test@example.com - - POSTGRES_HOST=postgis + - POSTGRES_HOST=pgbouncer - POSTGRES_PASSWORD=tagbase - POSTGRES_PORT=5432 - OTEL_SDK_DISABLED=${OTEL_SDK_DISABLED:-true} diff --git a/docker-compose.yml b/docker-compose.yml index 787c825d..7913adf1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,17 +28,18 @@ services: build: context: ./services/docker-cron depends_on: - postgis: + pgbouncer: condition: service_healthy environment: + - POSTGRES_HOST=pgbouncer - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-tagbase} - - POSTGRES_PORT=${POSTGRES_PORT:-5432} + - POSTGRES_PORT=5432 labels: "docker_compose_diagram.cluster": "Internal Network" "docker_compose_diagram.description": "'metadata_types' and\n'observation_types' synchronization" "docker_compose_diagram.icon": "python" links: - - postgis + - pgbouncer networks: - internal-network restart: unless-stopped @@ -173,16 +174,61 @@ services: - type: tmpfs target: /opt/data + pgbouncer: + image: edoburu/pgbouncer:v1.25.1-p0 + depends_on: + postgis: + condition: service_healthy + environment: + - DB_USER=tagbase + - DB_PASSWORD=${POSTGRES_PASSWORD:-tagbase} + - DB_HOST=postgis + - DB_PORT=${POSTGRES_PORT:-5432} + - DB_NAME=tagbase + # Plaintext in userlist so PgBouncer can auth to PostGIS (SCRAM or md5). + - AUTH_TYPE=scram-sha-256 + - POOL_MODE=transaction + - SERVER_RESET_QUERY=DISCARD ALL + - MAX_CLIENT_CONN=100 + - DEFAULT_POOL_SIZE=10 + - RESERVE_POOL_SIZE=2 + - RESERVE_POOL_TIMEOUT=3 + - MAX_DB_CONNECTIONS=20 + - MAX_USER_CONNECTIONS=20 + - IGNORE_STARTUP_PARAMETERS=extra_float_digits + - ADMIN_USERS=tagbase + - STATS_USERS=tagbase + # edoburu listens on 5432 (Postgres-compatible). Distinct from host PostGIS via hostname. + expose: + - "5432" + healthcheck: + test: + ["CMD-SHELL", "pg_isready -h 127.0.0.1 -p 5432 -U tagbase -d tagbase"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 10s + hostname: pgbouncer + labels: + "docker_compose_diagram.cluster": "Internal Network" + "docker_compose_diagram.description": "PgBouncer connection pooler" + "docker_compose_diagram.icon": "docker" + networks: + - internal-network + restart: unless-stopped + # No host ports: app/cron use internal DNS; host DB clients keep PostGIS :5432. + tagbase_server: build: context: ./tagbase_server depends_on: - postgis: + pgbouncer: condition: service_healthy environment: - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-admin@example.com} + - POSTGRES_HOST=pgbouncer - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-tagbase} - - POSTGRES_PORT=${POSTGRES_PORT:-5432} + - POSTGRES_PORT=5432 - OTEL_SDK_DISABLED=${OTEL_SDK_DISABLED:-true} - OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-} - OTEL_SERVICE_NAME=${OTEL_SERVICE_NAME:-tagbase_server} @@ -194,7 +240,7 @@ services: "docker_compose_diagram.description": "tagbase-server tag \ningestion and administration" "docker_compose_diagram.icon": "flask" links: - - postgis + - pgbouncer networks: - internal-network restart: unless-stopped diff --git a/renovate.json b/renovate.json index 3207e63a..1fd83397 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,12 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended", ":dependencyDashboard"] + "extends": ["config:recommended", ":dependencyDashboard"], + "packageRules": [ + { + "description": "edoburu/pgbouncer uses tags like v1.25.1-p0 (image rebuild suffix)", + "matchDatasources": ["docker"], + "matchPackageNames": ["edoburu/pgbouncer"], + "versioning": "loose" + } + ] } diff --git a/services/docker-cron/poll_metadata_and_obs_types.py b/services/docker-cron/poll_metadata_and_obs_types.py index 5037b28d..39d6737e 100755 --- a/services/docker-cron/poll_metadata_and_obs_types.py +++ b/services/docker-cron/poll_metadata_and_obs_types.py @@ -82,7 +82,7 @@ def connect(): return psycopg2.connect( dbname="tagbase", user="tagbase", - host="postgis", + host=os.getenv("POSTGRES_HOST", "pgbouncer"), port=os.getenv("POSTGRES_PORT", "5432"), password=os.getenv("POSTGRES_PASSWORD", "tagbase"), ) diff --git a/tagbase_server/tagbase_server/utils/db_utils.py b/tagbase_server/tagbase_server/utils/db_utils.py index d3cabe97..c86930f5 100644 --- a/tagbase_server/tagbase_server/utils/db_utils.py +++ b/tagbase_server/tagbase_server/utils/db_utils.py @@ -45,8 +45,8 @@ def connect(): conn = psycopg2.connect( dbname="tagbase", user="tagbase", - host=os.getenv("POSTGRES_HOST", "postgis"), - port=os.getenv("POSTGRES_PORT"), + host=os.getenv("POSTGRES_HOST", "pgbouncer"), + port=os.getenv("POSTGRES_PORT", "5432"), password=os.getenv("POSTGRES_PASSWORD"), ) except psycopg2.OperationalError as poe: