Replication 구성으로 얻을 수 있는 장점으론 당연히 장애로부터 자유(?)로워 질 수 있는 부분도 있지만 서비스적인 측면으로 봤을 때
- 마스터는 Write(저장, 수정, 삭제)로 활용
- 슬레이브는 Read(조회)로 활용
볼 수 있다. 이렇게 분리하면
- 장애 시 서비스에 끼치는 영향도를 낮춤 단일 서버 구성 시 장애가 발생 되면 저장, 삭제, 수정, 조회등 전체적인 서비스에 영향이 발생 되지만 분리 시에는 장애 발생 서버를 제외한 서비스는 가능하다.
- 퍼포먼스 향상 Read와 Write 작업을 분리함으로서 서로 간에 작업 영향도를 최대한 격리하여 퍼포먼스를 향상 시킬 수 있다.
Postgresql(이하 pg)의 Replication은 WAL(write-ahead logging)을 활용하며 pg에선 WAL로 생성된 로그파일을 아래 흐름으로 Replication을 구성한다.
- 마스터 서버의 모든 작업을 로그로 생성
- 마스터의 작업 로그를 슬레이브 서버로 전달
- 로그를 전달 받은 슬레이브 서버에선 복원(재실행)
환경
- Postgresql 11
- Window 11 - WSL2
- Dockek
- docker-compose
WAL 전달 방식
WAL의 전달 방식은 2가지로 나뉜다.
로그 파일은 $PG_SQL/data/pg_xlog
디렉토리에 쌓이게 된다.
Log-Shipping
pg_xlog
디렉토리의 WAL 파일 자체를 슬레이브 서버에 전달(File Copy)
Streaming
WAL 파일 여부와 상관없이 로그 내용을 Streaming 형식으로 슬레이브 서버로 전달
설정
Replication 설정의 대략적인 흐름을 설명 하자면
마스터 서버에선
- Relication 전용 User 생성 - query
- WAL 관련 설정 - postgresql.conf
- 생성한 사용자 계정 접근 권한 설정 - pg_hba.conf
슬레이브 서버에선
- 마스터 서버 백업(복제/복원) - pg_basebackup 명령어
- WAL 관련 설정 - postgresql.conf
- recovery.conf 설정
이다.
docker-compose.yml
마스터 서버는
- Dockerfile.primary 실행
- Dockerfile.primary의 ENTRYPOINT에서 setup-primary.sh 실행
슬레이브 서버는
- Dockerfile.readonly 실행
- Dockerfile.readonly의 ENTRYPOINT에서 setup-readonly.sh 실행
순서를 가진다.
version: "3"
services:
pg_primary:
build:
context: ./docker/pg
dockerfile: Dockerfile.primary
command: postgres -c log_destination=stderr -c log_statement=all
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- ./pg_primary_data:/var/lib/postgresql/data
ports:
- "5445:5432"
pg_readonly:
build:
context: ./docker/pg
dockerfile: Dockerfile.readonly
command: postgres -c log_destination=stderr -c log_statement=all
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- ./pg_readonly_data:/var/lib/postgresql/data
depends_on:
- pg_primary
ports:
- "5446:5432"
Dockerfile
마스터 - Dockerfile.primary
FROM postgres:11-alpine
ENV LANG C.UTF-8
COPY ./setup-primary.sh /docker-entrypoint-initdb.d/setup-primary.sh
RUN chmod 0666 /docker-entrypoint-initdb.d/setup-primary.sh
슬레이브 - Dockerfile.readonly
FROM postgres:11-alpine
ENV LANG C.UTF-8
ENV ENTRYKIT_VERSION 0.4.0
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& mv entrykit /bin/entrykit \
&& chmod +x /bin/entrykit \
&& entrykit --symlink
RUN apk --update add openssh-client && rm -rf /var/cache/apk/*
COPY ./setup-readonly.sh /setup-readonly.sh
RUN chmod +x /setup-readonly.sh
ENTRYPOINT [ \
"prehook", \
"/setup-readonly.sh", \
"--", \
"docker-entrypoint.sh" \
]
CMD ["postgres"]
슬레이브 서버 경우 마스터 서버와 다르게 아래와 같이 entrykit을 다운 받는다.(https://github.com/progrium/entrykit/)
ENV ENTRYKIT_VERSION 0.4.0
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
&& mv entrykit /bin/entrykit \
&& chmod +x /bin/entrykit \
&& entrykit --symlink
entrykit은 Dockerfile의 ENTRYPOINT를 확장하여 다양한 기능을 사용할 수 있게 해준다.
여기선 prehook를 사용하였는데 이는 슬레이브 서버가 시작 되기 전에
- 마스터 서버 정상 실행 여부
- pg_basebackup 명령어로 마스터 서버 백업(복제/복원)
- postgresql.conf 및 recovery.conf 설정
실행 한 후 슬레이브 서버가 시작 된다.
Shell
마스터 - setup-primary.sh
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE ROLE replication_user LOGIN REPLICATION PASSWORD 'replicationpassword';
EOSQL
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
EOSQL
mkdir $PGDATA/archive
cat >> "$PGDATA/postgresql.conf" <<EOF
wal_level = hot_standby
max_wal_senders = 10
max_replication_slots = 10
synchronous_commit = off
EOF
echo "host replication replication_user 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf"
setup-master.sh에선 위 상단에 설명한대로
- Relication 전용 User 생성 - query
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE ROLE replication_user LOGIN REPLICATION PASSWORD 'replicationpassword';
EOSQL
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
EOSQL
- 생성한 사용자 계정 접근 권한 설정 - pg_hba.conf
echo "host replication replication_user 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf"
실행한다.
슬레이브 - setup-readonly.sh
#!/bin/bash
set -e
if [ ! -s "$PGDATA/PG_VERSION" ]; then
echo "*:*:*:replication_user:replicationpassword" > ~/.pgpass
chmod 0600 ~/.pgpass
until ping -c 1 -W 1 pg_primary
do
echo "Waiting for primary to ping..."
sleep 1s
done
until pg_basebackup -h pg_primary -D ${PGDATA} -U replication_user -vP -W
do
echo "Waiting for primary to connect..."
sleep 1s
done
sed -i 's/wal_level = hot_standby/wal_level = replica/g' ${PGDATA}/postgresql.conf
cat > ${PGDATA}/recovery.conf <<EOF
standby_mode = on
primary_conninfo = 'host=pg_primary port=5432 user=replication_user password=replicationpassword application_name=pg_readonly'
primary_slot_name = 'node_a_slot'
EOF
chown postgres:postgres ${PGDATA} -R
chmod 700 ${PGDATA} -R
fi
setup-readonly.sh은 슬레이브 서버 시작 전에 아래와 같이 마스터 서버가 정상적으로 실행 되었는지 확인한다.
until ping -c 1 -W 1 pg_primary
do
echo "Waiting for primary to ping..."
sleep 1s
done
그 후 아래와 같이
- 마스터 서버 백업(복제/복원) - pg_basebackup 명령어
until pg_basebackup -h pg_primary -D ${PGDATA} -U replication_user -vP -W
do
echo "Waiting for primary to connect..."
sleep 1s
done
- WAL 관련 설정 - postgresql.conf
sed -i 's/wal_level = hot_standby/wal_level = replica/g' ${PGDATA}/postgresql.conf
- recovery.conf 설정
cat > ${PGDATA}/recovery.conf <<EOF
standby_mode = on
primary_conninfo = 'host=pg_primary port=5432 user=replication_user password=replicationpassword application_name=pg_readonly'
primary_slot_name = 'node_a_slot'
EOF
실행한다.
실행
해당 docker-compose.yml이 있는 경로에서
docker-compose -f docker-compose.yml up -d
명령어 실행 한 후
docker ps
위 명령어로 아래와 같이 컨테이너가 정상적으로 실행 되는지 확인한다.
아래 출력 결과는 실제와 상이하며 정상 실행 여부는 STATUS 상태가 UP인지 확인한다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1d942fcd16a7 pg_repl_pg_readonly "prehook /setup-read…" 4 hours ago Up 4 hours 0.0.0.0:5446->5432/tcp pg_repl-pg_readonly-1
7d4eb0a9d4f8 pg_repl_pg_primary "docker-entrypoint.s…" 4 hours ago Up 4 hours 0.0.0.0:5445->5432/tcp pg_repl-pg_primary-1
확인
테이블 생성
마스터 서버에서 테이블 및 컬럼 생성 후 슬레이브에서도 동일하게 생성되었는지 확인한다.
마스터 서버
아래 명령어로 마스터 서버 컨테이너의 CONTAINER ID 혹은 NAMES을 확인한다.
docker ps
필자의 docker 상태로 예로 들자면 마스터 서버의 컨테이너 ID는 7d4eb0a9d4f8 이고 NAMES는 pg_repl-pg_primary-1이다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1d942fcd16a7 pg_repl_pg_readonly "prehook /setup-read…" 4 hours ago Up 4 hours 0.0.0.0:5446->5432/tcp pg_repl-pg_readonly-1
7d4eb0a9d4f8 pg_repl_pg_primary "docker-entrypoint.s…" 4 hours ago Up 4 hours 0.0.0.0:5445->5432/tcp pg_repl-pg_primary-1
그 후 아래 명령어로 컨테이너로 접속한다.
docker exec -it `CONTAINER ID 혹은 NAMES` /bin/bash
정상적으로 접속하였으면 아래 psql로 postgresql 서버에 접속한다.
psql -U postgres -d postgres
그리고 아래와 같이 테스트 테이블을 생성한다.
create table repl (name varchar(10));
슬레이브 서버
위 마스터를 참고하여 슬레이브의 postgresql 서버까지 접속한다. 접속 후 아래 명령어로 테이블이 정상적으로 생성 되어있는지 확인한다.
\dt
정상적이라면 아래와 같이 repl 테이블이 조회된다.
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | repl | table | postgres
(1 row)
select * from repl;
명령어로 name 컬럼도 정상적으로 생성 되었는지 확인한다. 정상적이면 아래와 같은 결과가 출력된다.
데이터 저장, 수정, 삭제
마스터 서버에서 데이터 저장, 수정, 삭제 실행 하고 슬레이브 서버에서도 결과가 동일한지 확인한다.
저장
마스터 서버
위 테이블 생성 부분을 참고하여 마스터의 postgresql 서버까지 접속한다. 그 후 아래와 같이 Insert 문을 실행한다.
insert into repl(name) values ('devsull');
슬레이브 서버
슬레이브 서버의 postgresql 서버에서 select * from repl; 쿼리를 실행하여 아래와 같이 정상적으로 동일한 데이터가 저장 되었는지 확인한다.
postgres=# select * from repl;
name
---------
devsull
(1 row)
수정
마스터 서버
마스터의 postgresql 서버에서 아래와 같이 Update 문을 실행한다.
update repl set name = 'good';
슬레이브 서버
슬레이브 서버의 postgresql 서버에서 위와 동일하게 Select문을 수행하여 데이터가 변경 되었는지 확인한다.
postgres=# select * from repl;
name
------
good
(1 row)
삭제
마스터 서버
마스터의 postgresql 서버에서 아래와 같이 Delete 문을 실행한다.
delete from repl where name = 'good';
슬레이브 서버
슬레이브 서버의 postgresql 서버에서 위와 동일하게 Select문을 수행하여 데이터가 삭제 되었는지 확인한다.
postgres=# select * from repl;
name
------
(0 rows)
위 몇 가지의 확인 과정들이 정상적이면 일단은(?) pg의 replication 구성이 정상적으로 된 것이다.
본 글에 사용된 설정 파일은 GitHub - sungwookkim/postg11repl에서 확인 할 수 있다.
그리고 다중 데이터소스 관련 Spring 설정은
다중 데이터소스 관련 Service 구현은
Spring Multi Datasource(다중 데이터소스)를 활용한 Service 구현 :: 신나게의 개발썰
Spring Multi Datasource(다중 데이터소스)를 활용한 Service 구현-2 :: 신나게의 개발썰
에서 참고하실 수 있습니다.
참고
WAL이란? :
https://ko.wikipedia.org/wiki/%EB%A1%9C%EA%B7%B8_%EC%84%A0%ED%96%89_%EA%B8%B0%EC%9E%85
Postgresql Replication 구축 및 설명 :
https://medium.com/qodot/postgresql-replication-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-dfd481c4fb04
https://brunch.co.kr/@daniellim/40
http://kimchki.blogspot.com/2019/09/postgresql-replication.html
Postgresql 11 docker-compose 버전 :