diff --git a/dockerfiles/postgres-cluster/.env b/dockerfiles/postgres-cluster/.env new file mode 100644 index 0000000..499e86e --- /dev/null +++ b/dockerfiles/postgres-cluster/.env @@ -0,0 +1,5 @@ +POSTGRES_USER=admin +POSTGRES_PASSWORD=admin +POSTGRES_DB=admin +PGPOOL_ADMIN_USERNAME=admin +PGPOOL_ADMIN_PASSWORD=admin diff --git a/dockerfiles/postgres-cluster/GUIDE.md b/dockerfiles/postgres-cluster/GUIDE.md new file mode 100644 index 0000000..000a93f --- /dev/null +++ b/dockerfiles/postgres-cluster/GUIDE.md @@ -0,0 +1,57 @@ +# PostgreSQL + Pgpool Replication + +This project sets up a PostgreSQL replication environment using Docker Compose. It includes: +- One **Primary** PostgreSQL node +- Two **Replicas** +- A **Pgpool** node for load balancing and (future) failover + +It also includes an automated test script to verify replication is working as expected. + +--- + +## ✅ Features + +- PostgreSQL 16 replication (streaming) +- Two replicas kept in sync with the primary +- Pgpool for centralized connection management +- Automatic test script to insert and verify data +- Fully Dockerized and isolated environment + +--- + +## 🛠️ Setup Instructions (Step-by-Step) + +### 1. Clean Previous Setup + +Remove existing containers, volumes, and the custom Docker network (if you’ve run the system before): + +```bash +docker compose down -v # Stops and removes containers and volumes +docker network rm pgnet # Removes the external network +``` + +This command starts all services in the background: + +```bash +docker network create pgnet && docker compose up --build -d +``` + +### 2. Test + +```bash +chmod +x test.sh +``` + +```bash +./test.sh +``` + +This script will: + +- Connect to the Primary and insert test data into a table + +- Query the Replicas and Pgpool + +- Compare the data across all nodes + +- Output the result of the verification diff --git a/dockerfiles/postgres-cluster/docker-compose.yml b/dockerfiles/postgres-cluster/docker-compose.yml new file mode 100644 index 0000000..507fbb3 --- /dev/null +++ b/dockerfiles/postgres-cluster/docker-compose.yml @@ -0,0 +1,115 @@ +version: "3.9" + +services: + primary: + image: postgres:16 + container_name: pg-primary + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - "5432:5432" + volumes: + - primary-data:/var/lib/postgresql/data + - ./primary/postgres.conf:/etc/postgresql/postgresql.conf + - ./primary/init.sh:/docker-entrypoint-initdb.d/init.sh + healthcheck: + test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}"] + interval: 5s + timeout: 3s + retries: 5 + networks: + - pgnet + + replica1: + image: postgres:16 + container_name: pg-replica-1 + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + depends_on: + - primary + volumes: + - replica1-data:/var/lib/postgresql/data + command: > + bash -c " + echo '*:*:*:${POSTGRES_USER}:${POSTGRES_PASSWORD}' > /var/lib/postgresql/.pgpass && + chown postgres:postgres /var/lib/postgresql/.pgpass && + chmod 600 /var/lib/postgresql/.pgpass && + rm -rf /var/lib/postgresql/data/* && + until pg_isready -h primary -p 5432 -U ${POSTGRES_USER}; do + echo 'Waiting for primary...'; + sleep 2; + done && + gosu postgres pg_basebackup -h primary -D /var/lib/postgresql/data -U ${POSTGRES_USER} -Fp -Xs -P -R && + chmod 700 /var/lib/postgresql/data && + chown -R postgres:postgres /var/lib/postgresql/data && + exec gosu postgres postgres + " + networks: + - pgnet + + replica2: + image: postgres:16 + container_name: pg-replica-2 + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + depends_on: + - primary + volumes: + - replica2-data:/var/lib/postgresql/data + command: > + bash -c " + echo '*:*:*:${POSTGRES_USER}:${POSTGRES_PASSWORD}' > /var/lib/postgresql/.pgpass && + chown postgres:postgres /var/lib/postgresql/.pgpass && + chmod 600 /var/lib/postgresql/.pgpass && + rm -rf /var/lib/postgresql/data/* && + until pg_isready -h primary -p 5432 -U ${POSTGRES_USER}; do + echo 'Waiting for primary...'; + sleep 2; + done && + gosu postgres pg_basebackup -h primary -D /var/lib/postgresql/data -U ${POSTGRES_USER} -Fp -Xs -P -R && + chmod 700 /var/lib/postgresql/data && + chown -R postgres:postgres /var/lib/postgresql/data && + exec gosu postgres postgres + " + networks: + - pgnet + + pgpool: + image: bitnami/pgpool:4 + container_name: pgpool + restart: always + ports: + - "5433:5432" + environment: + - PGPOOL_BACKEND_NODES=0:pg-primary:5432,1:pg-replica-1:5432,2:pg-replica-2:5432 + - PGPOOL_ENABLE_LOAD_BALANCING=yes + - PGPOOL_SR_CHECK_USER=${POSTGRES_USER} + - PGPOOL_SR_CHECK_PASSWORD=${POSTGRES_PASSWORD} + - PGPOOL_POSTGRES_USERNAME=${POSTGRES_USER} + - PGPOOL_POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - PGPOOL_ADMIN_USERNAME=${PGPOOL_ADMIN_USERNAME} + - PGPOOL_ADMIN_PASSWORD=${PGPOOL_ADMIN_PASSWORD} + depends_on: + - primary + - replica1 + - replica2 + volumes: + - ./pgpool/pgpool.conf:/opt/bitnami/pgpool/conf/pgpool.conf + networks: + - pgnet + +volumes: + primary-data: + replica1-data: + replica2-data: + +networks: + pgnet: + external: true diff --git a/dockerfiles/postgres-cluster/pgpool/pgpool.conf b/dockerfiles/postgres-cluster/pgpool/pgpool.conf new file mode 100644 index 0000000..436bb72 --- /dev/null +++ b/dockerfiles/postgres-cluster/pgpool/pgpool.conf @@ -0,0 +1,1032 @@ +# -------------------------------- +# Pgpool-II 4.6 configuration file +# -------------------------------- +# +# This file consists of lines of the form: +# +# name = value +# +# Whitespace may be used. Comments are introduced with "#" anywhere on a line. +# The complete list of parameter names and allowed values can be found in the +# pgPool-II documentation. +# +# This file is read on server startup and when the server receives a SIGHUP +# signal. If you edit the file on a running system, you have to SIGHUP the +# server for the changes to take effect, or use "pgpool reload". Some +# parameters, which are marked below, require a server shutdown and restart to +# take effect. +# + +#------------------------------------------------------------------------------ +# BACKEND CLUSTERING MODE +# Choose one of: 'streaming_replication', 'native_replication', +# 'logical_replication', 'slony', 'raw' or 'snapshot_isolation' +# (change requires restart) +#------------------------------------------------------------------------------ + +backend_clustering_mode = 'streaming_replication' + +#------------------------------------------------------------------------------ +# CONNECTIONS +#------------------------------------------------------------------------------ + +# - pgpool Connection Settings - + +listen_addresses = '*' + # what host name(s) or IP address(es) to listen on; + # comma-separated list of addresses; + # defaults to 'localhost'; use '*' for all + # (change requires restart) +port = '5432' + # Port number + # (change requires restart) +unix_socket_directories = '/opt/bitnami/pgpool/tmp' + # Unix domain socket path(s) + # The Debian package defaults to + # /var/run/postgresql + # (change requires restart) +#unix_socket_group = '' + # The Owner group of Unix domain socket(s) + # (change requires restart) +#unix_socket_permissions = 0777 + # Permissions of Unix domain socket(s) + # (change requires restart) +#reserved_connections = 0 + # Number of reserved connections. + # Pgpool-II does not accept connections if over + # num_init_children - reserved_connections. + + +# - pgpool Communication Manager Connection Settings - + +#pcp_listen_addresses = 'localhost' + # what host name(s) or IP address(es) for pcp process to listen on; + # comma-separated list of addresses; + # defaults to 'localhost'; use '*' for all + # (change requires restart) +#pcp_port = 9898 + # Port number for pcp + # (change requires restart) +pcp_socket_dir = '/opt/bitnami/pgpool/tmp' + # Unix domain socket path(s) for pcp + # The Debian package defaults to + # /var/run/postgresql + # (change requires restart) +#listen_backlog_multiplier = 2 + # Set the backlog parameter of listen(2) to + # num_init_children * listen_backlog_multiplier. + # (change requires restart) +#serialize_accept = off + # whether to serialize accept() call to avoid thundering herd problem + # (change requires restart) + +# - Backend Connection Settings - + +#backend_hostname0 = 'host1' + # Host name or IP address to connect to for backend 0 +#backend_port0 = 5432 + # Port number for backend 0 +#backend_weight0 = 1 + # Weight for backend 0 (only in load balancing mode) +#backend_data_directory0 = '/data' + # Data directory for backend 0 +#backend_flag0 = 'ALLOW_TO_FAILOVER' + # Controls various backend behavior + # ALLOW_TO_FAILOVER, DISALLOW_TO_FAILOVER + # or ALWAYS_PRIMARY +#backend_application_name0 = 'server0' + # walsender's application_name, used for "show pool_nodes" command +#backend_hostname1 = 'host2' +#backend_port1 = 5433 +#backend_weight1 = 1 +#backend_data_directory1 = '/data1' +#backend_flag1 = 'ALLOW_TO_FAILOVER' +#backend_application_name1 = 'server1' + +# - Authentication - + +enable_pool_hba = 'on' + # Use pool_hba.conf for client authentication +pool_passwd = 'pool_passwd' + # File name of pool_passwd for md5 authentication. + # "" disables pool_passwd. + # (change requires restart) +authentication_timeout = '30' + # Delay in seconds to complete client authentication + # 0 means no timeout. + +allow_clear_text_frontend_auth = 'off' + # Allow Pgpool-II to use clear text password authentication + # with clients, when pool_passwd does not + # contain the user password + +# - SSL Connections - + +#ssl = off + # Enable SSL support + # (change requires restart) +#ssl_key = 'server.key' + # SSL private key file + # (change requires restart) +#ssl_cert = 'server.crt' + # SSL public certificate file + # (change requires restart) +#ssl_ca_cert = '' + # Single PEM format file containing + # CA root certificate(s) + # (change requires restart) +#ssl_ca_cert_dir = '' + # Directory containing CA root certificate(s) + # (change requires restart) +#ssl_crl_file = '' + # SSL certificate revocation list file + # (change requires restart) + +#ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' + # Allowed SSL ciphers + # (change requires restart) +#ssl_prefer_server_ciphers = off + # Use server's SSL cipher preferences, + # rather than the client's + # (change requires restart) +#ssl_ecdh_curve = 'prime256v1' + # Name of the curve to use in ECDH key exchange +#ssl_dh_params_file = '' + # Name of the file containing Diffie-Hellman parameters used + # for so-called ephemeral DH family of SSL cipher. +#ssl_passphrase_command='' + # Sets an external command to be invoked when a passphrase + # for decrypting an SSL file needs to be obtained + # (change requires restart) + +#------------------------------------------------------------------------------ +# POOLS +#------------------------------------------------------------------------------ + +# - Concurrent session and pool size - + +#process_management_mode = static + # process management mode for child processes + # Valid options: + # static: all children are pre-forked at startup + # dynamic: child processes are spawned on demand. + # number of idle child processes at any time are + # configured by min_spare_children and max_spare_children + +#process_management_strategy = gentle + # process management strategy to satisfy spare processes + # Valid options: + # + # lazy: In this mode, the scale-down is performed gradually + # and only gets triggered when excessive spare processes count + # remains high for more than 5 mins + # + # gentle: In this mode, the scale-down is performed gradually + # and only gets triggered when excessive spare processes count + # remains high for more than 2 mins + # + # aggressive: In this mode, the scale-down is performed aggressively + # and gets triggered more frequently in case of higher spare processes. + # This mode uses faster and slightly less smart process selection criteria + # to identify the child processes that can be serviced to satisfy + # max_spare_children + # + # (Only applicable for dynamic process management mode) + +#num_init_children = 32 + # Maximum Number of concurrent sessions allowed + # (change requires restart) +#min_spare_children = 5 + # Minimum number of spare child processes waiting for connection + # (Only applicable for dynamic process management mode) + +#max_spare_children = 10 + # Maximum number of idle child processes waiting for connection + # (Only applicable for dynamic process management mode) + +max_pool = '15' + # Number of connection pool caches per connection + # (change requires restart) + +# - Life time - + +#child_life_time = 5min + # Pool exits after being idle for this many seconds +#child_max_connections = 0 + # Pool exits after receiving that many connections + # 0 means no exit +#connection_life_time = 0 + # Connection to backend closes after being idle for this many seconds + # 0 means no close +#client_idle_limit = 0 + # Client is disconnected after being idle for that many seconds + # (even inside an explicit transactions!) + # 0 means no disconnection + + +#------------------------------------------------------------------------------ +# LOGS +#------------------------------------------------------------------------------ + +# - Where to log - + +#log_destination = 'stderr' + # Where to log + # Valid values are combinations of stderr, + # and syslog. Default to stderr. + +# - What to log - + +#log_line_prefix = '%m: %a pid %p: ' # printf-style string to output at beginning of each log line. + +log_connections = 'off' + # Log connections +#log_disconnections = off + # Log disconnections +#log_pcp_processes = on + # Log PCP Processes +log_hostname = 'off' + # Hostname will be shown in ps status + # and in logs if connections are logged +#log_statement = off + # Log all statements +log_per_node_statement = 'off' + # Log all statements + # with node and backend information +#notice_per_node_statement = off + # logs notice message for per node detailed SQL statements +#log_client_messages = off + # Log any client messages +#log_backend_messages = none + # Log any backend messages + # Valid values are none, terse and verbose + +#log_standby_delay = 'if_over_threshold' + # Log standby delay + # Valid values are combinations of always, + # if_over_threshold, none + +# - Syslog specific - + +#syslog_facility = 'LOCAL0' + # Syslog local facility. Default to LOCAL0 +#syslog_ident = 'pgpool' + # Syslog program identification string + # Default to 'pgpool' + +# - Debug - + +#log_error_verbosity = default # terse, default, or verbose messages + +#client_min_messages = notice # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # log + # notice + # warning + # error + +#log_min_messages = warning # values in order of decreasing detail: + # debug5 + # debug4 + # debug3 + # debug2 + # debug1 + # info + # notice + # warning + # error + # log + # fatal + # panic + +# This is used when logging to stderr: +#logging_collector = off + # Enable capturing of stderr + # into log files. + # (change requires restart) + +# -- Only used if logging_collector is on --- + +#log_directory = '/tmp/pgpool_logs' + # directory where log files are written, + # can be absolute +#log_filename = 'pgpool-%Y-%m-%d_%H%M%S.log' + # log file name pattern, + # can include strftime() escapes + +#log_file_mode = 0600 + # creation mode for log files, + # begin with 0 to use octal notation + +#log_truncate_on_rotation = off + # If on, an existing log file with the + # same name as the new log file will be + # truncated rather than appended to. + # But such truncation only occurs on + # time-driven rotation, not on restarts + # or size-driven rotation. Default is + # off, meaning append to existing files + # in all cases. + +#log_rotation_age = 1d + # Automatic rotation of logfiles will + # happen after that (minutes)time. + # 0 disables time based rotation. +#log_rotation_size = 10MB + # Automatic rotation of logfiles will + # happen after that much (KB) log output. + # 0 disables size based rotation. +#------------------------------------------------------------------------------ +# FILE LOCATIONS +#------------------------------------------------------------------------------ + +pid_file_name = '/opt/bitnami/pgpool/tmp/pgpool.pid' + # PID file name + # Can be specified as relative to the" + # location of pgpool.conf file or + # as an absolute path + # (change requires restart) +logdir = '/opt/bitnami/pgpool/logs' + # Directory of pgPool status file + # (change requires restart) + + +#------------------------------------------------------------------------------ +# CONNECTION POOLING +#------------------------------------------------------------------------------ + +#connection_cache = on + # Activate connection pools + # (change requires restart) + + # Semicolon separated list of queries + # to be issued at the end of a session + # The default is for 8.3 and later +#reset_query_list = 'ABORT; DISCARD ALL' + # The following one is for 8.2 and before +#reset_query_list = 'ABORT; RESET ALL; SET SESSION AUTHORIZATION DEFAULT' + + +#------------------------------------------------------------------------------ +# REPLICATION MODE +#------------------------------------------------------------------------------ + +#replicate_select = off + # Replicate SELECT statements + # when in replication mode + # replicate_select is higher priority than + # load_balance_mode. + +#insert_lock = on + # Automatically locks a dummy row or a table + # with INSERT statements to keep SERIAL data + # consistency + # Without SERIAL, no lock will be issued +#lobj_lock_table = '' + # When rewriting lo_creat command in + # replication mode, specify table name to + # lock + +# - Degenerate handling - + +#replication_stop_on_mismatch = off + # On disagreement with the packet kind + # sent from backend, degenerate the node + # which is most likely "minority" + # If off, just force to exit this session + +#failover_if_affected_tuples_mismatch = off + # On disagreement with the number of affected + # tuples in UPDATE/DELETE queries, then + # degenerate the node which is most likely + # "minority". + # If off, just abort the transaction to + # keep the consistency + + +#------------------------------------------------------------------------------ +# LOAD BALANCING MODE +#------------------------------------------------------------------------------ + +load_balance_mode = 'on' + # Activate load balancing mode + # (change requires restart) +#ignore_leading_white_space = on + # Ignore leading white spaces of each query +#read_only_function_list = '' + # Comma separated list of function names + # that don't write to database + # Regexp are accepted +#write_function_list = '' + # Comma separated list of function names + # that write to database + # Regexp are accepted + # If both read_only_function_list and write_function_list + # is empty, function's volatile property is checked. + # If it's volatile, the function is regarded as a + # writing function. + +#primary_routing_query_pattern_list = '' + # Semicolon separated list of query patterns + # that should be sent to primary node + # Regexp are accepted + # valid for streaming replication mode only. + +#user_redirect_preference_list = '' + # comma separated list of pairs of user name and node id. + # example: postgres:primary,user[0-4]:1,user[5-9]:2' + # valid for streaming replication mode only. + +#database_redirect_preference_list = '' + # comma separated list of pairs of database and node id. + # example: postgres:primary,mydb[0-4]:1,mydb[5-9]:2' + # valid for streaming replication mode only. + +#app_name_redirect_preference_list = '' + # comma separated list of pairs of app name and node id. + # example: 'psql:primary,myapp[0-4]:1,myapp[5-9]:standby' + # valid for streaming replication mode only. +#allow_sql_comments = off + # if on, ignore SQL comments when judging if load balance or + # query cache is possible. + # If off, SQL comments effectively prevent the judgment + # (pre 3.4 behavior). + +disable_load_balance_on_write = 'transaction' + # Load balance behavior when write query is issued + # in an explicit transaction. + # + # Valid values: + # + # 'transaction' (default): + # if a write query is issued, subsequent + # read queries will not be load balanced + # until the transaction ends. + # + # 'trans_transaction': + # if a write query is issued, subsequent + # read queries in an explicit transaction + # will not be load balanced until the session ends. + # + # 'dml_adaptive': + # Queries on the tables that have already been + # modified within the current explicit transaction will + # not be load balanced until the end of the transaction. + # + # 'always': + # if a write query is issued, read queries will + # not be load balanced until the session ends. + # + # Note that any query not in an explicit transaction + # is not affected by the parameter except 'always'. + +#dml_adaptive_object_relationship_list= '' + # comma separated list of object pairs + # [object]:[dependent-object], to disable load balancing + # of dependent objects within the explicit transaction + # after WRITE statement is issued on (depending-on) object. + # + # example: 'tb_t1:tb_t2,insert_tb_f_func():tb_f,tb_v:my_view' + # Note: function name in this list must also be present in + # the write_function_list + # only valid for disable_load_balance_on_write = 'dml_adaptive'. + +statement_level_load_balance = 'off' + # Enables statement level load balancing + +#------------------------------------------------------------------------------ +# STREAMING REPLICATION MODE +#------------------------------------------------------------------------------ + +# - Streaming - + +sr_check_period = '30' + # Streaming replication check period + # Default is 10s. +sr_check_user = 'admin' + # Streaming replication check user + # This is necessary even if you disable streaming + # replication delay check by sr_check_period = 0 +sr_check_password = 'AESIdSY+WwYAncDcAca4aPcdg==' + # Password for streaming replication check user + # Leaving it empty will make Pgpool-II to first look for the + # Password in pool_passwd file before using the empty password + +sr_check_database = 'postgres' + # Database name for streaming replication check +#delay_threshold = 0 + # Threshold before not dispatching query to standby node + # Unit is in bytes + # Disabled (0) by default +#delay_threshold_by_time = 0 + # Threshold before not dispatching query to standby node + # The default unit is in millisecond(s) + # Disabled (0) by default + +#prefer_lower_delay_standby = off + # If delay_threshold is set larger than 0, Pgpool-II send to + # the primary when selected node is delayed over delay_threshold. + # If this is set to on, Pgpool-II send query to other standby + # delayed lower. + +# - Special commands - + +#follow_primary_command = '' + # Executes this command after main node failover + # Special values: + # %d = failed node id + # %h = failed node host name + # %p = failed node port number + # %D = failed node database cluster path + # %m = new main node id + # %H = new main node hostname + # %M = old main node id + # %P = old primary node id + # %r = new main port number + # %R = new main database cluster path + # %N = old primary node hostname + # %S = old primary node port number + # %% = '%' character + +#------------------------------------------------------------------------------ +# HEALTH CHECK GLOBAL PARAMETERS +#------------------------------------------------------------------------------ + +health_check_period = '30' + # Health check period + # Disabled (0) by default +health_check_timeout = '10' + # Health check timeout + # 0 means no timeout +health_check_user = 'admin' + # Health check user +health_check_password = 'AESIdSY+WwYAncDcAca4aPcdg==' + # Password for health check user + # Leaving it empty will make Pgpool-II to first look for the + # Password in pool_passwd file before using the empty password + +#health_check_database = '' + # Database name for health check. If '', tries 'postgres' first. +health_check_max_retries = '5' + # Maximum number of times to retry a failed health check before giving up. +health_check_retry_delay = '5' + # Amount of time to wait (in seconds) between retries. +connect_timeout = '10000' + # Timeout value in milliseconds before giving up to connect to backend. + # Default is 10000 ms (10 second). Flaky network user may want to increase + # the value. 0 means no timeout. + # Note that this value is not only used for health check, + # but also for ordinary connection to backend. + +#------------------------------------------------------------------------------ +# HEALTH CHECK PER NODE PARAMETERS (OPTIONAL) +#------------------------------------------------------------------------------ +#health_check_period0 = 0 +#health_check_timeout0 = 20 +#health_check_user0 = '' +#health_check_password0 = '' +#health_check_database0 = '' +#health_check_max_retries0 = 0 +#health_check_retry_delay0 = 1 +#connect_timeout0 = 10000 + +#------------------------------------------------------------------------------ +# FAILOVER AND FAILBACK +#------------------------------------------------------------------------------ + +failover_command = 'echo ">>> Failover - that will initialize new primary node search!"' + # Executes this command at failover + # Special values: + # %d = failed node id + # %h = failed node host name + # %p = failed node port number + # %D = failed node database cluster path + # %m = new main node id + # %H = new main node hostname + # %M = old main node id + # %P = old primary node id + # %r = new main port number + # %R = new main database cluster path + # %N = old primary node hostname + # %S = old primary node port number + # %% = '%' character +#failback_command = '' + # Executes this command at failback. + # Special values: + # %d = failed node id + # %h = failed node host name + # %p = failed node port number + # %D = failed node database cluster path + # %m = new main node id + # %H = new main node hostname + # %M = old main node id + # %P = old primary node id + # %r = new main port number + # %R = new main database cluster path + # %N = old primary node hostname + # %S = old primary node port number + # %% = '%' character + +failover_on_backend_error = 'off' + # Initiates failover when reading/writing to the + # backend communication socket fails + # If set to off, pgpool will report an + # error and disconnect the session. + +failover_on_backend_shutdown = 'on' + # Initiates failover when backend is shutdown, + # or backend process is killed. + # If set to off, pgpool will report an + # error and disconnect the session. + +#detach_false_primary = off + # Detach false primary if on. Only + # valid in streaming replication + # mode and with PostgreSQL 9.6 or + # after. + +search_primary_node_timeout = '0' + # Timeout in seconds to search for the + # primary node when a failover occurs. + # 0 means no timeout, keep searching + # for a primary node forever. + +#------------------------------------------------------------------------------ +# ONLINE RECOVERY +#------------------------------------------------------------------------------ + +#recovery_user = '' + # Online recovery user +#recovery_password = '' + # Online recovery password + # Leaving it empty will make Pgpool-II to first look for the + # Password in pool_passwd file before using the empty password + +#recovery_1st_stage_command = '' + # Executes a command in first stage +#recovery_2nd_stage_command = '' + # Executes a command in second stage +#recovery_timeout = 90 + # Timeout in seconds to wait for the + # recovering node's postmaster to start up + # 0 means no wait +#client_idle_limit_in_recovery = 0 + # Client is disconnected after being idle + # for that many seconds in the second stage + # of online recovery + # 0 means no disconnection + # -1 means immediate disconnection + +#auto_failback = off + # Detached backend node reattach automatically + # if replication is 'streaming'. +#auto_failback_interval = 1min + # Min interval of executing auto_failback in + # seconds. + +#------------------------------------------------------------------------------ +# WATCHDOG +#------------------------------------------------------------------------------ + +# - Enabling - + +#use_watchdog = off + # Activates watchdog + # (change requires restart) + +# -Connection to upstream servers - + +#trusted_servers = '' + # trusted server list which are used + # to confirm network connection + # (hostA,hostB,hostC,...) + # (change requires restart) + +#trusted_server_command = 'ping -q -c3 %h' + # Command to execute when communicate trusted server. + # Special values: + # %h = host name specified by trusted_servers + +# - Watchdog communication Settings - + +#hostname0 = '' + # Host name or IP address of pgpool node + # for watchdog connection + # (change requires restart) +#wd_port0 = 9000 + # Port number for watchdog service + # (change requires restart) +#pgpool_port0 = 9999 + # Port number for pgpool + # (change requires restart) + +#hostname1 = '' +#wd_port1 = 9000 +#pgpool_port1 = 9999 + +#hostname2 = '' +#wd_port2 = 9000 +#pgpool_port2 = 9999 + +#wd_priority = 1 + # priority of this watchdog in leader election + # (change requires restart) + +#wd_authkey = '' + # Authentication key for watchdog communication + # (change requires restart) + +#wd_ipc_socket_dir = '/tmp' + # Unix domain socket path for watchdog IPC socket + # The Debian package defaults to + # /var/run/postgresql + # (change requires restart) + + +# - Virtual IP control Setting - + +#delegate_ip = '' + # delegate IP address + # If this is empty, virtual IP never bring up. + # (change requires restart) +#if_cmd_path = '/sbin' + # path to the directory where if_up/down_cmd exists + # If if_up/down_cmd starts with "/", if_cmd_path will be ignored. + # (change requires restart) +#if_up_cmd = '/usr/bin/sudo /sbin/ip addr add $_IP_$/24 dev eth0 label eth0:0' + # startup delegate IP command + # (change requires restart) +#if_down_cmd = '/usr/bin/sudo /sbin/ip addr del $_IP_$/24 dev eth0' + # shutdown delegate IP command + # (change requires restart) +#arping_path = '/usr/sbin' + # arping command path + # If arping_cmd starts with "/", if_cmd_path will be ignored. + # (change requires restart) +#arping_cmd = '/usr/bin/sudo /usr/sbin/arping -U $_IP_$ -w 1 -I eth0' + # arping command + # (change requires restart) + +#ping_path = '/bin' + # ping command path + # (change requires restart) + +# - Behavior on escalation Setting - + +#clear_memqcache_on_escalation = on + # Clear all the query cache on shared memory + # when standby pgpool escalate to active pgpool + # (= virtual IP holder). + # This should be off if client connects to pgpool + # not using virtual IP. + # (change requires restart) +#wd_escalation_command = '' + # Executes this command at escalation on new active pgpool. + # (change requires restart) +#wd_de_escalation_command = '' + # Executes this command when leader pgpool resigns from being leader. + # (change requires restart) + +# - Watchdog consensus settings for failover - + +#failover_when_quorum_exists = on + # Only perform backend node failover + # when the watchdog cluster holds the quorum + # (change requires restart) + +#failover_require_consensus = on + # Perform failover when majority of Pgpool-II nodes + # agrees on the backend node status change + # (change requires restart) + +#allow_multiple_failover_requests_from_node = off + # A Pgpool-II node can cast multiple votes + # for building the consensus on failover + # (change requires restart) + + +#enable_consensus_with_half_votes = off + # apply majority rule for consensus and quorum computation + # at 50% of votes in a cluster with even number of nodes. + # when enabled the existence of quorum and consensus + # on failover is resolved after receiving half of the + # total votes in the cluster, otherwise both these + # decisions require at least one more vote than + # half of the total votes. + # (change requires restart) + +# - Watchdog cluster membership settings for quorum computation - + +#wd_remove_shutdown_nodes = off + # when enabled cluster membership of properly shutdown + # watchdog nodes gets revoked, After that the node does + # not count towards the quorum and consensus computations + +#wd_lost_node_removal_timeout = 0s + # Timeout after which the cluster membership of LOST watchdog + # nodes gets revoked. After that the node node does not + # count towards the quorum and consensus computations + # setting timeout to 0 will never revoke the membership + # of LOST nodes + +#wd_no_show_node_removal_timeout = 0s + # Time to wait for Watchdog node to connect to the cluster. + # After that time the cluster membership of NO-SHOW node gets + # revoked and it does not count towards the quorum and + # consensus computations + # setting timeout to 0 will not revoke the membership + # of NO-SHOW nodes + + +# - Lifecheck Setting - + +# -- common -- + +#wd_monitoring_interfaces_list = '' + # Comma separated list of interfaces names to monitor. + # if any interface from the list is active the watchdog will + # consider the network is fine + # 'any' to enable monitoring on all interfaces except loopback + # '' to disable monitoring + # (change requires restart) + +#wd_lifecheck_method = 'heartbeat' + # Method of watchdog lifecheck ('heartbeat' or 'query' or 'external') + # (change requires restart) +#wd_interval = 10 + # lifecheck interval (sec) > 0 + # (change requires restart) + +# -- heartbeat mode -- + +#heartbeat_hostname0 = '' + # Host name or IP address used + # for sending heartbeat signal. + # (change requires restart) +#heartbeat_port0 = 9694 + # Port number used for receiving/sending heartbeat signal + # Usually this is the same as heartbeat_portX. + # (change requires restart) +#heartbeat_device0 = '' + # Name of NIC device (such like 'eth0') + # used for sending/receiving heartbeat + # signal to/from destination 0. + # This works only when this is not empty + # and pgpool has root privilege. + # (change requires restart) + +#heartbeat_hostname1 = '' +#heartbeat_port1 = 9694 +#heartbeat_device1 = '' +#heartbeat_hostname2 = '' +#heartbeat_port2 = 9694 +#heartbeat_device2 = '' + +#wd_heartbeat_keepalive = 2 + # Interval time of sending heartbeat signal (sec) + # (change requires restart) +#wd_heartbeat_deadtime = 30 + # Deadtime interval for heartbeat signal (sec) + # (change requires restart) + +# -- query mode -- + +#wd_life_point = 3 + # lifecheck retry times + # (change requires restart) +#wd_lifecheck_query = 'SELECT 1' + # lifecheck query to pgpool from watchdog + # (change requires restart) +#wd_lifecheck_dbname = 'template1' + # Database name connected for lifecheck + # (change requires restart) +#wd_lifecheck_user = '' + # watchdog user monitoring pgpools in lifecheck + # (change requires restart) +#wd_lifecheck_password = '' + # Password for watchdog user in lifecheck + # Leaving it empty will make Pgpool-II to first look for the + # Password in pool_passwd file before using the empty password + # (change requires restart) + +#------------------------------------------------------------------------------ +# OTHERS +#------------------------------------------------------------------------------ +#relcache_expire = 0 + # Life time of relation cache in seconds. + # 0 means no cache expiration(the default). + # The relation cache is used for cache the + # query result against PostgreSQL system + # catalog to obtain various information + # including table structures or if it's a + # temporary table or not. The cache is + # maintained in a pgpool child local memory + # and being kept as long as it survives. + # If someone modify the table by using + # ALTER TABLE or some such, the relcache is + # not consistent anymore. + # For this purpose, cache_expiration + # controls the life time of the cache. +#relcache_size = 256 + # Number of relation cache + # entry. If you see frequently: + # "pool_search_relcache: cache replacement occurred" + # in the pgpool log, you might want to increase this number. + +#check_temp_table = catalog + # Temporary table check method. catalog, trace or none. + # Default is catalog. + +#check_unlogged_table = on + # If on, enable unlogged table check in SELECT statements. + # This initiates queries against system catalog of primary/main + # thus increases load of primary. + # If you are absolutely sure that your system never uses unlogged tables + # and you want to save access to primary/main, you could turn this off. + # Default is on. +#enable_shared_relcache = on + # If on, relation cache stored in memory cache, + # the cache is shared among child process. + # Default is on. + # (change requires restart) + +#relcache_query_target = primary + # Target node to send relcache queries. Default is primary node. + # If load_balance_node is specified, queries will be sent to load balance node. +#------------------------------------------------------------------------------ +# IN MEMORY QUERY MEMORY CACHE +#------------------------------------------------------------------------------ +#memory_cache_enabled = off + # If on, use the memory cache functionality, off by default + # (change requires restart) +#memqcache_method = 'shmem' + # Cache storage method. either 'shmem'(shared memory) or + # 'memcached'. 'shmem' by default + # (change requires restart) +#memqcache_memcached_host = 'localhost' + # Memcached host name or IP address. Mandatory if + # memqcache_method = 'memcached'. + # Defaults to localhost. + # (change requires restart) +#memqcache_memcached_port = 11211 + # Memcached port number. Mandatory if memqcache_method = 'memcached'. + # Defaults to 11211. + # (change requires restart) +#memqcache_total_size = 64MB + # Total memory size in bytes for storing memory cache. + # Mandatory if memqcache_method = 'shmem'. + # Defaults to 64MB. + # (change requires restart) +#memqcache_max_num_cache = 1000000 + # Total number of cache entries. Mandatory + # if memqcache_method = 'shmem'. + # Each cache entry consumes 48 bytes on shared memory. + # Defaults to 1,000,000(45.8MB). + # (change requires restart) +#memqcache_expire = 0 + # Memory cache entry life time specified in seconds. + # 0 means infinite life time. 0 by default. + # (change requires restart) +#memqcache_auto_cache_invalidation = on + # If on, invalidation of query cache is triggered by corresponding + # DDL/DML/DCL(and memqcache_expire). If off, it is only triggered + # by memqcache_expire. on by default. + # (change requires restart) +#memqcache_maxcache = 400kB + # Maximum SELECT result size in bytes. + # Must be smaller than memqcache_cache_block_size. Defaults to 400KB. + # (change requires restart) +#memqcache_cache_block_size = 1MB + # Cache block size in bytes. Mandatory if memqcache_method = 'shmem'. + # Defaults to 1MB. + # (change requires restart) +#memqcache_oiddir = '/var/log/pgpool/oiddir' + # Temporary work directory to record table oids + # (change requires restart) +#cache_safe_memqcache_table_list = '' + # Comma separated list of table names to memcache + # that don't write to database + # Regexp are accepted +#cache_unsafe_memqcache_table_list = '' + # Comma separated list of table names not to memcache + # that don't write to database + # Regexp are accepted +backend_hostname0 = 'pg-primary' +backend_port0 = 5432 +backend_weight0 = 1 +backend_data_directory0 = '/opt/bitnami/pgpool/data' +backend_flag0 = 'ALLOW_TO_FAILOVER' +backend_hostname1 = 'pg-replica-1' +backend_port1 = 5432 +backend_weight1 = 1 +backend_data_directory1 = '/opt/bitnami/pgpool/data' +backend_flag1 = 'ALLOW_TO_FAILOVER' +backend_hostname2 = 'pg-replica-2' +backend_port2 = 5432 +backend_weight2 = 1 +backend_data_directory2 = '/opt/bitnami/pgpool/data' +backend_flag2 = 'ALLOW_TO_FAILOVER' diff --git a/dockerfiles/postgres-cluster/primary/init.sh b/dockerfiles/postgres-cluster/primary/init.sh new file mode 100755 index 0000000..97b9d02 --- /dev/null +++ b/dockerfiles/postgres-cluster/primary/init.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +chown -R postgres:postgres /var/lib/postgresql + +echo "host replication all 0.0.0.0/0 md5" >> /var/lib/postgresql/data/pg_hba.conf +echo "host all all 0.0.0.0/0 md5" >> /var/lib/postgresql/data/pg_hba.conf + +exec gosu postgres postgres -c config_file=/etc/postgresql/postgresql.conf diff --git a/dockerfiles/postgres-cluster/primary/postgres.conf b/dockerfiles/postgres-cluster/primary/postgres.conf new file mode 100644 index 0000000..8d9e66f --- /dev/null +++ b/dockerfiles/postgres-cluster/primary/postgres.conf @@ -0,0 +1,7 @@ +listen_addresses = '*' +wal_level = replica +max_wal_senders = 10 +wal_keep_size = 512 +hot_standby = on +synchronous_commit = remote_apply +synchronous_standby_names = 'FIRST 2 (pg-replica-1, pg-replica-2)' \ No newline at end of file diff --git a/dockerfiles/postgres-cluster/test.sh b/dockerfiles/postgres-cluster/test.sh new file mode 100755 index 0000000..2c7ce40 --- /dev/null +++ b/dockerfiles/postgres-cluster/test.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +echo "▶️ Starting PostgreSQL Replication Test..." + +# 1. Insert data into PRIMARY +echo "🔹 Inserting data into primary..." +docker exec -i pg-primary psql -U admin -d admin <