Blog on Iterating

PostgreSQL, Docker 컨테이너 환경에서 Kubernetes Pod로 이전

내가 생각한 계획

  1. PostgreSQL을 Pod로 실행한다.

  2. 컨테이너에 저장된 데이터를 Pod로 Replication 한다.

  3. 데이터가 모두 옮겨지면 Pod를 마스터로 설정한다.

  4. 컨테이너를 제거한다.

잘 모르는 지식들

  • Replica 설정은 AWS에서 제어해본 것이 전부였다. 프로그램을 직접 수정해보는 것은 처음이다.

  • 현재 사용하고 있는 포트 번호는 내 나름의 규칙을 적용한 값이다. 지금의 포트를 꼭 사용하고 싶다면 어떻게 해야할까?

  • 그 전에 어떤 원리로 Master 서버가 죽으면 Standby 서버로 알아서 대체되는 건가?

  • MySQL만 써봤는데 PostgreSQL이라고 다를 게 있을까?

1. PostgreSQL 문서 확인

  • High Availability - 여러 서버가 함께 동작하다가 Primary Server가 중단되면 다른 서버가 이를 대체하는 것.

    • Shared Disk Failover - 하나의 디스크를 가지고 Primary Server와 Standby Server가 필요시 마운트.

    • File System (Block Device) Replication - 두 개의 디스크 간에 파일 시스템을 복제하여 Primary Server, Standby Server 각각이 사용.

    • Write-Ahead Log Shipping - Warm, Hot Standby Server가 Primary Server의 WAL를 스트림으로 읽다가 Primary Server가 중단되면 빠르게 이를 대체하는 것.

    • Logical Replication - 물리적 장치가 아닌 데이터 변경에 대해서 테이블 단위로 복제한다.

    • Trigger-Based Primary-Standby Replication - 테이블에서 일어나는 변경에 대해 트리거를 설정해서 복제한다.

    • SQL-Based Replication Middleware - 미들웨어 프로그램이 SQL을 가로채서 모든 서버에 전파하는 방식으로 복제한다.

  • Load Balancing - 여러 서버가 동일한 데이터를 가지고 서빙하는 것이다.

  • Primary Server - 읽기/쓰기 기능을 가진 서버이다.

  • Standby Server - Primary Server의 장애 발생 시 이를 대체하는 서버이다.

    • Warm Standby Server - 장애 발생 즉시 Primary Server로 승격되기 위한 서버이다. 승격 전까지는 사용되지 않는다.

    • Hot Standby Server - 장애 발생 시 Primary Server로 승격될 수 있지만 평소에는 읽기 용도로 사용되는 서버이다.

  • Replication 종류

    • Streaming Replication - 항상 생성되는 WAL를 TCP로 전송하는 것이다. Primary Server가 매 순간 전달해주는 것이 아니고 Standby Server가 특정 LSN 이후의 데이터를 요청하여 가져가는 방식이다.

    • Logical Replication - WAL 내부에 있는 데이터 변경 쿼리에 대해서만 복제한다. 특정 테이블을 지정할 수 있다.

    • Cascading Replication - 모든 Standby Server가 Primary Server를 직접 바라보지 않고, Standby Server 간 릴레이로 전달한다.

  • restore_command - Standby Server가 WAL 파일을 가져오는 명령어이다.

  • WAL - Write Ahead Log, 데이터베이스 전체의 변경을 기록하는 로그. 각 레코드는 LSN(Log Sequence Number)를 가지며 클러스터 식별을 위한 System Identifier 등의 정보를 포함한다.

  • WAL 아카이브 - 모든 WAL를 보관하는 장소이다.

  • Replication Slot - 모든 Standby Server가 WAL을 누락 없이 받을 때까지 WAL을 유지하며, Standby Server의 연결이 종료되어있어도 WAL를 유지한다.

  • Base Backup - 데이터베이스 전체의 스냅샷이다. WAL 데이터도 포함하고 있어 Standby Server는 복원 직후 마지막으로 생성된 WAL의 다음 WAL를 요청한다. PostgreSQL 전체를 구성하므로, 이 스냅샷을 내려받는 디렉토리는 기본 데이터베이스 디렉토리와 별개여야 한다.

  • Disaster Recovery - Base Backup로부터 데이터를 복구하는 것이다.

  • Split-Brain - Primary Server가 둘 이상 생성되어 독립적으로 동작하는 상황이다.

  • PGDATA - 데이터베이스 클러스터 디렉토리를 가리키는 환경 변수이다. Base Backup은 데이터베이스 클러스터를 그대로 복제한 것이므로 PGDATA가 Base Backup이 생성된 디렉토리를 바라본다면 데이터베이스가 교체된다.

동작(Log-Shipping Standby Servers)

  • Standby Server의 standby 모드는 서버가 시작될 때 standby.signal파일이 존재하면 활성화된다.

    • WAL을 가져오기 위해 사용되는 restore_command를 작성한다.

    • TCP 스트리밍을 위해서는 primary_conninfo를 작성한다.

  • Standby 모드에서는 Primary Server로부터 두 방식으로 WAL를 지속적으로 가져온다.

    • File-based log shipping - 아카이브로부터 WAL 파일을 16MB 단위로 전송한다.

    • Streaming Replication - TCP로 WAL 레코드를 계속 전송한다.

  • Standby Server는 중단되거나 Primary Server로 승격될 때까지 다음의 동작을 반복한다.

    • Standby Server는 WAL 아카이브에 저장된 WAL를 복원한다. (restore_command 사용)

    • Standby Server는 WAL 아카이브 복원이 실패하면 로컬 pg_wal 디렉토리에서 WAL를 복원한다.

    • Standby Server는 Streaming Replication이 활성화되어 있다면 마지막 WAL 다음에 생성되는 WAL를 스트리밍으로 가져온다. 스트리밍이 활성화되어 있는 동안에는 스트리밍 동작을 계속 수행하고, 활성화되어 있지 않다면 첫 번째 동작으로 되돌아간다.

  • Standby Server는 pg_promote() 함수가 실행되면 Primary Server로 승격된다.

  • Primary Server는 postgresql.conf를 설정해야 한다.

    • wal_level - replication 정보를 설정하는 옵션이다.

    • max_wal_senders - Primary Server에 연결되는 최대 connection 수.

    • wal_keep_size - WAL 저장 크기이다. 이 크기를 넘으면 오래된 WAL가 삭제된다. Standby Server가 삭제된 WAL를 받지 못하면 replication이 종료되고 새로운 Base Backup으로 초기화되어야 한다.

    • max_replication_slots - 최대 Replication Slot 개수이다.

    • archive_mode - 과거 WAL를 보존할지 여부를 설정하는 옵션이다.

    • archive_command - WAL 파일을 복사하기 위한 명령어이다. cp, rsync 등

  • Primary Server는 Standby Server가 접근할 수 있는 위치에 WAL 아카이브를 생성해야 한다.

  • Primary Server는 streaming을 제공하기 위해 인증 및 권한 설정을 해야한다.

    • Primary Server는 pg_hba.conf를 이용해서 사용자 및 네트워크 접속을 제어할 수 있다.

  • Primary Server는 WAL 생명주기를 다음의 기능들로 관리한다.

    • Replication Slot

    • wal_keep_size

2. 계획 변경

알게된 사실은 PostgreSQL에서 Failover를 자체적으로 지원해주지 않는다는 것이다. 수동으로 작업하거나 외부 관리 프로그램을 이용해야 한다. 그간 이용했던 것은 AWS에서 자동으로 제공해주었던 것이다.

외부 관리 프로그램을 이용할 만큼의 규모가 아니기 때문에 수동 작업을 선택한다.

기존에 있던 Docker 컨테이너가 Primary Server가 된다. 새롭게 추가되는 k8s Pod는 Standby Server가 된다.

  1. Standby Server을 Pod로 생성

  2. Primary Server의 WAL 아카이브 저장위치를 Standby Server가 접근할 수 있는 위치로 설정한다.

  3. Primary Server에서 Standby Server에 대한 Streaming Replication 접근 권한을 추가한다.

  4. Standby Server의 설정 파일에 WAL 아카이브에서 데이터를 가져오는 명령을 작성한다.

  5. Standby Server가 WAL 수신 후 Streaming Replication 순으로 데이터 복제 작업이 진행되기를 기다린다.

  6. Primary Server를 중단한다.

  7. Standby Server를 Primary Server로 승격한다.

  8. 새 Primary Server의 포트를 변경한다.

3. 실행

  1. Docker 컨테이너로 실행 중인 PostgreSQL에 Primary Server 설정을 한다. (postgresql.conf, pg_hba.conf)

  2. Primary Server로 접속할 때 사용되는 인증 계정을 만든다.

  3. WAL 아카이브로 지정한 S3에 WAL 파일이 기록되는 것을 확인한다.

  4. Base Backup 파일을 생성한다.

  5. 새로운 포트로 실행된 k8s Pod PostreSQL에서 Standby Server 설정 후 재시작한다.

  6. Primary Server를 중단하여 새로운 데이터 변경이 발생하지 않도록 한다.

  7. Standby Server의 포트를 중단된 Primary Server의 포트로 변경한다.

  8. Standby Server를 Primary Server로 승격한다.

  9. (옵션) S3에 저장된 WAL 파일을 제거한다.

예외상황

  • 기존에 사용하던 컨테이너의 PostgreSQL 버전은 17이었다. 최신 버전을 사용하려고 하는데 최신 버전은 18이라 버전 호환이 맞지 않아 업그레이드가 필요하다. 이 글은 Replication에 관한 글이기에 다루지 않는다.

  • 무중단 변경은 PostgreSQL 만으로는 불가능하다.
    Primary Server를 구성하기 위해서도 서버 재시작이 필요하다.
    Standby Server를 Primary Server로 승격하는 과정에서도 Split-Brain 상황이 발생하지 않도록 Primary Server를 중단해 데이터 변경이 발생하지 않도록 해야 한다.

  • 3.2
    데이터베이스에는 데이터 변경이 발생하지 않고 있는데 WAL 파일이 16MB 단위로 계속 쌓이는 것이 보였다.
    작업 전에 공부한 내용으로는 WAL 파일은 16MB 크기마다 파일로 아카이빙하는 것이었기 때문에 이상했다. 생각보다 저장공간을 많이 차지하는 것도 문제로 인식되었다.
    확인해보니 archive_timeout 값이 원인이었다. 이것은 Standby Server가 file-based log shipping으로 운영되는 경우, 데이터 변경이 거의 없어 WAL 파일이 16MB까지 채워지지 않는 경우가 많기 때문에, 일정 시간이 지나면 강제로 WAL를 아카이빙하기 위한 목적이었다.
    나는 Streaming Replication을 이용할 것이기에 큰 문제가 되지는 않았지만, 동작여부를 눈으로 확인할 수 있으니 작업동안에는 옵션을 유지했다.

  • 3.2
    WAL 파일이 아카이빙 되는 것을 확인하고 버킷 초기화 목적으로 WAL 파일을 지웠다.
    WAL 파일은 아카이빙과 별개로 항상 PostgreSQL 내부에서 생성되는 데이터로, 그 시점의 유일한 데이터가 된다. 누락이 없다는 원칙상 아카이브에 WAL 파일이 누락되면 replication 동작을 불가능하게 만든다. 이런 경우에는 지워진 WAL 파일보다 이후의 Base Backup파일을 이용해 복구해야 한다.

  • 3.3
    Base Backup은 Standby Server가 Primary Server에 요청하는 형태로 이루어진다. 이때 여러 Standby Server가 요청하게 되면 Primary Server에 큰 부담이 되는데 이것을 어떻게 처리하는지 궁금했다.
    찾아보니 Cascading Replication을 구성해서 Standby Server들이 순차적으로 replication 되도록 하고, 모든 Standby Server가 구성되면 각각에 대해서 다시 Primary Server로 Streaming Replication을 설정하면 된다.

  • 3.4 - 3.5
    pg_basebackup 명령어를 이용하면 Base Backup을 생성함과 동시에 Standby Server 설정이 완료된다. 이제 PGDATA 환경 변수를 Base Backup을 생성한 경로로 지정하고 재실행하면 Standby Server가 실행된다.

  • 3.7
    이 때 App에서 데이터 변경을 시도하면 읽기 전용 서버이므로 명령이 실패한다.

  • 3.8
    재시작은 필요 없다.

  • 3.9
    하루만에 10GB를 사용했다..