Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/jpa-boot-read-replica-postgresql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
cache: "maven"
- name: Start up databases via docker compose
run: |
docker compose up -d postgresql-master postgresql-slave
docker compose -f docker/docker-compose.yml up -d postgresql-master postgresql-slave
sleep 5
docker ps -a
- name: Build and analyze
Comment on lines 35 to 39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use healthchecks and --wait to deflake CI; drop arbitrary sleep.

Rely on Compose health status rather than sleep 5.

-          docker compose -f docker/docker-compose.yml up -d postgresql-master postgresql-slave
-          sleep 5
+          docker compose -f docker/docker-compose.yml up -d --wait
           docker ps -a

Follow-up: add healthchecks in the compose file (see next comment).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: |
docker compose up -d postgresql-master postgresql-slave
docker compose -f docker/docker-compose.yml up -d postgresql-master postgresql-slave
sleep 5
docker ps -a
- name: Build and analyze
run: |
docker compose -f docker/docker-compose.yml up -d --wait
docker ps -a
🤖 Prompt for AI Agents
.github/workflows/jpa-boot-read-replica-postgresql.yml lines 35-39: the workflow
uses a fixed sleep to wait for DBs which flakes; replace the arbitrary sleep
with compose health-based waiting and remove the sleep. Update the `docker
compose` invocation to use the built-in wait behavior (e.g. `docker compose up
-d --wait` or `docker compose up -d` followed by a compose wait/health-check
loop) so the step blocks until postgresql-master and postgresql-slave are
healthy, and ensure the docker-compose.yml defines proper healthchecks for those
services; drop `sleep 5` and add a sensible timeout/fail behavior if services
don’t become healthy.

Expand Down
10 changes: 10 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,16 @@
"projectName": "boot-scheduler-quartz",
"args": "--spring.profiles.active=local",
"envFile": "${workspaceFolder}/.env"
},
{
"type": "java",
"name": "Spring Boot-ReadReplicaApplication<boot-read-replica-postgresql>",
"request": "launch",
"cwd": "${workspaceFolder}",
"mainClass": "com.example.demo.readreplica.ReadReplicaApplication",
"projectName": "boot-read-replica-postgresql",
"args": "",
"envFile": "${workspaceFolder}/.env"
}
]
}
46 changes: 0 additions & 46 deletions jpa/boot-read-replica-postgresql/docker-compose.yaml

This file was deleted.

25 changes: 25 additions & 0 deletions jpa/boot-read-replica-postgresql/docker/configure-master.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
set -euo pipefail

echo "Configuring PostgreSQL master for replication..."

# Update postgresql.conf for replication
cat >> "$PGDATA/postgresql.conf" <<EOF

# Replication settings
wal_level = replica
max_wal_senders = 3
max_replication_slots = 3
hot_standby = on
hot_standby_feedback = on
listen_addresses = '*'
EOF
Comment on lines +7 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add essential PG settings for modern auth and dev replication.

Ensure SCRAM and retain WAL for short outages.

 cat >> "$PGDATA/postgresql.conf" <<EOF
 
 # Replication settings
 wal_level = replica
 max_wal_senders = 3
 max_replication_slots = 3
 hot_standby = on
 hot_standby_feedback = on
 listen_addresses = '*'
+password_encryption = scram-sha-256
+wal_keep_size = 64MB
 EOF
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cat >> "$PGDATA/postgresql.conf" <<EOF
# Replication settings
wal_level = replica
max_wal_senders = 3
max_replication_slots = 3
hot_standby = on
hot_standby_feedback = on
listen_addresses = '*'
EOF
cat >> "$PGDATA/postgresql.conf" <<EOF
# Replication settings
wal_level = replica
max_wal_senders = 3
max_replication_slots = 3
hot_standby = on
hot_standby_feedback = on
listen_addresses = '*'
password_encryption = scram-sha-256
wal_keep_size = 64MB
EOF
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/configure-master.sh around lines 7 to
16, the generated postgresql.conf lacks modern auth and WAL-retention settings;
update the appended block to set password_encryption = 'scram-sha-256' to enable
SCRAM authentication and add a small WAL retention like wal_keep_size = 64MB (or
a size appropriate for your outage window) so standby replicas can catch up
after short outages; keep the existing replication settings and ensure these two
lines are added to the same heredoc so they are written into postgresql.conf.


# Update pg_hba.conf to allow replication connections
cat >> "$PGDATA/pg_hba.conf" <<EOF

# Replication connections
host replication repl_user 0.0.0.0/0 md5
EOF
Comment on lines +19 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use SCRAM and avoid world-open CIDR for replication access.

md5 will fail if users are stored with SCRAM (default in newer Postgres). Also, 0.0.0.0/0 is unnecessarily broad; restrict to your Docker network or make it configurable.

 cat >> "$PGDATA/pg_hba.conf" <<EOF
 
 # Replication connections
-host replication repl_user 0.0.0.0/0 md5
+# Prefer a narrowed CIDR (e.g., 172.18.0.0/16) or pass REPLICA_CIDR via env.
+host replication repl_user ${REPLICA_CIDR:-172.18.0.0/16} scram-sha-256
 EOF
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cat >> "$PGDATA/pg_hba.conf" <<EOF
# Replication connections
host replication repl_user 0.0.0.0/0 md5
EOF
cat >> "$PGDATA/pg_hba.conf" <<EOF
# Replication connections
# Prefer a narrowed CIDR (e.g., 172.18.0.0/16) or pass REPLICA_CIDR via env.
host replication repl_user ${REPLICA_CIDR:-172.18.0.0/16} scram-sha-256
EOF
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/configure-master.sh around lines 19
to 23, the pg_hba.conf entry uses "md5" and "0.0.0.0/0"; update it to use
"scram-sha-256" (to match modern Postgres SCRAM password storage) and restrict
the allowed CIDR to a more specific network or a configurable environment
variable (e.g., use an env var like REPL_NETWORK with a sensible default) so
replication access is not world-open; also ensure repl_user is
created/configured to use SCRAM-authenticated password in the setup scripts.


echo "Master configuration complete"
62 changes: 62 additions & 0 deletions jpa/boot-read-replica-postgresql/docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: read-replica-postgresql
services:
postgresql-master:
image: 'postgres:17'
ports:
- '5432:5432'
volumes:
- 'postgresql_master_data:/var/lib/postgresql/data'
- './init-master.sql:/docker-entrypoint-initdb.d/01-init-master.sql:ro'
- './configure-master.sh:/docker-entrypoint-initdb.d/02-configure-master.sh:ro'
environment:
- POSTGRES_USER=postgres_write
- POSTGRES_PASSWORD=postgres_write
- POSTGRES_DB=my_database
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres_write -d my_database -h 127.0.0.1 -p 5432"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
postgresql-slave:
image: 'postgres:17'
ports:
- '15432:5432'
depends_on:
postgresql-master:
condition: service_healthy
volumes:
- 'postgresql_slave_data:/var/lib/postgresql/data'
- './setup-slave.sh:/setup-slave.sh:ro'
environment:
- POSTGRES_USER=postgres_write
- POSTGRES_PASSWORD=postgres_write
- PGUSER=postgres_write
- POSTGRES_MASTER_HOST=postgresql-master
- POSTGRES_MASTER_PORT=5432
- REPLICATION_USER=repl_user
- REPLICATION_PASSWORD=repl_password
entrypoint: ["/setup-slave.sh"]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres_write -h 127.0.0.1 -p 5432"]
interval: 5s
timeout: 3s
retries: 20
start_period: 15s

pgadmin4:
image: dpage/pgadmin4
ports:
- "5050:80"
depends_on:
postgresql-master:
condition: service_healthy
environment:
- [email protected]
- PGADMIN_DEFAULT_PASSWORD=admin

volumes:
postgresql_master_data:
driver: local
postgresql_slave_data:
driver: local
18 changes: 18 additions & 0 deletions jpa/boot-read-replica-postgresql/docker/init-master.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- Create replication user
CREATE USER repl_user WITH REPLICATION ENCRYPTED PASSWORD 'repl_password';

Comment on lines +1 to +3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Create replication user with SCRAM; avoid deprecated ENCRYPTED keyword.

Rely on server password_encryption=scram-sha-256 and use PASSWORD.

--- Create replication user
-CREATE USER repl_user WITH REPLICATION ENCRYPTED PASSWORD 'repl_password';
+CREATE USER repl_user WITH REPLICATION PASSWORD 'repl_password';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Create replication user
CREATE USER repl_user WITH REPLICATION ENCRYPTED PASSWORD 'repl_password';
-- Create replication user
CREATE USER repl_user WITH REPLICATION PASSWORD 'repl_password';
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/init-master.sql around lines 1 to 3,
the CREATE USER uses the deprecated ENCRYPTED keyword; change it to use PASSWORD
so the server applies SCRAM-SHA-256 per password_encryption. Replace "CREATE
USER repl_user WITH REPLICATION ENCRYPTED PASSWORD 'repl_password';" with a
statement that uses "WITH REPLICATION PASSWORD 'repl_password';" and ensure the
PostgreSQL instance has password_encryption=scram-sha-256 so the password is
stored with SCRAM.

-- Grant necessary permissions
GRANT CONNECT ON DATABASE my_database TO repl_user;
GRANT USAGE ON SCHEMA public TO repl_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO repl_user;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO repl_user;
Comment on lines +4 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Separate app read-only user from replication user.

Don’t use the replication role for application queries. Create a distinct least-privileged user.

 -- Grant necessary permissions
-GRANT CONNECT ON DATABASE my_database TO repl_user;
-GRANT USAGE ON SCHEMA public TO repl_user;
-GRANT SELECT ON ALL TABLES IN SCHEMA public TO repl_user;
-GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO repl_user;
+CREATE USER app_readonly PASSWORD 'readonly_password';
+GRANT CONNECT ON DATABASE my_database TO app_readonly;
+GRANT USAGE ON SCHEMA public TO app_readonly;
+GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;
+GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO app_readonly;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_readonly;

Follow-up: point spring.replica.datasource.username to app_readonly in application.yml.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Grant necessary permissions
GRANT CONNECT ON DATABASE my_database TO repl_user;
GRANT USAGE ON SCHEMA public TO repl_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO repl_user;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO repl_user;
-- Grant necessary permissions
CREATE USER app_readonly PASSWORD 'readonly_password';
GRANT CONNECT ON DATABASE my_database TO app_readonly;
GRANT USAGE ON SCHEMA public TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO app_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_readonly;
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/init-master.sql around lines 4 to 8:
currently the replication role is being used for application read queries;
create a distinct least-privileged app_readonly user instead of using the
replication user, grant it CONNECT on my_database, USAGE on schema public, and
SELECT on all tables and sequences (and set any necessary default privileges for
future tables), keep repl_user solely for replication, and then update
application.yml to point spring.replica.datasource.username to app_readonly (and
set its password) so the app uses the new read-only role.


-- Update pg_hba.conf to allow replication connections
-- This will be added to the end of pg_hba.conf by PostgreSQL init process
DO $$
BEGIN
-- Add replication entry to pg_hba.conf
-- Note: The official postgres image handles pg_hba.conf configuration
-- The replication connection will be allowed via the default settings
RAISE NOTICE 'Replication user created successfully';
END $$;
49 changes: 49 additions & 0 deletions jpa/boot-read-replica-postgresql/docker/setup-slave.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
set -Eeuo pipefail

export PGDATA=/var/lib/postgresql/data

echo "Setting up PostgreSQL slave..."

# Wait for master to be ready
echo "Waiting for master to be ready..."
until PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRES_MASTER_HOST" -p "$POSTGRES_MASTER_PORT" -U "$POSTGRES_USER" -d "my_database" -c '\l' > /dev/null 2>&1
do
echo "Waiting for master database..."
sleep 3
done

echo "Master is ready. Checking if slave data exists..."

if [ "$(ls -A $PGDATA 2>/dev/null)" ]; then
echo "Data directory exists, starting PostgreSQL..."
# Change ownership and start as postgres user
chown -R postgres:postgres "$PGDATA"
exec gosu postgres postgres -c hot_standby=on -c hot_standby_feedback=on
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pass data dir explicitly to postgres.

Be explicit with -D "$PGDATA"; relying on environment here is brittle.

-    exec gosu postgres postgres -c hot_standby=on -c hot_standby_feedback=on
+    exec gosu postgres postgres -D "$PGDATA" -c hot_standby=on -c hot_standby_feedback=on
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exec gosu postgres postgres -c hot_standby=on -c hot_standby_feedback=on
exec gosu postgres postgres -D "$PGDATA" -c hot_standby=on -c hot_standby_feedback=on
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/setup-slave.sh around line 22, the
postgres invocation should explicitly pass the data directory; replace the
existing exec line with one that adds -D "$PGDATA" before the -c options (i.e.
exec gosu postgres postgres -D "$PGDATA" -c hot_standby=on -c
hot_standby_feedback=on), preserving the existing flags and ensuring PGDATA is
quoted.

else
Comment on lines +18 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make the data-dir check robust and quote variables.

ls -A on a non-PG dir can false-positive. Check PG_VERSION instead and quote $PGDATA.

-if [ "$(ls -A $PGDATA 2>/dev/null)" ]; then
+if [ -s "$PGDATA/PG_VERSION" ]; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ "$(ls -A $PGDATA 2>/dev/null)" ]; then
echo "Data directory exists, starting PostgreSQL..."
# Change ownership and start as postgres user
chown -R postgres:postgres "$PGDATA"
exec gosu postgres postgres -c hot_standby=on -c hot_standby_feedback=on
else
if [ -s "$PGDATA/PG_VERSION" ]; then
echo "Data directory exists, starting PostgreSQL..."
# Change ownership and start as postgres user
chown -R postgres:postgres "$PGDATA"
exec gosu postgres postgres -c hot_standby=on -c hot_standby_feedback=on
else
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/setup-slave.sh around lines 18 to 23,
the current data-dir check using ls -A can return false positives and doesn't
quote variables; replace it with a robust existence check for the PostgreSQL
version file and quote PGDATA. Specifically, test for the presence of
"$PGDATA/PG_VERSION" (e.g. if [ -f "$PGDATA/PG_VERSION" ]; then) and ensure all
references to PGDATA are quoted (e.g. "$PGDATA") before chown and when deciding
to start PostgreSQL.

echo "Data directory is empty, creating base backup..."

# Create base backup from master
PGPASSWORD="$REPLICATION_PASSWORD" pg_basebackup \
-h "$POSTGRES_MASTER_HOST" \
-p "$POSTGRES_MASTER_PORT" \
-U "$REPLICATION_USER" \
-D "$PGDATA" \
-W \
-v \
-R \
-X stream
Comment on lines +27 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove -W; it forces an interactive password prompt and can hang CI.

pg_basebackup -W ignores PGPASSWORD and demands a TTY; you already export PGPASSWORD. Drop -W.

-        -W \
         -v \
         -R \
         -X stream
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PGPASSWORD="$REPLICATION_PASSWORD" pg_basebackup \
-h "$POSTGRES_MASTER_HOST" \
-p "$POSTGRES_MASTER_PORT" \
-U "$REPLICATION_USER" \
-D "$PGDATA" \
-W \
-v \
-R \
-X stream
PGPASSWORD="$REPLICATION_PASSWORD" pg_basebackup \
-h "$POSTGRES_MASTER_HOST" \
-p "$POSTGRES_MASTER_PORT" \
-U "$REPLICATION_USER" \
-D "$PGDATA" \
-v \
-R \
-X stream
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/setup-slave.sh around lines 27 to 35,
the pg_basebackup invocation includes the -W flag which forces an interactive
password prompt (ignoring PGPASSWORD) and can hang CI; remove the -W flag from
the command so PGPASSWORD is used non-interactively, and verify PGPASSWORD is
exported or set in the environment before calling pg_basebackup.


# Set proper permissions
chmod 700 "$PGDATA"
chown -R postgres:postgres "$PGDATA"

# Add standby configuration
cat >> "$PGDATA/postgresql.conf" <<EOF
hot_standby = on
hot_standby_feedback = on
EOF

echo "Base backup complete. Starting PostgreSQL in standby mode..."
exec gosu postgres postgres
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Same: start with -D.

-    exec gosu postgres postgres
+    exec gosu postgres postgres -D "$PGDATA"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exec gosu postgres postgres
exec gosu postgres postgres -D "$PGDATA"
🤖 Prompt for AI Agents
In jpa/boot-read-replica-postgresql/docker/setup-slave.sh around line 48, the
postgres process is started without the data-directory flag; update the exec
line to start the postgres server with the same -D data-directory argument used
elsewhere (e.g., pass -D followed by the PGDATA path variable) so the server
uses the correct data directory when launched under gosu postgres.

fi
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.example.demo.readreplica;

import java.util.TimeZone;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ReadReplicaApplication {

public static void main(String[] args) {
// Set default timezone to UTC to avoid timezone conflicts
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
SpringApplication.run(ReadReplicaApplication.class, args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ spring:
password: postgres_write
username: postgres_write
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/my_database
url: jdbc:postgresql://localhost:5432/my_database?options=-c%20timezone=UTC
hikari:
auto-commit: false
pool-name: primaryHikariPool
data-source-properties:
ApplicationName: ${spring.application.name}
serverTimezone: UTC
replica:
datasource:
password: repl_password
username: repl_user
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://localhost:15432/my_database
url: jdbc:postgresql://localhost:15432/my_database?options=-c%20timezone=UTC
hikari:
auto-commit: false
poolName: replicaHikariPool
data-source-properties:
serverTimezone: UTC
################ Database #####################
data:
jpa:
Expand Down