first commit
This commit is contained in:
55
postgres/audit/init.sql
Normal file
55
postgres/audit/init.sql
Normal file
@@ -0,0 +1,55 @@
|
||||
-- =============================================================================
|
||||
-- postgres/audit/init.sql
|
||||
-- Runs once on first container start (postgres-audit).
|
||||
-- Creates login users for audit_writer and audit_maintainer roles.
|
||||
-- Role privileges are granted by V2 Flyway migration.
|
||||
-- =============================================================================
|
||||
|
||||
-- audit_writer_login: login user that maps to audit_writer role
|
||||
-- Used by HAPI audit datasource. INSERT only on audit schema.
|
||||
CREATE USER audit_writer_login WITH
|
||||
NOSUPERUSER
|
||||
NOCREATEDB
|
||||
NOCREATEROLE
|
||||
NOINHERIT -- does not automatically inherit role privileges
|
||||
LOGIN
|
||||
CONNECTION LIMIT 20 -- hard cap: prevents connection exhaustion
|
||||
PASSWORD 'PLACEHOLDER_REPLACED_BY_ENTRYPOINT';
|
||||
-- NOTE: Actual password is set by the postgres Docker entrypoint
|
||||
-- reading AUDIT_DB_WRITER_PASSWORD from environment. This CREATE USER
|
||||
-- is a template — the entrypoint rewrites the password on init.
|
||||
-- In practice, use the POSTGRES_* env vars pattern and manage user
|
||||
-- creation via an init script that reads env vars:
|
||||
|
||||
-- Grant the audit_writer role to the login user
|
||||
-- (role created by V2 migration — this runs after migration on first start)
|
||||
-- This GRANT is idempotent — safe to re-run.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'audit_writer') THEN
|
||||
GRANT audit_writer TO audit_writer_login;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- audit_maintainer_login: login user for partition maintenance cron job
|
||||
CREATE USER audit_maintainer_login WITH
|
||||
NOSUPERUSER
|
||||
NOCREATEDB
|
||||
NOCREATEROLE
|
||||
NOINHERIT
|
||||
LOGIN
|
||||
CONNECTION LIMIT 5
|
||||
PASSWORD 'PLACEHOLDER_REPLACED_BY_ENTRYPOINT';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'audit_maintainer') THEN
|
||||
GRANT audit_maintainer TO audit_maintainer_login;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Grant connect on database to both login users
|
||||
GRANT CONNECT ON DATABASE auditdb TO audit_writer_login;
|
||||
GRANT CONNECT ON DATABASE auditdb TO audit_maintainer_login;
|
||||
55
postgres/audit/postgresql.conf
Normal file
55
postgres/audit/postgresql.conf
Normal file
@@ -0,0 +1,55 @@
|
||||
# =============================================================================
|
||||
# postgres/audit/postgresql.conf
|
||||
# PostgreSQL 15 configuration for the audit database.
|
||||
# Container memory limit: 1GB (lighter than FHIR store).
|
||||
# Workload: INSERT-heavy (audit events), occasional SELECT (analytics).
|
||||
#
|
||||
# For 1GB container:
|
||||
# shared_buffers = 256MB
|
||||
# effective_cache_size = 768MB
|
||||
# work_mem = 4MB
|
||||
# maintenance_work_mem = 100MB
|
||||
# =============================================================================
|
||||
|
||||
max_connections = 20
|
||||
superuser_reserved_connections = 3
|
||||
|
||||
shared_buffers = 256MB
|
||||
effective_cache_size = 768MB
|
||||
work_mem = 4MB
|
||||
maintenance_work_mem = 100MB
|
||||
|
||||
wal_buffers = 8MB
|
||||
checkpoint_completion_target = 0.9
|
||||
synchronous_commit = on
|
||||
|
||||
random_page_cost = 1.1
|
||||
effective_io_concurrency = 200
|
||||
|
||||
# Logging
|
||||
log_destination = stderr
|
||||
logging_collector = off
|
||||
log_min_messages = WARNING
|
||||
log_min_error_statement = ERROR
|
||||
log_min_duration_statement = 500
|
||||
log_line_prefix = '%t [%p] %u@%d '
|
||||
log_checkpoints = on
|
||||
log_lock_waits = on
|
||||
log_temp_files = 0
|
||||
|
||||
# Autovacuum — partitioned tables need careful autovacuum tuning.
|
||||
# Each monthly partition is a separate physical table for autovacuum purposes.
|
||||
autovacuum = on
|
||||
autovacuum_max_workers = 3
|
||||
autovacuum_naptime = 60s
|
||||
|
||||
timezone = 'UTC'
|
||||
log_timezone = 'UTC'
|
||||
|
||||
lc_messages = 'en_US.UTF-8'
|
||||
lc_monetary = 'en_US.UTF-8'
|
||||
lc_numeric = 'en_US.UTF-8'
|
||||
lc_time = 'en_US.UTF-8'
|
||||
|
||||
track_io_timing = on
|
||||
track_counts = on
|
||||
38
postgres/fhir/init.sql
Normal file
38
postgres/fhir/init.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- =============================================================================
|
||||
-- postgres/fhir/init.sql
|
||||
-- Runs once on first container start (postgres-fhir).
|
||||
-- Creates the application user that HAPI uses at runtime.
|
||||
-- Flyway migrations run as superuser separately.
|
||||
-- =============================================================================
|
||||
|
||||
-- Application user — read/write to HAPI JPA tables
|
||||
-- Password injected from FHIR_DB_APP_PASSWORD environment variable
|
||||
-- via docker-compose. The \getenv syntax requires psql — use DO block instead.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = current_setting('app.db_user', true)) THEN
|
||||
-- User created by the entrypoint using POSTGRES_* env vars equivalent.
|
||||
-- This script creates it explicitly for auditability.
|
||||
NULL;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Create app user. Password set via environment variable substitution
|
||||
-- in the Docker entrypoint. The actual CREATE USER is handled by
|
||||
-- the entrypoint script reading FHIR_DB_APP_USER/PASSWORD env vars.
|
||||
-- This script grants the necessary privileges after user creation.
|
||||
|
||||
-- Grant connect
|
||||
GRANT CONNECT ON DATABASE fhirdb TO hapi_app;
|
||||
|
||||
-- Grant schema usage and object privileges
|
||||
-- Flyway creates all tables as superuser; we then grant hapi_app access.
|
||||
-- These grants run after Flyway migrations on first startup via Spring Boot
|
||||
-- ApplicationListener — see DataSourceConfig.java.
|
||||
-- Pre-grant public schema access:
|
||||
GRANT USAGE ON SCHEMA public TO hapi_app;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO hapi_app;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT USAGE, SELECT ON SEQUENCES TO hapi_app;
|
||||
81
postgres/fhir/postgresql.conf
Normal file
81
postgres/fhir/postgresql.conf
Normal file
@@ -0,0 +1,81 @@
|
||||
# =============================================================================
|
||||
# postgres/fhir/postgresql.conf
|
||||
# PostgreSQL 15 configuration tuned for HAPI FHIR JPA workload.
|
||||
# Container memory limit: 2GB (set in docker-compose deploy.resources).
|
||||
#
|
||||
# Tuning methodology:
|
||||
# shared_buffers = 25% of container RAM
|
||||
# effective_cache = 75% of container RAM
|
||||
# work_mem = (RAM - shared_buffers) / (max_connections * 2)
|
||||
# maintenance_work_mem = 10% of RAM
|
||||
#
|
||||
# For 2GB container:
|
||||
# shared_buffers = 512MB
|
||||
# effective_cache_size = 1536MB
|
||||
# work_mem = 8MB (conservative — many parallel queries)
|
||||
# maintenance_work_mem = 200MB
|
||||
# =============================================================================
|
||||
|
||||
# Connection settings
|
||||
# max_connections must be > pgBouncer pool_size to leave headroom for
|
||||
# superuser connections (Flyway, maintenance).
|
||||
# pgBouncer pool_size=20 + 5 superuser = 25 total.
|
||||
max_connections = 30
|
||||
superuser_reserved_connections = 3
|
||||
|
||||
# Memory
|
||||
shared_buffers = 512MB
|
||||
effective_cache_size = 1536MB
|
||||
work_mem = 8MB
|
||||
maintenance_work_mem = 200MB
|
||||
|
||||
# Write performance
|
||||
# wal_buffers: 16MB is good for write-heavy workloads
|
||||
wal_buffers = 16MB
|
||||
checkpoint_completion_target = 0.9
|
||||
# synchronous_commit=on: do not disable — data integrity is non-negotiable
|
||||
# for a national health record system.
|
||||
synchronous_commit = on
|
||||
|
||||
# Query planner
|
||||
# random_page_cost=1.1: appropriate for SSD storage (not spinning disk).
|
||||
# If storage is HDD, set to 4.0.
|
||||
random_page_cost = 1.1
|
||||
effective_io_concurrency = 200
|
||||
|
||||
# Logging — errors and slow queries only
|
||||
# log_min_duration_statement: log queries taking >500ms.
|
||||
# Adjust down to 100ms if you want more visibility during initial deployment.
|
||||
log_destination = stderr
|
||||
logging_collector = off # Docker captures stderr directly
|
||||
log_min_messages = WARNING
|
||||
log_min_error_statement = ERROR
|
||||
log_min_duration_statement = 500
|
||||
log_line_prefix = '%t [%p] %u@%d '
|
||||
log_checkpoints = on
|
||||
log_connections = off # pgBouncer already logs connections
|
||||
log_disconnections = off
|
||||
log_lock_waits = on
|
||||
log_temp_files = 0
|
||||
|
||||
# Autovacuum — keep defaults but tune for HAPI's high-write token tables
|
||||
autovacuum = on
|
||||
autovacuum_max_workers = 3
|
||||
autovacuum_naptime = 30s
|
||||
# HFJ_SPIDX_TOKEN is written heavily — lower threshold for autovacuum
|
||||
autovacuum_vacuum_scale_factor = 0.02
|
||||
autovacuum_analyze_scale_factor = 0.01
|
||||
|
||||
# Timezone — all timestamps in UTC
|
||||
timezone = 'UTC'
|
||||
log_timezone = 'UTC'
|
||||
|
||||
# Locale
|
||||
lc_messages = 'en_US.UTF-8'
|
||||
lc_monetary = 'en_US.UTF-8'
|
||||
lc_numeric = 'en_US.UTF-8'
|
||||
lc_time = 'en_US.UTF-8'
|
||||
|
||||
# Statistics
|
||||
track_io_timing = on
|
||||
track_counts = on
|
||||
Reference in New Issue
Block a user