버전 3.08.0, MySQL 8.0.39와 호환

 

내부 임시 테이블에서 사용하는 메모리 양을 표시하기 위해 aurora_temptable_ram_allocation 및 라는 두 가지 전역 상태 변수를 추가했습니다aurora_temptable_max_ram_allocation. 이러한 전역 상태 변수는 관찰성을 높이고 내부 임시 테이블 메모리 사용과 관련된 문제를 진단하는 데 도움이 됩니다.

 

https://dev.mysql.com/doc/refman/8.0/en/internal-temporary-tables.html

 

MySQL :: MySQL 8.0 Reference Manual :: 10.4.4 Internal Temporary Table Use in MySQL

10.4.4 Internal Temporary Table Use in MySQL In some cases, the server creates internal temporary tables while processing statements. Users have no direct control over when this occurs. The server creates temporary tables under conditions such as these: E

dev.mysql.com

 

https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.3080.html

반응형

'AWS > Aurora' 카테고리의 다른 글

[Aurora mysql v3] innodb_flush_log_at_trx_commit check  (0) 2024.06.13

Temporary tables 관련 이슈

https://bugs.mysql.com/bug.php?id=98974

 

MySQL Bugs: #98974: InnoDB temp table could hurt InnoDB perf badly

 

bugs.mysql.com

 

반응형

'MySQL' 카테고리의 다른 글

[Percona] pt-query-digest 사용 방법  (0) 2021.10.28
[MySQL] InnoDB Adaptive Hash index [펌]  (0) 2018.04.16
[MySQL] ARCHIVE Engine  (0) 2017.07.19
[펌][MySQL] CockroachDB in Comparison  (0) 2017.07.03
[펌] [MyISAM] myisamchk 사용하기  (0) 2016.11.25

[OpenAI 의 도움과 Git 내용 들을 종합하여 작성한 글입니다.]

설치 과정 : https://hyunki1019.tistory.com/201

 

Patroni git : https://github.com/patroni/patroni

 

GitHub - patroni/patroni: A template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes

A template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes - patroni/patroni

github.com

 

Patroni?

  • PostgreSQL 의 HA (High Availability) 를 지원하는 Open Source
  • 자동 장애를 감지하여 자동으로 Failover 지원 및 Replication 관리 등을 제공하여 서비스 중단을 최소화
    • maximum_lag_on_failover 기준에 따라 Failover 여부를 결정
  • PostgreSQL cluster를 쉽고 유연하게 구축을 지원하며, Python 기반
  • 자체적으로 Leader 를 선출(promotion)하는데, 이때 Distributed Configuration Store(다양한 분산 설정 저장소-ZooKeeper, etcd, Consul, Kubernetes 등)를 활용하여 Cluster leader(primary) 를 선출
    • 주로 Cluster status, Leader Info 등의 정보를 저장하는 용도이며, 모든 node가 해당 정보를 공유
    • RAFT 합의 알고리즘을 사용(대부분 election 관련한 것은 RAFT 알고리즘을 사용하는 듯 - MongoDB...)
  • PostgreSQL 인스턴스 startup / stop / restart 를 직접 관리하며, replication 생성을 위한 설정 변경 등도 자동으로 지원
  • Rest API를 제공하여 노드별 관리 및 상태 확인, 수동으로 장애조치(Manual Failover) 등을 지원
    • patronictl 명령어로 클러스터 상태 모니터링, switchover(수동 리더 전환), manual failover(강제 장애조치) 등 수행 가능
  • Cloud 환경 지원 (AWS, GCP, k8s)
  • 설정 파일에 대해 실시간 적용
구성요소 역할
Patroni 인스턴스 각 서버(노드)에서 PostgreSQL 프로세스를 관리, 상태 체크, 리더 선출에 참여
분산 저장소(etcd 등) 클러스터 메타데이터, 리더 정보, 노드 상태 등 공유 역할
PostgreSQL 인스턴스 실질적인 데이터 저장 및 클라이언트 서비스 제공

 

Failover(장애조치) 동작 방식

  1. 상태 모니터링: 각 Patroni 인스턴스는 자신의 PostgreSQL과 외부 분산 저장소(etcd/Consul 등)을 통해 클러스터 전체 상태를 파악
    • 각 Instance 에 존재하는 Patroni instance 들이 자신의 상태정보(정상 여부, LSN 동기화 등)를 주기적으로 etcd 에 등록
    • etcd 에 등록된 다른 노드들은, 이러한 정보들을 서로 계속 모니터링
  2. Primary 장애 감지: Primary(PostgreSQL 리더) 인스턴스에 장애가 발생해 heartbeat/stats 갱신이 중지되면, 나머지 Replica Patroni 인스턴스들이 즉시 이를 감지.(Health check)
  3. Primary(Leader) 선출(election): Patroni 인스턴스들이 협의하여 etcd 같은 분산 저장소를 통해 새로운 리더를 선출. 가장 최신의 WAL 로그를 가진 standby가 우선 승격 대상.
  4. 자동 승격(Promotion): 선출된 Replica는 자동으로 PostgreSQL primary로 승격(promote) 되고, 클러스터 상태가 분산 저장소에 반영
  5. 기존(Old) Primary restart : 장애가 복구된 이전 Primary는 자동으로 Replica로 강등(demote)되어 클러스터에 재합류(재동기화).
  6. 동적 재구성: 연결 관리(HAProxy, PgBouncer, 애플리케이션 등)는 REST API와 클러스터 메타 정보로 새로운 리더 정보를 즉시 인지.

DCS (분산 구성 저장소, Distributed Configuration Store) 역할

  • Cluster 상태 공유 및 Leader 선출
    • Cluster 내 모든 node(PostgreSQL + Patroni ) 의 DCS 에 접근하여 클러스터의 상태와 구성 정보를 저장하고 공유
    • Primary (Leader)의 경우 주기적으로 DCS에 자신의 상태(leader key, TTL 등)를 갱신하고, 다른 노드들이 이 정보를 지속적으로 체크하여, 혹시라도 갱신이 멈추면 key 를 만료시켜 Leader 선출 프로세스가 자동으로 시작
  • Node 상태 감지 및 Failover 지원
    • Patroni agent 가 자신의 상태 정보를 DCS 에 주기적으로 업데이트 하여 모든 노드가 서로의 상태를 감지할 수 있음
  • Split brain 방지
    • DCS 의 Leader lock 의 TTL 관리와 patroni 의 Self-fencing (자기 격리) 메커니즘을 통해, 네트워크 split 이나 시스템 장애시 여러 node 가 동시에 primary 로 오작동하는 문제를 예방
    • DCS와 connection 이 끊어지거나, Leader key 갱신이 실패하면, 해당 node 는 자신을 자동으로 replica 로 강등시켜 데이터 손실 및 충독을 예방

각종 관련된 확인 내용 

#Patroni 클러스터 정보
root@QEMU-Virtual-Machine:/data# patronictl -c /etc/patroni1.yml list
+ Cluster: pg-cluster (7527206046275954701) ----+----+-----------+
| Member | Host           | Role    | State     | TL | Lag in MB |
+--------+----------------+---------+-----------+----+-----------+
| node1  | 127.0.0.1:5432 | Leader  | running   |  1 |           |
| node2  | 127.0.0.1:5433 | Replica | streaming |  1 |         0 |
| node3  | 127.0.0.1:5434 | Replica | streaming |  1 |         0 |
+--------+----------------+---------+-----------+----+-----------+

# DCS(etcd) 정보
root@QEMU-Virtual-Machine:/data# etcdctl member list
2c5b13349b914f7c, started, etcd2, http://127.0.0.1:2480, http://127.0.0.1:2479, false
d6b6fc75feb1ea61, started, etcd3, http://127.0.0.1:2580, http://127.0.0.1:2579, false
e17d9e661baad1bc, started, etcd1, http://127.0.0.1:2380, http://127.0.0.1:2379, false

#Leader / member 정보
root@QEMU-Virtual-Machine:/data# etcdctl endpoint status --cluster
http://127.0.0.1:2479, 2c5b13349b914f7c, 3.6.2, 3.6.0, 78 kB, 78 kB, 0%, 0 B, false, false, 2, 292, 292, , , false
http://127.0.0.1:2579, d6b6fc75feb1ea61, 3.6.2, 3.6.0, 78 kB, 78 kB, 0%, 0 B, false, false, 2, 292, 292, , , false
http://127.0.0.1:2379, e17d9e661baad1bc, 3.6.2, 3.6.0, 78 kB, 78 kB, 0%, 0 B, true, false, 2, 292, 292, , , false

#/service/{Cluster name} 경로에 Cluster 상태 정보 및 구성 값을 저장되어 확인이 가능
# 아래 내용은 모든 내용을 출력 / 해당 정보들이 꾸준히 갱신
root@QEMU-Virtual-Machine:/data# etcdctl get / --prefix
/service/pg-cluster/config
{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":1048576,"postgresql":{"use_pg_rewind":true,"parameters":{"wal_level":"replica","hot_standby":"on","max_wal_senders":10,"max_replication_slots":10,"wal_keep_size":"512MB"}}}
/service/pg-cluster/initialize
7527206046275954701
/service/pg-cluster/leader
node1
/service/pg-cluster/members/node1
{"conn_url":"postgres://127.0.0.1:5432/postgres","api_url":"http://127.0.0.1:8008/patroni","state":"running","role":"primary","version":"4.0.4","xlog_location":150995304,"timeline":1}
/service/pg-cluster/members/node2
{"conn_url":"postgres://127.0.0.1:5433/postgres","api_url":"http://127.0.0.1:8018/patroni","state":"running","role":"replica","version":"4.0.4","xlog_location":150995304,"replication_state":"streaming","timeline":1}
/service/pg-cluster/members/node3
{"conn_url":"postgres://127.0.0.1:5434/postgres","api_url":"http://127.0.0.1:8028/patroni","state":"running","role":"replica","version":"4.0.4","xlog_location":150995304,"replication_state":"streaming","timeline":1}
/service/pg-cluster/status
{"optime":150995304,"slots":{"node2":150995304,"node3":150995304,"node1":150995304},"retain_slots":["node1","node2","node3"]}
/service/pgtest/config
{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":1048576,"postgresql":{"use_pg_rewind":true,"use_slots":true,"parameters":{"wal_level":"replica","hot_standby":"on","wal_keep_size":"64MB","max_wal_senders":10,"max_replication_slots":10}}}
/service/pgtest/initialize
7527193237175963385
/service/pgtest/status
{"optime":67438232,"slots":{"node2":33554680,"node3":33554680,"node1":67438232},"retain_slots":["node1","node2","node3"]}


# leader 정보만 확인 /service/{cluster name}/leader
root@QEMU-Virtual-Machine:/data# etcdctl get /service/pg-cluster/leader
/service/pg-cluster/leader
node1


# Patroni 가 사용하는 관련 모든 정보 /service/{cluster name}
root@QEMU-Virtual-Machine:/data# etcdctl get /service/pg-cluster --prefix
/service/pg-cluster/config
{"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":1048576,"postgresql":{"use_pg_rewind":true,"parameters":{"wal_level":"replica","hot_standby":"on","max_wal_senders":10,"max_replication_slots":10,"wal_keep_size":"512MB"}}}
/service/pg-cluster/initialize
7527206046275954701
/service/pg-cluster/leader
node1
/service/pg-cluster/members/node1
{"conn_url":"postgres://127.0.0.1:5432/postgres","api_url":"http://127.0.0.1:8008/patroni","state":"running","role":"primary","version":"4.0.4","xlog_location":150995304,"timeline":1}
/service/pg-cluster/members/node2
{"conn_url":"postgres://127.0.0.1:5433/postgres","api_url":"http://127.0.0.1:8018/patroni","state":"running","role":"replica","version":"4.0.4","xlog_location":150995304,"replication_state":"streaming","timeline":1}
/service/pg-cluster/members/node3
{"conn_url":"postgres://127.0.0.1:5434/postgres","api_url":"http://127.0.0.1:8028/patroni","state":"running","role":"replica","version":"4.0.4","xlog_location":150995304,"replication_state":"streaming","timeline":1}
/service/pg-cluster/status
{"optime":150995304,"slots":{"node2":150995304,"node3":150995304,"node1":150995304},"retain_slots":["node1","node2","node3"]}
+------------------------+
|      etcd Cluster      | <-- 클러스터 상태 관리(leader key, member key)
+-----------+------------+
            ^
            |
    Patroni REST API (상태 저장/전파, leader 선출 등)
+-----------+------------+
|         Patroni         |  <-- 각 PostgreSQL 노드에 설치
+-----------+------------+
            |
     PostgreSQL 인스턴스 (primary, standby)

#by. perplexity

AWS Patroni Architecture

 

AWS ec2 에서 postgresql patroni 구성 권장 가이드

- https://docs.aws.amazon.com/ko_kr/prescriptive-guidance/latest/migration-databases-postgresql-ec2/ha-patroni-etcd-considerations.html

반응형

 

설치 환경

  • Mac Pro 14 - UTAM
  • ubuntu-25.04-desktop-arm64

 

etcd Single 설치 및 실행

ETCD_VER=v3.6.2

# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
DOWNLOAD_URL=${GOOGLE_URL}

rm -f /tmp/etcd-${ETCD_VER}-linux-arm64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test

curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-arm64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-arm64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-arm64.tar.gz -C /tmp/etcd-download-test --strip-components=1 --no-same-owner
rm -f /tmp/etcd-${ETCD_VER}-linux-arm64.tar.gz

/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version
/tmp/etcd-download-test/etcdutl version

# start a local etcd server
# screen 으로 띄우자
sudo cp etcd etcdctl etcdutl /usr/local/bin/
screen -S etcd
etcd  -- 실행
# 로그가 실행되면, Screen 에서 빠져나오자.

# write,read to etcd test
/tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 put foo bar
/tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 get foo

# test2
> etcdctl put mykey "this is awesome"
OK

> etcdctl get mykey
mykey
this is awesome
 

Releases · etcd-io/etcd

Distributed reliable key-value store for the most critical data of a distributed system - etcd-io/etcd

github.com

 

etcd Cluster 구성

vi /etc/systemd/system/etcd1.service
#아래내용 작성
[Unit]
Description=etcd instance 1
After=network.target

[Service]
User=etcd
Type=notify
ExecStart=/usr/local/bin/etcd \
  --name etcd1 \
  --data-dir /var/lib/etcd1 \
  --listen-peer-urls http://127.0.0.1:2380 \
  --listen-client-urls http://127.0.0.1:2379 \
  --advertise-client-urls http://127.0.0.1:2379 \
  --initial-advertise-peer-urls http://127.0.0.1:2380 \
  --initial-cluster etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 \
  --initial-cluster-token single-machine-cluster \
  --initial-cluster-state new

Restart=always
RestartSec=5s




vi /etc/systemd/system/etcd2.service
#아래내용 작성
[Install]
WantedBy=multi-user.target

[Unit]
Description=etcd instance 2
After=network.target

[Service]
User=etcd
Type=notify
ExecStart=/usr/local/bin/etcd \
  --name etcd2 \
  --data-dir /var/lib/etcd2 \
  --listen-peer-urls http://127.0.0.1:2480 \
  --listen-client-urls http://127.0.0.1:2479 \
  --advertise-client-urls http://127.0.0.1:2479 \
  --initial-advertise-peer-urls http://127.0.0.1:2480 \
  --initial-cluster etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 \
  --initial-cluster-token single-machine-cluster \
  --initial-cluster-state new

Restart=always
RestartSec=5s



vi /etc/systemd/system/etcd3.service
#아래내용 작성
[Install]
WantedBy=multi-user.target


[Unit]
Description=etcd instance 3
After=network.target

[Service]
User=etcd
Type=notify
ExecStart=/usr/local/bin/etcd \
  --name etcd3 \
  --data-dir /var/lib/etcd3 \
  --listen-peer-urls http://127.0.0.1:2580 \
  --listen-client-urls http://127.0.0.1:2579 \
  --advertise-client-urls http://127.0.0.1:2579 \
  --initial-advertise-peer-urls http://127.0.0.1:2580 \
  --initial-cluster etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 \
  --initial-cluster-token single-machine-cluster \
  --initial-cluster-state new

Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target
root@QEMU-Virtual-Machine:/etc/systemd/system# sudo systemctl daemon-reexec
root@QEMU-Virtual-Machine:/etc/systemd/system# sudo systemctl daemon-reload

root@QEMU-Virtual-Machine:/etc/systemd/system# sudo systemctl enable --now etcd1
Created symlink '/etc/systemd/system/multi-user.target.wants/etcd1.service' → '/etc/systemd/system/etcd1.service'.

root@QEMU-Virtual-Machine:/etc/systemd/system# sudo systemctl enable --now etcd2
Created symlink '/etc/systemd/system/multi-user.target.wants/etcd2.service' → '/etc/systemd/system/etcd2.service'.

root@QEMU-Virtual-Machine:/etc/systemd/system# sudo systemctl enable --now etcd3
Created symlink '/etc/systemd/system/multi-user.target.wants/etcd3.service' → '/etc/systemd/system/etcd3.service'.

root@QEMU-Virtual-Machine:/etc/systemd/system# systemctl status etcd1
● etcd1.service - etcd instance 1
     Loaded: loaded (/etc/systemd/system/etcd1.service; enabled; preset: enabled)
     Active: active (running) since Tue 2025-07-15 00:48:37 KST; 26s ago
 Invocation: fe945f33a9544d13bffc13deb0e4491b
   Main PID: 30951 (etcd)
      Tasks: 10 (limit: 8745)
     Memory: 15M (peak: 16.5M)
        CPU: 4.740s
     CGroup: /system.slice/etcd1.service
             └─30951 /usr/local/bin/etcd --name etcd1 --data-dir /var/lib/etcd1 --listen-peer-urls http://127.0.0.1:2380 --listen-client-urls http://127.0.0.1:2379 --advertise-client-urls http://127.0.0.1:2379 --initial-advertise-peer-urls http://127.0.0.1:2380 --initial-cluster etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 --initial-cluster-token single-machine-cluster --initial-clu>

Jul 15 00:48:44 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:44.793367+0900","caller":"rafthttp/stream.go:248","msg":"set message encoder","from":"e17d9e661baad1bc","to":"d6b6fc75feb1ea61","stream-type":"stream MsgApp v2"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:44.793379+0900","caller":"rafthttp/stream.go:273","msg":"established TCP streaming connection with remote peer","stream-writer-type":"stream MsgApp v2","local-member-id":"e17d9e661baad1bc","remote-peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:44.823457+0900","caller":"rafthttp/stream.go:411","msg":"established TCP streaming connection with remote peer","stream-reader-type":"stream MsgApp v2","local-member-id":"e17d9e661baad1bc","remote-peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:44.823823+0900","caller":"rafthttp/stream.go:411","msg":"established TCP streaming connection with remote peer","stream-reader-type":"stream Message","local-member-id":"e17d9e661baad1bc","remote-peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:45.949082+0900","caller":"etcdserver/server.go:2409","msg":"updating cluster version using v3 API","from":"3.0","to":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:45.952411+0900","caller":"membership/cluster.go:673","msg":"updated cluster version","cluster-id":"af7e1200b66aabd1","local-member-id":"e17d9e661baad1bc","from":"3.0","to":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:45.952565+0900","caller":"api/capability.go:76","msg":"enabled capabilities for version","cluster-version":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:45.952600+0900","caller":"etcdserver/server.go:2424","msg":"cluster version is updated","cluster-version":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:45.952729+0900","caller":"version/monitor.go:116","msg":"cluster version differs from storage version.","cluster-version":"3.6.0","storage-version":"3.5.0"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[30951]: {"level":"info","ts":"2025-07-15T00:48:45.952863+0900","caller":"schema/migration.go:65","msg":"updated storage version","new-storage-version":"3.6.0"}
root@QEMU-Virtual-Machine:/etc/systemd/system#
root@QEMU-Virtual-Machine:/etc/systemd/system#
root@QEMU-Virtual-Machine:/etc/systemd/system# systemctl status etcd2
● etcd2.service - etcd instance 2
     Loaded: loaded (/etc/systemd/system/etcd2.service; enabled; preset: enabled)
     Active: active (running) since Tue 2025-07-15 00:48:37 KST; 31s ago
 Invocation: eb34f34340484c41894eaec65fed3e74
   Main PID: 31108 (etcd)
      Tasks: 11 (limit: 8745)
     Memory: 11.6M (peak: 12.2M)
        CPU: 1.208s
     CGroup: /system.slice/etcd2.service
             └─31108 /usr/local/bin/etcd --name etcd2 --data-dir /var/lib/etcd2 --listen-peer-urls http://127.0.0.1:2480 --listen-client-urls http://127.0.0.1:2479 --advertise-client-urls http://127.0.0.1:2479 --initial-advertise-peer-urls http://127.0.0.1:2480 --initial-cluster etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 --initial-cluster-token single-machine-cluster --initial-clu>

Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:44.792708+0900","caller":"rafthttp/peer_status.go:53","msg":"peer became active","peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:44.792729+0900","caller":"rafthttp/stream.go:273","msg":"established TCP streaming connection with remote peer","stream-writer-type":"stream Message","local-member-id":"2c5b13349b914f7c","remote-peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:44.792736+0900","caller":"rafthttp/stream.go:248","msg":"set message encoder","from":"2c5b13349b914f7c","to":"d6b6fc75feb1ea61","stream-type":"stream MsgApp v2"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:44.792745+0900","caller":"rafthttp/stream.go:273","msg":"established TCP streaming connection with remote peer","stream-writer-type":"stream MsgApp v2","local-member-id":"2c5b13349b914f7c","remote-peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:44.853106+0900","caller":"rafthttp/stream.go:411","msg":"established TCP streaming connection with remote peer","stream-reader-type":"stream Message","local-member-id":"2c5b13349b914f7c","remote-peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:44.853138+0900","caller":"rafthttp/stream.go:411","msg":"established TCP streaming connection with remote peer","stream-reader-type":"stream MsgApp v2","local-member-id":"2c5b13349b914f7c","remote-peer-id":"d6b6fc75feb1ea61"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:45.952663+0900","caller":"membership/cluster.go:673","msg":"updated cluster version","cluster-id":"af7e1200b66aabd1","local-member-id":"2c5b13349b914f7c","from":"3.0","to":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:45.952760+0900","caller":"api/capability.go:76","msg":"enabled capabilities for version","cluster-version":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:45.952837+0900","caller":"version/monitor.go:116","msg":"cluster version differs from storage version.","cluster-version":"3.6.0","storage-version":"3.5.0"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31108]: {"level":"info","ts":"2025-07-15T00:48:45.952880+0900","caller":"schema/migration.go:65","msg":"updated storage version","new-storage-version":"3.6.0"}

root@QEMU-Virtual-Machine:/etc/systemd/system#
root@QEMU-Virtual-Machine:/etc/systemd/system#
root@QEMU-Virtual-Machine:/etc/systemd/system# systemctl status etcd3
● etcd3.service - etcd instance 3
     Loaded: loaded (/etc/systemd/system/etcd3.service; enabled; preset: enabled)
     Active: active (running) since Tue 2025-07-15 00:48:44 KST; 29s ago
 Invocation: e05bc4a064ff4359b3188cbe548dd98c
   Main PID: 31415 (etcd)
      Tasks: 11 (limit: 8745)
     Memory: 9.8M (peak: 10.6M)
        CPU: 1.142s
     CGroup: /system.slice/etcd3.service
             └─31415 /usr/local/bin/etcd --name etcd3 --data-dir /var/lib/etcd3 --listen-peer-urls http://127.0.0.1:2580 --listen-client-urls http://127.0.0.1:2579 --advertise-client-urls http://127.0.0.1:2579 --initial-advertise-peer-urls http://127.0.0.1:2580 --initial-cluster etcd1=http://127.0.0.1:2380,etcd2=http://127.0.0.1:2480,etcd3=http://127.0.0.1:2580 --initial-cluster-token single-machine-cluster --initial-clu>

Jul 15 00:48:44 QEMU-Virtual-Machine systemd[1]: Started etcd3.service - etcd instance 3.
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:44.843171+0900","caller":"etcdserver/server.go:522","msg":"initialized peer connections; fast-forwarding election ticks","local-member-id":"d6b6fc75feb1ea61","forward-ticks":8,"forward-duration":"800ms","election-ticks":10,"election-timeout":"1s","active-remote-members":2}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:44.853066+0900","caller":"rafthttp/stream.go:248","msg":"set message encoder","from":"d6b6fc75feb1ea61","to":"2c5b13349b914f7c","stream-type":"stream Message"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:44.853106+0900","caller":"rafthttp/stream.go:273","msg":"established TCP streaming connection with remote peer","stream-writer-type":"stream Message","local-member-id":"d6b6fc75feb1ea61","remote-peer-id":"2c5b13349b914f7c"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:44.853108+0900","caller":"rafthttp/stream.go:248","msg":"set message encoder","from":"d6b6fc75feb1ea61","to":"2c5b13349b914f7c","stream-type":"stream MsgApp v2"}
Jul 15 00:48:44 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:44.853125+0900","caller":"rafthttp/stream.go:273","msg":"established TCP streaming connection with remote peer","stream-writer-type":"stream MsgApp v2","local-member-id":"d6b6fc75feb1ea61","remote-peer-id":"2c5b13349b914f7c"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:45.952737+0900","caller":"membership/cluster.go:673","msg":"updated cluster version","cluster-id":"af7e1200b66aabd1","local-member-id":"d6b6fc75feb1ea61","from":"3.0","to":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:45.953168+0900","caller":"api/capability.go:76","msg":"enabled capabilities for version","cluster-version":"3.6"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:45.953228+0900","caller":"version/monitor.go:116","msg":"cluster version differs from storage version.","cluster-version":"3.6.0","storage-version":"3.5.0"}
Jul 15 00:48:45 QEMU-Virtual-Machine etcd[31415]: {"level":"info","ts":"2025-07-15T00:48:45.953290+0900","caller":"schema/migration.go:65","msg":"updated storage version","new-storage-version":"3.6.0"}

 

#etcd Cluster 상태 확인
etcdctl --endpoints=127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579 endpoint status --write-out=table

#etcdctl 확인
etcdctl --endpoints=127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579 endpoint health
127.0.0.1:2479 is healthy: successfully committed proposal: took = 5.062031ms
127.0.0.1:2379 is healthy: successfully committed proposal: took = 4.988364ms
127.0.0.1:2579 is healthy: successfully committed proposal: took = 5.038531ms

etcd cluster 설정 확인

 

 

Patroni 설치

 

patroni/postgres0.yml at master · patroni/patroni

A template for PostgreSQL High Availability with Etcd, Consul, ZooKeeper, or Kubernetes - patroni/patroni

github.com

 

  • 기본적인 설치 내용들
sudo apt install python3-pip -y
sudo apt-get install python3-psycopg2 
sudo apt-get install patroni

 

  • 각 node 들의 yml 정보
root@QEMU-Virtual-Machine:/data# cat /etc/patroni1.yml
scope: pg-cluster
name: node1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 127.0.0.1:8008

etcd3:
  hosts: 127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        wal_keep_size: 512MB

  initdb:
    - encoding: UTF8
    - data-checksums

postgresql:
  listen: 127.0.0.1:5432
  connect_address: 127.0.0.1:5432
  data_dir: /data/pg1
  bin_dir: /usr/lib/postgresql/17/bin
  config_dir: /data/pg1
  authentication:
    superuser:
      username: postgres
      password: supersecret1
    replication:
      username: replicator
      password: replpass1
  parameters:
    unix_socket_directories: '/tmp'
######################################################################
root@QEMU-Virtual-Machine:/data# cat /etc/patroni2.yml
scope: pg-cluster
name: node2

restapi:
  listen: 0.0.0.0:8018
  connect_address: 127.0.0.1:8018

etcd3:
  hosts: 127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        wal_keep_size: 512MB

  initdb:
    - encoding: UTF8
    - data-checksums

postgresql:
  listen: 127.0.0.1:5433
  connect_address: 127.0.0.1:5433
  data_dir: /data/pg2
  bin_dir: /usr/lib/postgresql/17/bin
  config_dir: /data/pg2
  authentication:
    superuser:
      username: postgres
      password: supersecret2
    replication:
      username: replicator
      password: replpass2
  parameters:
    unix_socket_directories: '/tmp'

######################################################################
root@QEMU-Virtual-Machine:/data# cat /etc/patroni3.yml
scope: pg-cluster
name: node3

restapi:
  listen: 0.0.0.0:8028
  connect_address: 127.0.0.1:8028

etcd3:
  hosts: 127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576
    postgresql:
      use_pg_rewind: true
      parameters:
        wal_level: replica
        hot_standby: "on"
        max_wal_senders: 10
        max_replication_slots: 10
        wal_keep_size: 512MB

  initdb:
    - encoding: UTF8
    - data-checksums

postgresql:
  listen: 127.0.0.1:5434
  connect_address: 127.0.0.1:5434
  data_dir: /data/pg3
  bin_dir: /usr/lib/postgresql/17/bin
  config_dir: /data/pg3
  authentication:
    superuser:
      username: postgres
      password: supersecret3
    replication:
      username: replicator
      password: replpass3
  parameters:
    unix_socket_directories: '/tmp'

 

  • Patroni 실행 진행 (서비스로 구성은 추후에 시도 예정)
    • 반드시 screen 으로 하는 것으로 하는 것을 추천 (가급적이면 서비스로 구성하여 로그들 확인)
# primary 로 정상적으로 로그가 발생하면 나머지 2,3 번 replica 들을 실행하는 것을 추천
root@QEMU-Virtual-Machine:/tmp/etcd-download-test# sudo -u postgres patroni /etc/patroni1.yml

root@QEMU-Virtual-Machine:/tmp/etcd-download-test# sudo -u postgres patroni /etc/patroni2.yml
root@QEMU-Virtual-Machine:/tmp/etcd-download-test# sudo -u postgres patroni /etc/patroni3.yml

#정상적으로 올라왔는지 확인하는 명령어
root@QEMU-Virtual-Machine:/data# patronictl -c /etc/patroni1.yml list
+ Cluster: pg-cluster (7527206046275954701) ----+----+-----------+
| Member | Host           | Role    | State     | TL | Lag in MB |
+--------+----------------+---------+-----------+----+-----------+
| node1  | 127.0.0.1:5432 | Leader  | running   |  1 |           |
| node2  | 127.0.0.1:5433 | Replica | streaming |  1 |         0 |
| node3  | 127.0.0.1:5434 | Replica | streaming |  1 |         0 |
+--------+----------------+---------+-----------+----+-----------+
  • 정상적으로 실행된 로그들
root@QEMU-Virtual-Machine:/etc/systemd/system# sudo -u postgres patroni /etc/patroni1.yml
2025-07-15 15:34:06,108 INFO: Selected new etcd server http://127.0.0.1:2579
2025-07-15 15:34:06,152 INFO: No PostgreSQL configuration items changed, nothing to reload.
2025-07-15 15:34:06,195 INFO: Lock owner: None; I am node1
2025-07-15 15:34:06,281 INFO: trying to bootstrap a new cluster
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.UTF-8".
The default text search configuration will be set to "english".

Data page checksums are enabled.

creating directory /data/pg1 ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... Asia/Seoul
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    /usr/lib/postgresql/17/bin/pg_ctl -D /data/pg1 -l logfile start

2025-07-15 15:34:06.872 KST [36610] LOG:  starting PostgreSQL 17.5 (Ubuntu 17.5-0ubuntu0.25.04.1) on aarch64-unknown-linux-gnu, compiled by gcc (Ubuntu 14.2.0-19ubuntu2) 14.2.0, 64-bit
2025-07-15 15:34:06.872 KST [36610] LOG:  listening on IPv4 address "127.0.0.1", port 5433
2025-07-15 15:34:06.873 KST [36610] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5433"
2025-07-15 15:34:06.876 KST [36613] LOG:  database system was shut down at 2025-07-15 15:34:06 KST
2025-07-15 15:34:06,880 INFO: postmaster pid=36610
2025-07-15 15:34:06.880 KST [36610] LOG:  database system is ready to accept connections
localhost:5433 - accepting connections
localhost:5433 - accepting connections
2025-07-15 15:34:06,895 INFO: establishing a new patroni heartbeat connection to postgres
2025-07-15 15:34:06,942 INFO: running post_bootstrap
2025-07-15 15:34:06,950 WARNING: Could not activate Linux watchdog device: Can't open watchdog device: [Errno 2] No such file or directory: '/dev/watchdog'
2025-07-15 15:34:07.096 KST [36611] LOG:  checkpoint starting: force wait
2025-07-15 15:34:07,125 INFO: initialized a new cluster
2025-07-15 15:34:07.541 KST [36611] LOG:  checkpoint complete: wrote 7 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.408 s, sync=0.008 s, total=0.445 s; sync files=6, longest=0.005 s, average=0.002 s; distance=8984 kB, estimate=8984 kB; lsn=0/2000080, redo lsn=0/2000028
2025-07-15 15:34:07.541 KST [36611] LOG:  checkpoint starting: force wait
2025-07-15 15:34:07.545 KST [36611] LOG:  checkpoint complete: wrote 0 buffers (0.0%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.001 s, total=0.005 s; sync files=0, longest=0.000 s, average=0.000 s; distance=0 kB, estimate=8085 kB; lsn=0/2000150, redo lsn=0/20000F8


#Replica node log들
root@QEMU-Virtual-Machine:/tmp/etcd-download-test# sudo -u postgres patroni /etc/patroni3.yml
2025-07-15 16:53:53,208 INFO: Selected new etcd server http://127.0.0.1:2379
2025-07-15 16:53:53,253 INFO: No PostgreSQL configuration items changed, nothing to reload.
2025-07-15 16:53:53,296 INFO: Lock owner: node1; I am node3
2025-07-15 16:53:53,339 INFO: trying to bootstrap from leader 'node1'
2025-07-15 16:53:53,548 INFO: replica has been created using basebackup
2025-07-15 16:53:53,549 INFO: bootstrapped from leader 'node1'
2025-07-15 16:53:53,741 INFO: postmaster pid=39826
localhost:5434 - no response
2025-07-15 16:53:53.772 KST [39826] LOG:  starting PostgreSQL 17.5 (Ubuntu 17.5-0ubuntu0.25.04.1) on aarch64-unknown-linux-gnu, compiled by gcc (Ubuntu 14.2.0-19ubuntu2) 14.2.0, 64-bit
2025-07-15 16:53:53.772 KST [39826] LOG:  listening on IPv4 address "127.0.0.1", port 5434
2025-07-15 16:53:53.773 KST [39826] LOG:  listening on Unix socket "/tmp/.s.PGSQL.5434"
2025-07-15 16:53:53.775 KST [39830] LOG:  database system was interrupted; last known up at 2025-07-15 16:53:53 KST
2025-07-15 16:53:53.857 KST [39830] LOG:  starting backup recovery with redo LSN 0/8000028, checkpoint LSN 0/8000080, on timeline ID 1
2025-07-15 16:53:53.858 KST [39830] LOG:  entering standby mode
2025-07-15 16:53:53.860 KST [39830] LOG:  redo starts at 0/8000028
2025-07-15 16:53:53.861 KST [39830] LOG:  completed backup recovery with redo LSN 0/8000028 and end LSN 0/8000120
2025-07-15 16:53:53.861 KST [39830] LOG:  consistent recovery state reached at 0/8000120
2025-07-15 16:53:53.861 KST [39826] LOG:  database system is ready to accept read-only connections
2025-07-15 16:53:53.867 KST [39831] LOG:  started streaming WAL from primary at 0/9000000 on timeline 1
localhost:5434 - accepting connections
localhost:5434 - accepting connections
2025-07-15 16:53:54,794 INFO: Lock owner: node1; I am node3
2025-07-15 16:53:54,794 INFO: establishing a new patroni heartbeat connection to postgres
2025-07-15 16:53:54,867 INFO: no action. I am (node3), a secondary, and following a leader (node1)
2025-07-15 16:53:59,529 INFO: no action. I am (node3), a secondary, and following a leader (node1)
2025-07-15 16:54:10,074 INFO: no action. I am (node3), a secondary, and following a leader (node1)
2025-07-15 16:54:20,030 INFO: no action. I am (node3), a secondary, and following a leader (node1)
2025-07-15 16:54:30,074 INFO: no action. I am (node3), a secondary, and following a leader (node1)

 

 

Patroni Trouble shooting

  • Patroni 구성하면서 발생한 문제들에 대해 정리
  • 자료가 많지 않아, 구성 중에 설정값 누락도 많이 있었으며, 권한에 대한 이슈도 존재한 것을 작성

Case 1.

root@QEMU-Virtual-Machine:/etc/systemd/system# patroni /etc/patroni1.yml
2025-07-15 15:32:49,636 ERROR: Failed to get list of machines from http://127.0.0.1:2479/v2: EtcdException('Bad response : 404 page not found\n')
2025-07-15 15:32:49,637 ERROR: Failed to get list of machines from http://127.0.0.1:2379/v2: EtcdException('Bad response : 404 page not found\n')
2025-07-15 15:32:49,638 ERROR: Failed to get list of machines from http://127.0.0.1:2579/v2: EtcdException('Bad response : 404 page not found\n')
2025-07-15 15:32:49,638 INFO: waiting on etcd
2025-07-15 15:32:54,641 ERROR: Failed to get list of machines from http://127.0.0.1:2479/v2: EtcdException('Bad response : 404 page not found\n')
2025-07-15 15:32:54,645 ERROR: Failed to get list of machines from http://127.0.0.1:2379/v2: EtcdException('Bad response : 404 page not found\n')
2025-07-15 15:32:54,649 ERROR: Failed to get list of machines from http://127.0.0.1:2579/v2: EtcdException('Bad response : 404 page not found\n')
  • 기존 가이드들 보면, 다음과 같이 `etcd` 라고 명칭하지만 이것을 `etcd3` 로 변경하면 해결
root@QEMU-Virtual-Machine:/data# cat /etc/patroni1.yml
scope: pg-cluster
name: node1

restapi:
  listen: 0.0.0.0:8008
  connect_address: 127.0.0.1:8008

etcd3:  #<----- 일반 문서에는 etcd: 로 되어 있음
  hosts: 127.0.0.1:2379,127.0.0.1:2479,127.0.0.1:2579

 

Case 2.

  • root 에서 patroni 실행 시 정상 실행이 안되는 경우
  • postgres 계정으로 전환하여 진행
실행 명령어 및 로그 내용
root@QEMU-Virtual-Machine:/tmp/etcd-download-test# patroni /etc/patroni3.yml
2025-07-15 01:57:32,711 INFO: Lock owner: None; I am node3

--> 계속 이러한 로그만 나오고 정상적으로 실행되는 어떠한 내용도 확인이 되지 않음

# 아래 처럼 postgres 계정으로 서비스 시작 진행
root@QEMU-Virtual-Machine:/tmp/etcd-download-test# sudo -u postgres patroni /etc/patroni3.yml

 

Case 3.

  • Patroni node 3번이 실행 도중 다음과 같은 에러가 발생하며 문제를 발생
2025-07-15 16:31:47,698 INFO: doing crash recovery in a single user mode
2025-07-15 16:31:47,713 ERROR: Error when reading postmaster.opts
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/patroni/postgresql/rewind.py", line 545, in read_postmaster_opts
with open(os.path.join(self._postgresql.data_dir, 'postmaster.opts')) as f:
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/data/pg3/postmaster.opts'
2025-07-15 16:31:47,719 ERROR: Crash recovery finished with code=1
2025-07-15 16:31:47,719 INFO: stdout=
2025-07-15 16:31:47,719 INFO: stderr=2025-07-15 16:31:47.718 KST [39181] FATAL: data directory "/data/pg3" has invalid permissions
2025-07-15 16:31:47.718 KST [39181] DETAIL: Permissions should be u=rwx (0700) or u=rwx,g=rx (0750).

 

  • 권한 이슈로 다음과 같이 실행 후 다시 진행하여 해결 완료
    • DB가 시작되지 못해서 발생
# 소유권 변경 (root로 실행)
chown -R postgres:postgres /data/pg3

# 권한 변경
chmod 700 /data/pg3


root@QEMU-Virtual-Machine:/data# patronictl -c /etc/patroni1.yml list
+ Cluster: pg-cluster (7527206046275954701) -------+----+-----------+
| Member | Host           | Role    | State        | TL | Lag in MB |
+--------+----------------+---------+--------------+----+-----------+
| node1  | 127.0.0.1:5432 | Leader  | running      |  1 |           |
| node2  | 127.0.0.1:5433 | Replica | streaming    |  1 |         0 |
| node3  | 127.0.0.1:5434 | Replica | start failed |    |   unknown |
+--------+----------------+---------+--------------+----+-----------+

## 이슈 해결
root@QEMU-Virtual-Machine:/data# screen -R patroni3
[detached from 33456.patroni3]
root@QEMU-Virtual-Machine:/data# patronictl -c /etc/patroni1.yml list
+ Cluster: pg-cluster (7527206046275954701) ----+----+-----------+
| Member | Host           | Role    | State     | TL | Lag in MB |
+--------+----------------+---------+-----------+----+-----------+
| node1  | 127.0.0.1:5432 | Leader  | running   |  1 |           |
| node2  | 127.0.0.1:5433 | Replica | streaming |  1 |         0 |
| node3  | 127.0.0.1:5434 | Replica | streaming |  1 |         0 |
+--------+----------------+---------+-----------+----+-----------+

 

Case 4.

  • log 상에서 다음과 같은 에러가 보인다면, 해당 secondary node 를 재 구축 진행
  • 운영에서는 최신 백업으로 복구 후에 진행하는 것이 시간 단축에 도움
  • 초기 과정에서는 해당 노드를 삭제하고 다시 구성하는 것이 빠르고, 깔끔한 방법
## Error log (replica node)
2025-07-15 16:50:13.401 KST [39717] FATAL:  could not receive data from WAL stream: ERROR:  requested starting point 0/6000000 is ahead of the WAL flush position of this server 0/5050660

## 이 오류 메시지는 PostgreSQL 스트리밍 복제가 시작될 때 레플리카 노드가 요청한 WAL 시점이 프라이머리 노드가 아직 생성하지 않은 LSN(Log Sequence Number)에 있다는 것을 의미(제대로 복제 데이터를 가져오지 못한 현상)
################################################################################
## 삭제 후 재구축
# 레플리카 노드에서 실행 (중요: primary가 정상 상태여야 함)
# patroni 를 강제 중지 또는
sudo systemctl stop patroni3  # 프로세스 정지

# 데이터 디렉토리 정리 (중요: 실수로 primary에서 하지 않도록 주의)
rm -rf /data/pg3/*

# 또는 수동으로 초기화된 basebackup
sudo -u postgres pg_basebackup -h <primary_ip> -D /data/pg3 -U replicator -P -R

# 퍼미션 재확인
chown -R postgres:postgres /data/pg3
chmod 700 /data/pg3

# Patroni 재시작
sudo systemctl start patroni 또는
root@QEMU-Virtual-Machine:/tmp/etcd-download-test# sudo -u postgres patroni /etc/patroni3.yml

 

반응형

[OpenAI 도움을 받아 문서를 작성하였습니다.]

 

PostgreSQL 16을 설치하면서 데이터 디렉토리와 로그 디렉토리를 /data/pg1/data, /data/pg1/log로 분리하고, 설정 파일 postgresql.conf를 /data/pg1/에 따로 두는 방법을 정리

 

Mac에 UTM - Ubuntu


Path

PostgreSQL 데이터 디렉토리 /data/pg1/data
PostgreSQL 로그 디렉토리 /data/pg1/log
설정 파일 (postgresql.conf 등) /data/pg1/
 

1. PostgreSQL APT 저장소 등록 및 설치

sudo apt update sudo apt install wget gnupg2 lsb-release -y wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/postgresql.gpg echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | \ sudo tee /etc/apt/sources.list.d/pgdg.list sudo apt update sudo apt install postgresql-16 -y

2. 기본 클러스터 삭제

수동으로 설정할 것이므로 기본 클러스터는 삭제

sudo systemctl stop postgresql sudo pg_dropcluster 16 main --stop

3. 사용자 정의 디렉토리 구조 생성

sudo mkdir -p /data/pg1/data sudo mkdir -p /data/pg1/log sudo chown -R postgres:postgres /data/pg1 sudo chmod -R 700 /data/pg1

 


4. initdb (DB 기본 구성)

- 이때 템플릿이 존재하는 경우 템플릿으로 구성도 가능 ( 개념적으로 공부하였으며, 이에 대한 테스트 내용은 추가 정리 )

sudo -u postgres /usr/lib/postgresql/16/bin/initdb \ -D /data/pg1/data \ -U postgres \ --locale=en_US.UTF-8 \ --encoding=UTF8

5. Config 파일 이동

각종 Config 파일을 /data/pg1/로 이동

sudo -u postgres mv /data/pg1/data/postgresql.conf /data/pg1/ sudo -u postgres mv /data/pg1/data/pg_hba.conf /data/pg1/ sudo -u postgres mv /data/pg1/data/pg_ident.conf /data/pg1/

6. postgresql.conf 수정

 

vi /data/pg1/postgresql.conf 

# config 파일 위치 
data_directory = '/data/pg1/data'
hba_file = '/data/pg1/pg_hba.conf'
ident_file = '/data/pg1/pg_ident.conf'

#logging 및 기본 설정 정보 수정
logging_collector = on
log_directory = '/data/pg1/log'
log_filename = 'postgresql-%Y-%m-%d.log'
log_rotation_age = 1d
log_rotation_size = 0

7. Startup 체크 

제대로 동작하는지 정상 동작 여부 확인
 
sudo -u postgres /usr/lib/postgresql/16/bin/postgres -D /data/pg1/data -c config_file=/data/pg1/postgresql.conf

 


8. systemd 서비스 등록  

vi /etc/systemd/system/postgresql@pg1.service

# /etc/systemd/system/postgresql@pg1.service
[Unit]
Description=PostgreSQL Cluster pg1
After=network.target

[Service]
Type=forking
User=postgres
ExecStart=/usr/lib/postgresql/16/bin/pg_ctl start -D /data/pg1/data -l /data/pg1/log/server.log -o "-c config_file=/data/pg1/postgresql.conf"
ExecStop=/usr/lib/postgresql/16/bin/pg_ctl stop -D /data/pg1/data
ExecReload=/usr/lib/postgresql/16/bin/pg_ctl reload -D /data/pg1/data
Restart=on-failure

[Install]
WantedBy=multi-user.target

#----------------------------------------------------------------

# 서비스 enable 및 startupl
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable postgresql@pg1
sudo systemctl start postgresql@pg1

# 서비스 확인
sudo systemctl status postgresql@pg1



root@QEMU-Virtual-Machine:~# systemctl status postgresql@pg1
● postgresql@pg1.service - PostgreSQL Cluster pg1
     Loaded: loaded (/etc/systemd/system/postgresql@pg1.service; enabled; preset: enabled)
     Active: active (running) since Mon 2025-07-14 06:55:27 KST; 21s ago
 Invocation: 713c96deac224813aef721246ecd8566
    Process: 27196 ExecStart=/usr/lib/postgresql/16/bin/pg_ctl start -D /data/pg1/data -l /data/pg1/log/server.log -o -c config_file=/data/pg1/postgresql.conf (code=exited, status=0/SUCCESS)
   Main PID: 27202 (postgres)
      Tasks: 7 (limit: 8745)
     Memory: 17.7M (peak: 18.4M)
        CPU: 59ms
     CGroup: /system.slice/system-postgresql.slice/postgresql@pg1.service
             ├─27202 /usr/lib/postgresql/16/bin/postgres -D /data/pg1/data -c config_file=/data/pg1/postgresql.conf
             ├─27210 "postgres: logger "
             ├─27211 "postgres: checkpointer "
             ├─27212 "postgres: background writer "
             ├─27215 "postgres: walwriter "
             ├─27216 "postgres: autovacuum launcher "
             └─27217 "postgres: logical replication launcher "

9. 명령어 실행 여부 확인

  • PostgreSQL 데이터 디렉토리 확인:
bash
복사편집
sudo -u postgres psql -c "SHOW data_directory;"
  • 로그 생성 확인:
bash
복사편집
ls -l /data/pg1/log
sudo -u postgres psql -c "SHOW data_directory;"

root@QEMU-Virtual-Machine:~# sudo -u postgres psql -c "SHOW data_directory;"
 data_directory
----------------
 /data/pg1/data
(1 row)

# Log 확인
ls -l /data/pg1/log

root@QEMU-Virtual-Machine:~# ls -l /data/pg1/log
total 8
-rw------- 1 postgres postgres 1905 Jul 14 06:55 postgresql-2025-07-14.log
-rw------- 1 postgres postgres  197 Jul 14 06:55 server.log

 

결론. OpenAI 는 나의 삶에 도움을 준다????

반응형

'PostgreSQL' 카테고리의 다른 글

[PostgreSQL] Patroni 정리  (1) 2025.07.16
[PostgreSQL] Patroni 설치 (with etcd)  (1) 2025.07.15
[wedatalab] Postgresql 명품 강의 (admin)  (0) 2024.06.24

이 Study 는 기본적으로 [막힘없이 PostgreSQL - 액셈] 을 기본으로 하며, 각종 Open AI 및 공식 홈페이지, The Internals of PostgreSQL 를 참고하여 정리한 글입니다.

https://www.postgresql.org/docs/current/explicit-locking.html

 

정의

  • PostgreSQL Lock은 데이터베이스에서 동시성 제어와 데이터 무결성을 보장하기 위해 사용하는 잠금(락) 메커니즘
  • 여러 트랜잭션이 동시에 데이터에 접근하거나 수정할 때 충돌을 방지

Lock level

  • Object level lock
    • Object에 대한 변경이 발생할 때 object를 보호하는 역할
    • Shared memory lock space 영역에 저장하며, 최대 동시 접속 수와 Transaction 최대 Lock 개수의 곱으로 정의
      • (max_connectgions + max_jprepared_transactions) * max_locks_per_transaction
    • max_locks_per_transaction
      • default 64
      • 하나의 Transaction 당 최대로 획득할 수 있는 object lock 수
      • 하나의 Transaction 에서 파티션 테이블에 여러개인 파티션을 한번에 Query 하는 경우, 하나의 쿼리에서 Lock 이 조회한 파티션 수만큼 발생 가능성이 존재(유의)
    • pg_catalog.pg_locks view 를 통해 object lock 정보를 확인이 가능
  • Row level lock
    • Tuple 에 대한 Lock
    • MVCC 기반으로 작동하기 때문에 write 충돌 방지에만 사용
    • shared memory 에 저장되는 것이 아닌 Page 내부의 Touple version 에 저장
      • 메모리에 저장되는 것이 아니기 때문에, Lock 개수에는 영향을 받지 않음
      • Lock 을 대기하는 프로세스를 대기 큐에 넣을 수 없기 때문에 모니터링이 힘듦
  • Memory level lock
    • PostgreSQL에서 말하는 Memory-level lock은 공유 메모리(shared memory) 내에서 이루어지는 경량 잠금(Lightweight Lock, LWLock) 또는 스핀락(spinlock) 계열의 잠금
    • 서버 내부 동기화를 위한 메모리 구조 보호용

Memory level lock 종류

종류 설명
Spinlock - CPU 단위의 아주 빠른 락
- 매우 짧은 작업 보호 (획득 시간이 매우 짧고 여러 프로세스가 별도의 메모리 영역을 변경하지 못하도록 보호 - 루프 방식으로 돌면서 Lock 획득)
- 사용 예
  > 카운터 변수 증가
  > flags 설정
  > WALWriteLock 이전 준비 
LWLock (Lightweight Lock) - PostgreSQL 공유 메모리 내 구조체 보호용
- 읽기 모드(공유) / 쓰기 모드(배타) 지원
- 사용 예
  > WALWriteLock : WAL 버퍼에 기록할 때
  > BufMappingLock  : shared buffer에 페이지 맵핑 시
  > CLogControlLock : 트랜잭션 상태 제어
  > ProcArrayLock : 트랜잭션 및 백엔드 배열 정보 제어
- PostgreSQL 내부에는 수백 개의 LWLock이 존재
Buffer Pin Lock - 버퍼를 변경하기 위해 프로세스가 Buffer pin lock을 획득
- 특정 데이터 페이지가 Shared Buffer 에서 제거되지 않도록 보호
- 데이터를 참조하는 동안 해당 페이지가 교체(Evict) 되는 것을 방지
- 페이지 단위로 관리
WAL Buffer Lock - WAL(Write-Ahead Logging) 메커니즘에서 WAL Buffer를 보호하고 동시성을 제어하기 위해 사용
- WAL Record 생성 → WALInsertLock 획득 요청 → Buffer 공간 예약 및 저장(필요 시) WALBufMappingLock 획득 후 WAL Page 확장 → WALInsertLock 해제 → (커밋 등 조건 시) WALWriteLock 획득 후 디스크로 Write
- WALInsertLock :WAL Buffer에 WAL Record를 저장할 때 획득하는 락(default 8)
- WALBufMappingLock : WAL Buffer 내에 새로운 WAL Page가 필요할 때(즉, 공간 확장이 필요할 때) 획득하는 락
- WALWriteLock :WAL Buffer의 내용을 디스크로 플러시(쓰기)할 때 사용하는 락
Lock Tranche / Named Locks - LWLock 그룹화 및 모듈화
Named LWLocks - 확장 기능이나 커널 모듈에서도 사용 가능
-- 현재 프로세스의 blocking 정보 (간접 확인 가능)
-- 메모리 수준 락은 pg_locks 에 안 나옴
-- row/table-level lock만 기록됨
SELECT * FROM pg_blocking_pids(<pid>);
  • Advisory Lock (사용자 정의 Lock)
    • Transaction 외부에서도 사용할 수 있는 Application level 의 lock
    • Table이나 row와 직접 연결되지 않음
-- 세션 단위 Advisory Lock 획득
SELECT pg_advisory_lock(12345);

-- 세션 단위 Lock 해제
SELECT pg_advisory_unlock(12345);

 

#현재 Lock 확인: pg_locks 뷰와 pg_stat_activity를 조인하여 어떤 세션이 어떤 Lock을 보유/대기 중인지 확인
SELECT a.pid, a.usename, a.datname, a.state, a.query, l.locktype, l.mode, l.granted
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE a.state != 'idle';

 

주요 Lock 종류

Access Share Lock SELECT 쿼리 실행 시 걸리는 기본 락. 다른 읽기 작업과 충돌하지 않음.
Row Share Lock 외래키 제약조건이 있는 테이블에 INSERT, UPDATE 등 수행 시. 이름과 달리 테이블 수준의 락.
Row Exclusive Lock INSERT, UPDATE, DELETE 실행 시. 읽기 쿼리와는 충돌하지 않음.
Share Update Exclusive VACUUM, ANALYZE, CREATE INDEX CONCURRENTLY 등 유지보수 명령 실행 시.
Share Lock 테이블을 공유 모드로 잠금. 읽기는 가능하지만, 동시 업데이트는 불가.
Share Row Exclusive Lock CREATE TRIGGER, 일부 ALTER 명령 실행 시.
Exclusive Lock Materialized View 갱신 등에서 사용.
Access Exclusive Lock DROP TABLE, TRUNCATE 등 DDL 명령에서 사용. 모든 락과 충돌하는 가장 강력한 락.
Row-level Lock 특정 행(row)에만 잠금. SELECT ... FOR UPDATE 등에서 사용.
Advisory Lock 애플리케이션에서 명시적으로 사용하는 사용자 정의 락.

 

Conflicting Lock Modes

Requested Lock Mode Existing Lock Mode
Access share ROW SHARE ROW EXCL. SHARE UPDATE EXCL. SHARE SHARE ROW EXCL. EXCL. ACCESS EXCL. SQL
ACCESS SHARE               X SELECT
ROW SHARE             X X SELECT FOR UIPDATE/SHARE
ROW EXCL.         X X X X INSERT, UPDATE, DELETE
SHARE UPDATE EXCL.       X X X X X VACUUM, ALTER TABLE, CREATE INDEX CONCURRENCY
SHARE     X X   X X X CREATE INDEX
SHARE ROW EXCL.     X X X X X X CREATE TRIGGER, ALTER TABLE
EXCL.   X X X X X X X REFRESH MAT, VIEW CONCURRENTLY
ACCESS EXCL. X X X X X X X X DROP, TRUNCATE, VACUUM FULL, LOCK TABLE, ALTER TABLE, REFRESH MAT. VIEW

 

-- waiting_pid, waiting_query	대기 중인 쿼리 및 세션 정보
-- holder_pid, holder_query	해당 잠금을 보유 중인 쿼리 및 세션
-- locked_relation	충돌 중인 테이블 이름
-- waiting_mode, holder_mode	잠금 모드 (RowExclusiveLock 등)
-- kill_holder_query	해당 holder 세션을 강제 종료하는 SQL 문

WITH lock_conflicts AS (
  SELECT
    w.pid        AS waiting_pid,
    w.query      AS waiting_query,
    w.usename    AS waiting_user,
    w.application_name AS waiting_app,
    w.state      AS waiting_state,
    w.query_start AS waiting_query_start,
    l1.relation::regclass AS locked_relation,
    l1.mode      AS waiting_mode,
    h.pid        AS holder_pid,
    h.query      AS holder_query,
    h.usename    AS holder_user,
    h.application_name AS holder_app,
    h.state      AS holder_state,
    h.query_start AS holder_query_start,
    l2.mode      AS holder_mode,
    -- Kill query 자동 생성
    'SELECT pg_terminate_backend(' || h.pid || ');' AS kill_holder_query
  FROM
    pg_locks l1
    JOIN pg_stat_activity w ON l1.pid = w.pid
    JOIN pg_locks l2 ON l1.locktype = l2.locktype
                    AND l1.database IS NOT DISTINCT FROM l2.database
                    AND l1.relation IS NOT DISTINCT FROM l2.relation
                    AND l1.page IS NOT DISTINCT FROM l2.page
                    AND l1.tuple IS NOT DISTINCT FROM l2.tuple
                    AND l1.virtualxid IS NOT DISTINCT FROM l2.virtualxid
                    AND l1.transactionid IS NOT DISTINCT FROM l2.transactionid
                    AND l1.classid IS NOT DISTINCT FROM l2.classid
                    AND l1.objid IS NOT DISTINCT FROM l2.objid
                    AND l1.objsubid IS NOT DISTINCT FROM l2.objsubid
                    AND l1.pid <> l2.pid
    JOIN pg_stat_activity h ON l2.pid = h.pid
  WHERE NOT l1.granted AND l2.granted
)
SELECT * FROM lock_conflicts
ORDER BY waiting_query_start;

 

Multi Transactions

  • Shared lock mode는 서로 호환 가능한 lock mode 로, 요청할 경우 Multi transaction을 허용
    • Multixact ID (multi transaction id)를 별도로 적용하여 shared lock상태를 저장
  • Tuple 은 어느 시점에 Transaction ID와 Multixact ID가 동일한 값을 가질 수 있음
  • Multi transaction 상태는 pgrowlocks extension 을 통해서도 확인 가능
    • pgrowlocks를 조회하면 lock을 획득한 process, lock mode, tuple 정보 및 multi transaction 상태 등을 확인 가능
    • 현재 상태만 확인이 가능하고, 종료되면 확인이 불가능

Tuple lock waiting

  • Exclusive lock으로 인해 동일 모드로 lock을 요청하는 다른 Transaction 은 락을 대기할 수 밖에 없음.
  • pg_locks View를 통해 확인이 가능
# pg_stat_activity : 현재 데이터베이스의 모든 세션 상태(쿼리, 접속자, 실행 시간 등)를 보여주는 뷰
# pg_locks : 현재 데이터베이스 서버의 모든 활성 세션이 보유하거나 대기 중인 Lock 정보를 보여주는 시스템 뷰
# 어떤 세션이 어떤 Lock을 보유하거나 대기 중인지, 쿼리 내용과 Lock 보유 시간까지 한 번에 확인
# datname: 데이터베이스명
# locked_relation: 잠금 대상(테이블명 등)
# mode: Lock 종류
# granted: Lock 보유 여부(true/false)
# query: 실행 중인 쿼리
# lock_age: Lock 보유 시간(경과 시간)
# pid: 프로세스 ID

SELECT
  a.datname,
  l.relation::regclass AS locked_relation,
  l.transactionid,
  l.mode,
  l.granted,
  a.usename,
  a.query,
  a.query_start,
  age(now(), a.query_start) AS lock_age,
  a.pid
FROM pg_stat_activity a
JOIN pg_locks l ON l.pid = a.pid
ORDER BY a.query_start;

# blocking session 
# Lock으로 인해 대기 중인 세션과 이를 블로킹하는 세션을 한 번에 확인
SELECT
  COALESCE(blockingl.relation::regclass::text, blockingl.locktype) AS locked_item,
  now() - blockeda.query_start AS waiting_duration,
  blockeda.pid AS blocked_pid,
  blockeda.query AS blocked_query,
  blockedl.mode AS blocked_mode,
  blockinga.pid AS blocking_pid,
  blockinga.query AS blocking_query,
  blockingl.mode AS blocking_mode
FROM pg_catalog.pg_locks blockedl
JOIN pg_stat_activity blockeda ON blockedl.pid = blockeda.pid
JOIN pg_catalog.pg_locks blockingl
  ON (
    (blockingl.transactionid = blockedl.transactionid)
    OR (blockingl.relation = blockedl.relation AND blockingl.locktype = blockedl.locktype)
  ) AND blockedl.pid != blockingl.pid
JOIN pg_stat_activity blockinga ON blockingl.pid = blockinga.pid
  AND blockinga.datid = blockeda.datid
WHERE NOT blockedl.granted
  AND blockinga.datname = current_database();


# https://blog.ex-em.com/1928
SELECT current_timestamp AS db_time,
       waiter_pid,
       w_info.usename AS waiter_user ,
       w_info.query   AS waiter_query ,
       w_info.query_start AS waiter_query_start ,
       case
           when EXTRACT(EPOCH from current_timestamp - w_info.query_start ) < 0 then 0
           else EXTRACT(EPOCH from current_timestamp - w_info.query_start ) 
       end as waiter_elapsed_time, 
       holder_pid ,
       h_info.usename AS holder_user ,
       h_info.query   AS holder_query ,
       h_info.query_start AS holder_query_start,
       case
           when EXTRACT(EPOCH from current_timestamp - h_info.query_start ) < 0 then 0
           else EXTRACT(EPOCH from current_timestamp - h_info.query_start )
       end as holder_elapsed_time
FROM   (
              SELECT snaptime,
                     locktype,
                     waiter_pid,
                     w_cnt,
                     h_cnt ,
                     CASE
                            WHEN h_cnt=Max(h_cnt) OVER(partition BY waiter_pid) THEN holder_pid
                     END AS holder_pid
              FROM   (
                            SELECT current_timestamp                             AS snaptime,
                                   blocked_locks.locktype                        AS locktype,
                                   blocked_locks.pid                             AS waiter_pid,
                                   count(*) over(partition BY blocked_locks.pid) AS w_cnt,
                                   count(*) over(partition BY blocking_locks.pid)    h_cnt,
                                   blocking_locks.pid                             AS holder_pid
                            FROM   pg_catalog.pg_locks blocked_locks
                            JOIN   pg_catalog.pg_locks blocking_locks
                            ON     blocking_locks.locktype = blocked_locks.locktype
                            AND    blocking_locks.DATABASE IS NOT DISTINCT
                            FROM   blocked_locks.DATABASE
                            AND    blocking_locks.relation IS NOT DISTINCT
                            FROM   blocked_locks.relation
                            AND    blocking_locks.page IS NOT DISTINCT
                            FROM   blocked_locks.page
                            AND    blocking_locks.tuple IS NOT DISTINCT
                            FROM   blocked_locks.tuple
                            AND    blocking_locks.virtualxid IS NOT DISTINCT
                            FROM   blocked_locks.virtualxid
                            AND    blocking_locks.transactionid IS NOT DISTINCT
                            FROM   blocked_locks.transactionid
                            AND    blocking_locks.classid IS NOT DISTINCT
                            FROM   blocked_locks.classid
                            AND    blocking_locks.objid IS NOT DISTINCT
                            FROM   blocked_locks.objid
                            AND    blocking_locks.objsubid IS NOT DISTINCT
                            FROM   blocked_locks.objsubid
                            AND    blocking_locks.pid != blocked_locks.pid
                            WHERE  NOT blocked_locks.granted ) t ) t2
JOIN   pg_catalog.pg_stat_activity w_info
ON     w_info.pid = t2.waiter_pid
JOIN   pg_catalog.pg_stat_activity h_info
ON     h_info.pid = t2.holder_pid
WHERE  holder_pid IS NOT null;

db_time                      |waiter_pid|waiter_user|waiter_query                                        |waiter_query_start           |waiter_elapsed_time|holder_pid|holder_user|holder_query                                        |holder_query_start           |holder_elapsed_time|
-----------------------------+----------+-----------+----------------------------------------------------+-----------------------------+-------------------+----------+-----------+----------------------------------------------------+-----------------------------+-------------------+
2024-04-30 12:08:49.078 +0900|   2914359|postgresdt |update lock_test set c2 = 'row_update' where c1 = 1;|2024-04-30 12:07:55.777 +0900|          53.301024|   2914369|postgresdt |update lock_test set c2 = 'row_update' where c1 = 1;|2024-04-30 12:07:50.784 +0900|          58.294596|
2024-04-30 12:08:49.078 +0900|   2914369|postgresdt |update lock_test set c2 = 'row_update' where c1 = 1;|2024-04-30 12:07:50.784 +0900|          58.294596|   2914377|postgresdt |update lock_test set c2 = 'row_update' where c1 = 1;|2024-04-30 12:07:45.711 +0900|          63.366759|

 

  • Lock_timeout
    • lock_timeout은 PostgreSQL에서 쿼리가 Table, Index , tuple 등 데이터베이스 객체의 잠금(Lock)을 획득하기 위해 대기하는 최대 시간(밀리초 단위)을 설정하는 parameter
    • 설정 단위: 세션(Session) 또는 트랜잭션(Transaction) 단위로 설정 가능
    • Default: 0 (disable, 무한정 대기)
    • 적용 대상: 명시적/암시적 Lock 요청(예: DML, DDL, LOCK TABLE 등)
    • 동작: 지정한 시간 내에 Lock을 획득하지 못하면 쿼리가 취소되고, canceling statement due to lock timeout 오류가 발생
    • statement_timeout과 차이: statement_timeout은 쿼리 전체 실행 시간을 제한하지만, lock_timeout은 Lock 획득 대기 시간만 제한 ->  둘 다 설정되어 있으면, 먼저 도달한 타임아웃이 우선 적용
# ms 단위
set lock_timeout = 5000;
set lock_timeout = '5s';
ALTER TABLE mytable ADD COLUMN newcol int;

 

  • Deadlock
    • PostgreSQL은 데드락 상황을 자동으로 감지하고, 관련 트랜잭션 중 하나를 강제로 중단(ROLLBACK)시켜 교착상태를 해소.
    • 어떤 트랜잭션이 중단될지는 예측할 수 없음
    • deadlock_timeout parameter 
      • Deadlock 상황을 일정한 주기로 체크하여 감시하는 parameter
      • Default : 1000ms
    • Deadlock 방지 및 해결 전략
      • 락 획득 순서 일관성 유지: 모든 트랜잭션이 동일한 순서로 Lock을 획득하도록 설계하면 데드락 발생 가능성을 크게 줄일 수 있음
      • 작은 배치 크기: 한 번에 처리하는 데이터의 범위를 줄여 Lock 충돌 가능성을 낮춤
      • 트랜잭션 분리: DDL(DROP, TRUNCATE 등) 작업과 DML(INSERT, UPDATE 등)을 분리하여 실행
      • 재시도 메커니즘: 데드락 발생 시 자동으로 트랜잭션을 재시도하도록 애플리케이션을 설계
      • Lock 대기 시간 제한: lock_timeout 파라미터를 설정해 장기 대기를 방지
반응형

Architecture 중 WAL 에 대해 정리 하였습니다.

내용이 중복되는 내용도 있지만, 공식 문서에만 있는 수준의 정보도 있습니다. 쉽게 정리하는 것이 중요하지만, 이해를 못하더라도 연관 되는 내용이 반복되는 경우가 존재하기 때문에, 이런 내용도 있구나 하고 넘어 가는 것도 좋다고 생각합니다.

 


Postgresql Architecture

https://bitnine.tistory.com/549

 

WAL 동작에 대한 간략한 이해도

https://cloud.google.com/architecture/performing-pitr-postgresql

WAL(미리 쓰기 로그) : 파일이 변경되기 전에 데이터 파일의 변경사항이 WAL에 기록
WAL 레코드 : 데이터베이스에 적용된 각 트랜잭션이 WAL 레코드로 형식이 지정되어 저장
세그먼트 파일 :
  • 세그먼트 파일은 단조 증가하는 파일 이름을 사용하고 가급적 많은 WAL 레코드를 포함
  • 파일 크기는 구성 가능하며 기본값은 16MiB
  • 크기 또는 개수 측면에서 대량 트랜잭션이 예상되는 경우 생성되는 세그먼트 파일의 총 수를 줄이고 파일 관리 부담을 줄이기 위해 더 큰 크기를 선택할 수 있습니다.
  • MySQL 은 WAL 전략을 채택하여 Redo Log Buffer에 transaction log 를 작성(redo log를 뜻함)
  • WAL Buffers 내용을 WAL Files에 기록
  • WAL Log / Transaction Log / Redo log
  • sync / async mode 존재
  • Segment file size : 16mb
  • Log file 로 분할된 segment 개수 64개 (wal_keep_segments -> Logical log file)
    • max_wal_size 만큼 생성 (64개 넘어가면 한개 삭제되고 한개 생성되는 형태 -> rotation 이 아님)
    • 64*16 = 1024mb ( max_wal_size 1gb)
    • min_wal_size (80mb = 16*5 = 5개 생성되고 이후 한개 삭제, 한개 생성)
    • 해당 File 를 이용하면 CDC 프로그램도 개발이 가능
    • pg_resetxlog
      • 9.6 이하에서 사용하였으며, pg_resetwal 로 변경
      • WAL 파일을 reset 하는 명령어
      • pg_resetwal
        • WAL 초기화 진행 및 pg_control 파일에 저장된 일부 제어 정보도 초기화
        • 서버가 crash 로 인해 손상되어 시작이 안될 때 강제 부팅하는 역할이 필요할때만 진행
        • 데이터 일관성 문제 발생할 수 있으며, 별도 복구가 반드시 필요
    • pg_switch_xlog()
      • 강제로 WALog swiching
    • pg_xlogdlump
      • wal log 작성된 내용 확인
      • pg_xlogdump ./000000001000000012341223 (WAL log file)
      • 데이터 번호/ schema 번호 / 디비번호 형태로 결과
    • 병목 현상
      • block 단위로 WAL log 에 기록 다음 checkpoint 가 발생하기 전까지 레코드 단위로 WAL log 기록
      • log size 늘리거나 디스크를 좋은 것 사용 외에는 해결 불가
      • full_page_writes parameter 를 off 하여 속도 향상
      • Vacuum freeze를 하면 XID frozen 작업으로 인해 레코드 변경이 발생
      • 1bit 변경됐지만 full_page_writes 기능으로 인해 block 단위 redo가 생성
  • 모든 변경 기록을 보관(transaction log)
    • 무결성을 보장하기 위한 작업
    • Tip - Journaled file 은 불필요
      • WAL 가 안정적으로 저장하기 때문에, Journaled file이 필요없음
        • journal 
        • Journal 은 오버헤드로 인해 성능 저하를 유발(Disk flush / 단, ext3 일 경우 data=writeback 옵션을 사용하여 data flush 진행 시 발생할 수 있는 오버헤드를 줄일 수 있음)
        • Journal 은 DB Crash 발생 시 부팅 속도를 향상할 수 있는 효과
  • 복구를 위해 기록하는 것으로, DB가 crash 되었을 경우 check point 동작 전이라고 하면 디스크에 저장이 안되어 유실되는데, 이것을 방지하기 위해 WAL 로그를 읽어와 복구 진행
    • os buffer cache 영역에는 자주 요청되는 디스크 블록을 저장하지만, 이건 Disk 로 바로 write 되도록 postgresql 에서 강제로 할 수 있음.(wal_sync_method parameter 참고)
    • Disk 의 실제 페이지에 DML하기전에 주기적으로 전체 페이지 이미지를 WAL에 영구적으로 저장
    • 그래서 Crash가 발생하더라도 WAL에서 부분적으로 작성된 페이지를 복원이 가능
    • WAL 파일의 각 레코드는 레코드 내용이 올바른지 확인할 수 있는 CRC-32(32bit)검사로 작성 및 보호 (복구 시 확인)
      • WAL 레코드에 기록된 전체 페이지 이미지는 보호되지만, 데이터 페이지는 기본적으로 chechsum 되지 않음 
    • 데이터만 WAL에 작성이 되고, 통계정보 관련된 각종 시스템 정보들은 저장되지 않고, 복구 진행 시 최근 변경사항으로 재구축-재설정 되도록 동작
    • Temptable 정보도 저장되지 않음
  • WAL 를 사용하면 Disk write 수가 줄어드는데, Transaction 에 의해 변경된 모든 데이터가 아닌, commit 보장하기 위한 WAL 파일만 disk 에 flush 하면 되기 때문 -> 데이터 페이지를 Flush 하는 비용보다 더 저렴
    • 특히, 소규모 transaction 을 동시에 처리하는 시스템에서 WAL 파일의 fsync 하나만으로 많은 transaction을 commit 하는데 충분하기 때문
    • fsync (boolean)
      • fsync가 on이면, PostgreSQL서버는 fsync()시스템콜을 통해서 변경분을 디스크에 물리적으로 바로 쓴다. 이는 데이터베이스클러스터가 OS나 하드웨어 장애시 consistent한 상태로 복구가 가능함을 보장한다.
      • fsync를 off한다면, OS가 알아서 메모리에 있는 것을 디스크로 내려쓰게 된다. 언제 무엇이 디스크에 쓰여졌는지 아닌지 알수 없다. 그러므로 성능상 이득을 볼수는 있겠지만, 전원장애나 system crash로가 발생했을때 복구가 불가능할수 있다. 만약 전체 데이터베이스를 쉽게 재생성할수 있는 경우에만 off하도록 한다. 예를들어 백업본으로부터 새로운 데이터베이스 클러스트를 초기 구축하는 경우, 버리고 재생성할 데이터베이스의 데이터 처리, 자주 재생성되는 read-only 데이터베이스 복제본으로 failover에 사용되지 않는 데이터베이스인 경우 사용할 수 있다. 고성능의 하드웨어 장비라고해서 fsync를 끄는 것은 올바르지 않다.
    • 성능을 위해서라면 synchronous_commit을 off하는 것만으로 충분할 것이다.
    • 만약 off하기로 했다면, full_page_writes도 off하는 것을 고려하도록 한다.
  • WAL 를 사용하여, PITR 지원이 가능
    • WAL 를 재생하여 복구가 가능
  • Asynchronous Commit
    • DB Crash 발생 시, 가장 최근의 Transaction은 유실 가능성 존재(Risk)
    • 다만,  transaction 을 더 빠르게 처리가 가능
    • Default 는 synchronous (동기)
      • Client에 transaction result를 return 하기 전에, WAL 의 record가  Disk 에 write될 때까지 기다렸다가 ack 처리. -> commit 된 transaction 에 대한 무결성 보장을 의미
      • 다만, 이것에 대한 비용은 발생
    • 이러한 비용을 조금이라도 줄이기 위한 방법으로, disk flush 응답을 받기 전에 WAL buffer에서만 작성 후 ack 하는 방식을 Asynchronous Commit  -> Transaction 처리량 향상
    • synchronous_commit  Parameter 를 이용하여 수정이 가능하며, transaction 마다 설정이 가능
      • 트랜잭션 커밋하기전에 WAL 레코드가 disk까지 쓰인다음에 success를 리턴할 것인가 여부
      • on : default
      • off : 클라이언트에 바로 transaction commit을 보냄. 하지만 실제로 트랜잭션이 안전하게 반영(WAL record가 WAL file에 쓰여짐) 되기까지 딜레이가 존재함. 서버 crash났을때 트랜잭션 손실될 수 있음. (최대 delay는 wal_writer_delay(200ms)의 3배). 하지만 fsync와는 달리 off로 한다고 해서 db 일관성에 문제가 되지는 않음. 최근 커밋되어야하는 트랜잭션이 손실될 수는 있으나 database 상태는 이 트랜잭션들이 정상적으로 롤백된 것과 같아서 일관성에 문제 없음.
    • DROP TABLE 은  synchronous_commit 와 상관없이 동기식으로 처리 : 일관성을 보장하기 위함.
    • PREPARE TRANSACTION 와 같은 2-phase commit 의 경우도 동기식으로 처리
    • wal_writer_delay  ms 마다 WAL Writer 가 Disk 로 flush 진행
      • 최대 유실 시간은 wal_writer_delay milliseconds * 3 의 risk 가 존재
        • busy periods(바쁜기간?)에 WAL writer 가 한 번에 Disk 로 flush 를 진행하는 것을 선호로 인해 발생 (정확한 의미는 모르겠습니다. 왜 wal_writer_delay 의 최대 3배의 시간으로 명시했는지.
    • commit_delay parameter 와 유사하지만, commit_delay 는 synchronous commit 의 방법에 속함
      • Asynchronous commit 을 설정하면, commit_delay 는 무시
      • commit_delay 는 transaction 이 WAL disk flush 지연을 유발

https://minsql.com/postgres/PostgreSQL-synchronous_commit-%EA%B0%9C%EB%85%90%EB%8F%84/

 
  • WAL Configuration
    • 참고로 checkpoint 는 checkpoint 이전까지의 기록된 transaction 의 sequence 지점
      • checkpoint 를 기준으로 모든 Dirty data page가 Disk 로 flush 되고 특수 checkpoint record가 WAL 파일에 기록
      • DB Crash 가 발생하면, 최신 checkpoint 지점을 찾은 후, WAL 에서 시작해야 되는 시점을 찾아 복구 진행
      • 최소 Checkpoint 지점까지는 Disk 에 있는 것을 보장되며, WAL 기준으로는  checkpoint 이전 파일은 필요 없으며, 이전 segment 또한 필요가 없음을 의미하고 재활용 되거나 제거할 수 있음을 의미
      • full_page_writes
        • Default on
        • checkpoint 이후 각 Disk page를 수정(dml로 인해) 하는 도중에 해당 페이지의 전체 내용을 WAL 에 기록
          • os crash 발생 시, 진행 중인 페이지 쓰기가 부분적으로만 완료 되어 디스크 상의 페이지에 과거 데이터와 새 데이터가 공존하기 때문에, 복구 진행 시 올바른 복구가 보장되지만 WAL 에 기록하는 데이터량은 증가될 수 밖에 없음
          • checkpoint 간격을 늘릴 수 있음.
        • off 하면, 운영 속도가 향상되지만, 시스템 crash 발생 시, 손상된 데이터가 복구 불가능 또는 데이 손사이 발생 가능성 존재
          • fsync 를 해제했을 때와 유사
          • PITR 진행 시, WAL 아카이빙 사용에는 영향을 미치지 않음
          • postgresql.conf 파일 또는 command 로 설정 가능
      • checkpoint_timeout sec 마다 또는 max_wal_size 가 초과될 경우 checkpoint 발생
        • default : 5 min (300 sec) / 1 Gb
        • WAL 에 작성이 안된 경우 checkpoint_timeout 이 되어도 checkpoint 가 발생하지 않음
        • 물론  CHECKPOINT 명령어로 강제 실행은 가능
        • 만약 해당 변수 값들을 줄이며, 더 자주 checkpoint 발생 : DB crash 후 빠른 복구는 가능하지만 Disk I/O 비용 발생
        • full_page_writes 가 설정된 경우 (default 설정) , check point 발생 후 데이터 페이지 수정이 발생하는 경우 전체 페이지가 기록 -> 더 많은 Disk I/O 발생(시간을 짧게 가져갈수록 disk I/O는 더욱더 증가)
        • checkpoint_timeout 시간을 충분히 여유있게 가져가는 것을 책에서는 권장하며, checkpoint_warning 로 checkpoint 에 대해 sanity check (온전성 검사?)가 가능
        • 당연하겠지만, checkpoint timeout 보다 더 잦은 checkpoinrt 가 발생하면 max_wal_size 증가 하라는 메시지가 log 에 남음
      • XLogInsertRecord
        • Shared memory 내 WAR buffer 에 new record 를 배치하는데 사용
        • 만약 새로운 데이터가 insert 될 공간이 없으면, kernel cache 로 채워진 WAL buffer 일부를 move 시킴 -> 이것은 exclusive lock (대상 data page) 을 유발하기 때문에, 성능에 좋지 않음
        • 일반적으로 XLogFlush  에 의해 WAL buffer 를 WAL file 에 flush 되어야 함.
        • wal_buffers 를 이용하여 WAL bufferpool 수를 조정 가능하며, full_page_writes 가 설정되어 있고, 시스템이 바쁜경우 wal_buffers 수를 늘려주는 것을 권장
      • XLogFlush
        • LogWrite 의 issue_xlog_fsync 의 호출에 의해 XLogFlush 가 WAL buffer 의 내용을 disk 로 flush
        • commit_delay
          • commit_delay (ms)는 XLogFlush 내에서 lock 을 획득 하는동안, group commit 을 기다리는 시간(지연시간) parameter
          • 목적은 동시에 commit 되는 transaction 전체에 걸쳐 flush 작업의 비용을 분할할 수 있는 것(다만 transaction 시간은 증가)
            • commit_delay 가 발생하면, 대기하는 commit record들은 WAL Buffer 에 추가 하여 정합성을 지키는 효과 -> Transaction 대기 시간을 최소화
          • 0 : ( default)그룹 커밋의 효과가 중요
          • 1 : 동시에 commit 이 발생하여 transaction 처리?
          • 2 : 처리량이 commit 속도에 따라 제한 되는 경우 도움 ?
      • wal_debug 를 활성화 하면, XLogInsertRecord, XLogFlush 의 call 횟수를 확인할 수 있음.
      • WAL 데이터를 Disk 에 Writer 하는 함수는 XLogWrite and issue_xlog_fsync  존재
      • WAL 데이터를 동기화하는 데 소요되는 시간은, pg_stat_wal 내의  wal_write_time and wal_sync_time 에 기록
      • wal_sync_method 가  open_sync 또는 open_datasync  이면, XLogWrite는 WAL 데이터를 Disk 로 flush 하고 데이터 정합성을 보장하고, issue_xlog_fsync 는 어떠한 작업도 하지 않음
      • wal_sync_method 가  fdatasync , fsync , 또는 fsync_writethrough 가 되면, write opertaion이 WAL buffer 를 kernal cache로 이동하고 issue_xlog_fsync 는 kernal cache 내용을 Disk로 동기화 진행
      • XLogWrite Write 및 issue_xlog_fsync 가 WAL 데이터를 disk 로 동기화하는 횟수가, pg_stat_wal 에서 wal_write 및 wal_sync 값으로 저장
    • WAL Internal
      • WAL 는 rotation 하기 때문에, 관리자가 별도로 disk 관련하여 신경쓰지 않아도 됨
      • LSN 으로 어느 WAL 파일에 작성해야 하는지 (마지막 지점) 확인
      • WAL 파일은 Data directory 아래 pg_wal directory 에 segment file 로 저장되며, 16mb
      • WAL 파일 은 별도의 Disk 공간으로 분리 하는 것이 유리
        • pg_wal directory를 symbolic link 로 처리하면 가능
      • Checkpoint 가 발생 시, WAL 내용도 flush 된 후, checkpoint 의 값은 pg_control 에 저장
        • DB Crash 이후 recovery 진행할 때, 가장 먼저 pg_control 값을 확인 후, 이후 부터 wal 파일 조회하여 복구 진행
        • pg_control 이 손상되면, 최신 checkpoint 를 찾기 위해  WAL segment 를 역순으로 체크하면 되는데, 이것은 아직 구현되지 않음
        • 또한, pg_control 이 손상되어 DB를 복구 하지 못한 것은 아직 보고가 되지 않음

 

그 외 좋은 Tip 소개

[Why PostgreSQL WAL Archival is Slow]

  • Disk 사용량이 급증하는 경우, WAL (pg_wal) Directory 의 size 가 높은 경우가 종종 발생
  • 왜 rotation 안되고, 증가하는 것인지에 대한 의문
  1. WAL Archival 의 실패
  2. 오래된 WAL의 slot 을 holding 하는 경우
  • 최근에는, WAL segment 생성 속도에 비해, 삭제 속도가 느려서 발생하는 현상도 추가
    • Host (Instance) 서버의 처리 능력 증가
    • 파티셔닝 기능의 발전이나 대량 데이터 로딩 이 개선되면서 Postgresql 의 확장성 향상
    • 빠른 차세대 Storage 로 인해 처리량 증가이로 인해 WAL 가 생성되는 속도가 증가
  • 이로 인해 WAL 가 생성되는 속도가 증가
  • WAL-G 또는 pgBackRest 와 같은 백업 솔루션이 내장된 압축 기능으로 문제 해결도 가능
그 외 세부내용은 해당 블로그 확인
 
 

 

반응형

1. PostgreSQL 이란 ?

- PostgreSQL 은 캘리포니아 대학 버클리의 컴퓨터 과학 학과에서 개발된 POSTGRES, Version 4.2 를 기반으로 한 객체 관계형 데이터베이스 관리 시스템( ORDBMS )

- POSTGRES는 나중에 여러 상용 데이터베이스에서 사용할 수 있게 되었던 많은 개념에 대한 선구자

- PostgreSQL 은 오리지널 버클리 학교의 소스 코드를 인수한 오픈 소스 데이터베이스로 표준 SQL의 대부분과 다른 최신 기능을 지원

PostgreSQL 는, 여러가지 방법으로 유저가 확장 가능

  • 데이터 유형
  • 기능
  • 연산자
  • 집계 함수
  • 인덱스 메소드
  • 절차 언어

게다가 자유주의적 라이센스(Postgresql license 라고 하는데, 이건 BSD/MIT 라이센스 계열) 조건에 의해, PostgreSQL 은 누구에게나, 그 사용, 변경 , 배포를 개인 사용, 상용, 학술 등, 목적에 한정하지 않고 무상으로 가능

소스를 공개할 의무도 없고 아무런 제약도 없음. (다만 mysql, mariadb 의 경우 GLP 라이센스이라, fork 한 상용 DB라면 소스공개가 의무)

 

2. Postgres 95

- Postgres 에 SQL 언어를 추가

- ANSI C 로 재작성되어, 기존 코드에서 25% 정리되었으며 많은 내부 성능 향상 및 유지 보수성이 향상

- 기존 POSTGRES 에 비해 Benchmark에서 30~50 성능 향상

 

3. PostgreSQL

- 기존 Postgres95 이름에 대한 이야기가 많아 새로운 네이밍 생성

- POSTGRES 와 SQL 기능이 탑재된 최신 버전 반영한 PostgreSQL 이라는 네임 변경

- 6.0 으로 시작하는 버전 번호 설정

 

4. 그 외
- PostgreSQL wiki 는 프로젝트 FAQ (FAQ) 목록, TODO 목록 및 더 많은 주제에 대한 정보를 포함

- PostgreSQL  웹 사이트 에는 최신 릴리스에 대한 자세한 내용과 PostgreSQL 의 이용 및 조작을 할 때 생산성을 더욱 높이는 정보 제공

- PostgreSQL 은 오픈 소스 프로젝트라서, 소스 기여도 가능

- 버그 리포트 관련한 내용 (https://www.postgresql.jp/document/16/html/bug-reporting.html)

 

참고

https://www.postgresql.jp/document/16/html/preface.html

 

はじめに

<!-- Preface --> <!-- This book is the official documentation of PostgreSQL . It has been written by the PostgreSQL developers and other volunteers in parallel to the development of the PostgreSQL software. It describes all the functionality that the curre

www.postgresql.jp

https://wiki.postgresql.org/wiki/Main_Page

 

PostgreSQL wiki

 

wiki.postgresql.org

 

반응형

+ Recent posts