이 글은 CloudNet@ 팀 gasida님의 스터디 DOIK 2기 내용 및 실습으로 작성된 글입니다.
What is PostgreSQL?
오픈 소스 객체-관계형 데이터베이스 시스템(ORDBMS)으로, 약 20년 정도의 역사를 가지고 있으며 전 세계 오픈소스 개발자들과 관련 회사들이 개발에 참여하고 있다. 다른 관계형 데이터베이스와 달리 연산자, 복합 자료형, 집계 함수, 자료형 변환자, 확장 기능 등 다양한 데이터베이스 객체를 사용자가 임의로 만들 수 있는 기능을 제공함으로써, 하나의 새로운 프로그래밍 언어처럼 무한한 기능을 쉽게 구현할 수 있다.
관계형 DBMS의 기본적인 기능인 트랜잭션과 ACID(Atomicity, Consistency, Isolation, Durability)를 지원한다.
PostgreSQL 특징
1) 유연한 객체 생성
산자, 복합 자료형, 집계 함수, 자료형 변환자, 확장 기능 등 다양한 데이터베이스 객체를 사용자가 임의로 만들 수 있는 기능을 SQL 차원에서 제공
2) 상속
Java나 C++ 등의 프로그램 언어와 같이 테이블을 만들어서 해당 테이블에 상속 기능을 이용해 하위 테이블 생성 가능
테이블에 저장된 자료는 상위 테이블을 조회하면, 해당 테이블의 하위 테이블에 포함된 모든 자료 조회 가능
하위 테이블 생성 시, 상위 테이블의 컬럼을 상속받으면서 하위 테이블에만 속하는 컬럼 생성 가능
3) 함수
'저장 프로시저'라고 불리는 SQL문으로 작성된 함수를 서버 환경에서 사용 가능
PostgreSQL 구조
PostgreSQL은 Client/Server 모델을 사용한다.
서버는 데이터베이스 파일들을 관리하며, 클라이언트는 어플리케이션으로부터 들어오는 연결을 수용하고 클라이언트를 대신하여 데이터베이스 액션을 수행한다. 서버는 다중 클라이언트 연결을 처리할 수 있으며, 클라이언트의 연결 요청이 오면 각 커넥션에 대해 새로운 프로세스를 fork한다. 그리고 클라이언트는 기존 서버와 간섭 없이 새로 생성된 서버 프로세스와 통신한다.
Helloworld 블로그 김성규님의 게시글 내용 참고하여 작성하였습니다.
클라이언트는 인터페이스 라이브러리를 통해 서버와의 연결을 요청 (1)Initialize Connection 하면, Postmaster 프로세스가 서버와의 연결을 (2)Spawn server 중계한다. 이후 클라이언트는 할당된 서버와의 연결을 통해 질의를 (3)Query Request Response 수행한다.
클라이언트로부터 질의 요청이 올 경우 구문 분석 과정 (1)Syntax Parser을 통해 Parse Tree를 생성한다. 해당 단계에서는 개별 요소들에 대한 의미분석이 불가능하며, 단순 문법체크(Syntax)만 수행한다.
의미 분석 과정 (2)Semantic Parser를 통해 새로운 트랜잭션을 시작하고 Query Tree를 생성한다. Query Rewrite나 최적화가 필요하지 않은 Simple Query(Selete, Drop, Alter, Vacuum 등의 제어문)의 경우 바로 Executor 단계로 넘어간다.
이후 Query Tree에 사전 정의 된 Rule에 따라 Query Tree가 재생성 (3)Rewriter되고, 입출력 모두 Query Tree이다.
실행 가능한 여러 수행 계획 중 가장 최적화 된 Plan Tree를 생성 (4)Planner한다. 주요 결정 사항(Key Decisions)으로 Scan 및 Join Method가 있다.
실행 계획에 따라 Query를 수행 (5)Executor하고, 그 결과를 클라이언트에게 전달한다.
서버의 쿼리 수행 과정에는 DB 내부의 시스템 카탈로그가 많이 사용되는데, 사용자가 함수나 데이터 타입이나 인덱스 접근 방식, RULE 등을 시스템 카탈로그에 정의 할 수도 있다. PostgreSQL에서는 이것이 새로 추가학더나 확장하는 데 있어 중요한 포인트로 활용한다.
데이터가 저장되는 파일들은 여러 개의 페이지로 구성되며, 하나의 페이지는 확장 가능한 Slotted page 구조를 가진다. (데이터 페이지 구조, 인덱스 페이지 구조)
Vacuum
Vacuum은 PostgreSQL에만 존재하는 특수 개념이다. MVCC의 동작원리에 따른 공간 비효율과 XID Wraparound 발생 등 PostgreSQL의 특성에 기인한 부작용을 해소하기 위해 생겨났다. 해당 명령어를 사용하면 오래된 영역을 재사용하거나 정리를 해준다.
Vacuum을 사용하면 어느 곳도 참조되지 않고, 안전히 재사용할 수있는 행을 찾아 FSM(Fres Space Map)이라는 메모리 공간의 그 위치와 크기를 기록한다. 그리고 Insert, Update 등 새로운 행을 추가하는 경우 FSM에서 새로운 데이터를 저장할 수 있는 적당한 크기의 행을 찾아서 사용한다.
Manual Vaccum은 Autovacuum Launcher에 의해 수행되는 것이 아니라, 사용자가 직업 수행하는 것을 의미하며 Vaccum의 목적은 아래와 같다.
- 디스크 공간 확보
- Transaction ID Wraparound로 인한 오래된 자료 손실 방지
- 통계 정보 갱신
- Visibility Map(VM) 갱신 작업
▣ 디스크 공간 확보
PostgreSQL은 위에서 기재하였듯 MVCC에 의해 오래된 데이터를 삭제하지 않은 상태로 남겨놓기 때문에 변경이 빈번하게 발생되는 테이블의 사이즈는 증가하게 된다. 하지만, 오래된 데이터들이 현재 진행형인 트랜잭션에 의해 참조되지 않는다는 것이 보장되면 오래된 데이터는 삭제되거나 재사용 가능한 상태로 변경하여도 무방하기에 Vaccum은 오래된 데이터를 재사용 가능한 형태로 변경하거나 물리적인 공간을 OS로 반환함으로써 디스크 공간을 확보하는 역할을 수행한다.
데이터를 재사용 가능한 형태로 변경하는 것은 Stanard Vaccum이라고 부르고, VACUUM 명령어에 의해 수행된다. 하지만, 오래된 데이터를 OS로 반환하는 것은 데이터를 제외하여 파일을 재구성하는 방식으로 VACUUM FULL 명령어에 의해 수행된다.
Vacuum을 수행하는 것으로 인해 재사용이 가능한 공간 확보는 데이터 증가량을 억제하는 효과가 있기에 최적의 공간 유지의 일부 목적은 달성 가능하다.
Vacuum Full은 일종의 Table Reorganization 작업과 유사하다고 할 수 있다. Live Tuple들만 모아서 새로운 테이블을 구성하고, 기존의 테이블을 삭제하는 과정에서 물리적 공간 반환이 가능한 구조이다. 재구성 방식의 동작은 Table에 대한 Exclusive Lock의 획득 과정이 필요하고, 이는 동시성을 떨어뜨리는 원인이 되기 때문에 사용시 주의가 필요하다. 또한, 신규 테이블을 위한 일정량의 디스크 공간이 있어야 수행 가능하다.
▣ 오래된 자료 손실 방지
Data Freezing 작업은 개별 Tuple(Row)의 Age가 VACUUM_FREEZE_MIN_AGE 보다 큰(오래된) Tuple을 대상으로 한다. VACUUM_FREEZE_MIN_AGE의 값을 줄일 경우 보다 많은 Row Freezing을 시킬 수 있지만 더 많은 시간과 자원을 필요로 한다.
만약 최근 데이터에 대한 조작이 빈번한 테이블의 VACUUM_FREEZE_MIN_AGE 수치를 작게 설정할 경우, Freezing한 최근 데이터들이 다시 ALL_FROZEN=0 상태로 변경될 수 있다. 이런 경우 최근 변경 데이터에 대한 지속적인 Data Freezing 과정을 수행하는 비효율이 발생될 수 있어 용도와 사용 패턴에 맞는 설정을 해야한다.
(Vacuum이 Freeze할 최소 Row이고, 기본 값은 50,000,000이다.)
⊙ Standard Vacuum Mode
내부적으로 두 가지 Mode로 동작한다. Eager Mode, Lazy Mode라고 부르는데 Document에 존재하는 정식 용어는 아니라고 한다. 공식문서에서는 해당 Mode를 Aggressive Vacuum / Vacuum으로 칭하는데, 원글에서 Vacuum Freeze에 사용할 Aggressive Vacuum과 구분을 위해 두 가지 용어로 사용한다고 하였다. (원글: blog.ex-em.com/1667 / DB 인사이드님)
- Mode 기준
두 가지 Mode를 선택 여부는 Table의 Age에 의해 결정된다.
Vacuum이 수행되는 시점에서 Table의 Age가 VACUUM_FREEZE_TABLE_AGE보다 크면(오래된) Eager Mode로 동작하고, VACUUM_FREEZE_TABLE_AGE보다 작다면 Lazy Mode로 수행한다.
* Eager Mode: Visibility Map의 ALL_FROZEN Bit가 0인 모든 페이지
* Lazy Mode: Visibility Map의 ALL_VISIBLE Bit가 0인 모든 페이지
Lazy Mode는 Frozen과 상관없이 Dead Tuple이 있는 페이지(ALL_VISIBLE=0)만이 대상이며, Eager Mode는 Lazy보다 광범위하게 Frozen이 되지 않은 모든 페이지(ALL_FROZEN=0)을 Vacuum의 대상으로 삼는다.
만약 Vacuum이 Lazy Mode 방식 하나만 작동하면, ALL_VISIBLE=0,ALL_FROZEN=1의 상태에 있는 페이지는 Vacuum의 대상 페이지에서 항상 제외될 수 밖에 없다. 공간을 정리하기 위한 작업은 필요하지 않지만, 잠재적으로 Data Freezing의 대상이 될 Row를 갖고 있는 페이지 조차 Vacuum의 대상에서 제외되는 문제가 생길 수 있다.
이런 경우를 대비하여 Table의 Age가 VACUUM_FREEZE_TABLE_AGE보다 큰 경우 ALL_FROZEN=0인 모든 페이지를 대상으로 Eager Mode Vacuunm이 실행된다. Eager Mode로 Vacuum이 동작하는 것은 전체 페이지에 대한 Data Freezing 상태를 모두 확인(모든 Row가 Data Freezing이 불필요한 상태를 확인)하였다는 의미로 Table Age에 사용되는 relfrozenxid에 대한 갱신 작업도 가능하다.
Eager Mode Vacuum이 실행되면 Table의 relfrozenxid는 변경된다.
실습
OLM(Operator Lifecycle Manager) 설치
Namespace와 Service, Deployment 등 확인 시 위와 같이 생성된 것을 확인할 수 있다.
namespace가 olm으로 되어있는 게 매우 많은 것을 확인할 수 있다. (캡쳐뜨기에 힘들어서 일부만 캡쳐했다.)
Cloud Native Operator 설치
cnpg(Cloud Native PostgreSQL)가 배포된 것을 확인할 수 있다.
또한 cnpg operator가 설치된 것을 확인할 수 있다.
PG Cluster 배포
해당 파일을 apply해주고 실시간으로 pod의 status를 확인 할 수 있다.
생각보다 시간이 조금 걸리는 작업이었다.
처음에 initdb(job)이 실행되고, job이 실행되어 완료 후에 pg pod를 배포한다.
두 번째는 join으로 primary에 join하는 job이 돌게 되고 job이 완료되면 두 번째 pod를 배포한다.
세 번째도 join으로 job이 돌고, job이 완료되면 세 번째 pod를 배포한다.
3개의 pod가 배포된 것을 확인할 수 있다.
아래 명령어를 사용해서 배포된 것들을 확인하고, pv, Cluster까지 확인한다.
PV의 경우 구성한 것이 많이 없기 때문에 사용률이 낮은 것으로 확인된다.
kubectl get pod,pvc,pv,svc,ep
kubectl df-pv
kubectl describe pod -l cnpg.io/cluster=mycluster # TCP 9187 메트릭 제공
kubectl get pod -l cnpg.io/cluster=mycluster -owide
# curl -s <파드IP>:9187/metrics 통해 확인
kubectl get cluster
kubectl describe pod -l cnpg.io/cluster-mycluster 명령어를 사용하면 생성된 cluster 내용을 확인 할 수 있다.
생성된 Container Port확인 시, PG에서 사용하는 5432 외 2개의 포트가 더 생성된 것으로 보인다.
9187은 프로메테우스에 메트릭을 제공하기 위해 오픈하는 포트이다. curl로 접근하면 값을 확인할 수 있다.
PostgreSQL의 정보를 보기 위해 operator에서 제공하는 cnpg 플러그인을 설치한다.
kubectl krew install cnpg
설치하고 kubectl cnpg status mycluster 명령어 사용시, cluster의 정보를 디테일하게 볼 수 있다.
3개의 Pod중 1개는 Primary, 2개는 standby로 구성된 것을 확인할 수 있다.
PostgreSQL 접속
자격이 증명된 Secret을 확인하고, Superuser(=Admin) 계정(postgres)과 비밀번호, User계정(app)과 비밀번호를 확인한다.
myclient Pod 3대를 배포한다.
배포 후에 get pods로 확인하면 총 6대의 Pod를 확인할 수 있다.
postgres pod에 접근하여 연결정보, 데이터베이스, 타임존 등을 확인해볼 수 있다.
postgres계정으로 서비스에 매칭된 address와 port로 접근하였다는 내용을 볼 수 있다.
superuser(postgres)계정으로 접근해서 데이터 베이스의 리스트를 조회하고, user(app)계정으로 접근하여 접근정보를 확인한다.
app 계정으로 서비스 접근을 할 때 password를 입력하라고 나오는데 위에서 확인했던 비밀번호를 복붙해주면 된다.
테스트를 위해 DVD Rental Sample Database 파일을 가져왔다.
dvdrental 파일을 다운로드하고, 압축해제하여 myclient1 Podt에 tar를 복사한다.
그리고 myclient1에 superuser 계정(postgres) 계정으로 mycluster-rw 서비스 접속하여 데이터 베이스를 생성하고 확인한다.
DVD rental sample database를 불러와서 테이블을 조회하면 엄청 많은 정보가 나온다. (대략 200개의 테이블 조회 됨)
각 mycluster-ro와 mycluster-r 서비스에 접속해보고, Pod IP를 변수로 지정한다.
파드별 actor 테이블을 카운드로 조회하고, 서비스도 접근 조회해보았다.
rw pod에 접근하여서 Pod의 IP를 조회하고, for문을 사용해서 10번 정도 접근해보도록 실행하였다.
ro pod에 접근해서 Pod IP를 조회하고, for문 10번 ro pod에 접근하도록 실행하였더니, stand-by 서버로만 각각 5번씩 접근했다고 보여준다.
r pod에 접근해서 Pod IP 조회하고, 동일하게 for문을 10번 돌렸더니, 처음에는 사이좋게 stand-by만 접근했다고 보여주고, 한번 더 돌렸더니 이번에는 primary도 접근했다고 보여준다.
Cloud Natice PostgreSQL 장애 Test
장애 test를 위해 사전 준비는 아래와 같이 진행하였다.
Pod IP를 변수로 지정하고, test를 위해 sql 데이터 베이스를 생성/실행하였다.
생성 후 조회하면 1개의 데이터가 확인된다.
추가로 Insert를 하여 총 2개의 데이터을 만들었다.
그리고 for문으로 데이터를 더 생성하였는데 100개를 돌리기에 너무 많은듯하여 중간에 끊어 대략 23개의 데이터가 생성된 것으로 확인하였다.
[장애 Test 1] Primary Pod(인스턴스) 1대 강제 삭제 및 동작 확인
Pod를 삭제하기 전에 Primary Pod 정보를 확인한다.
Primary Pod는 mycluster-1인 것으로 확인된다.
그리고 터미널을 3개를 더 띄워서 각각 실시간으로 모니터링을 진행한다.
첫번째 터미널은 cluster를 두번째 터미널은 Pod의 데이터 카운트를 세번째 터미널은 for문으로 데이터 베이스에 다량의 데이터를 Insert 시킨다.
네번째 터미널에서 primary pod와 pv삭제를 진행하였고, 삭제 후 pod의 배치 및 위치 비교를 해보았다.
순간적으로 insert시, error가 발생하였고, 데이터를 conut하던 터미널은 순간적으로 멈춘 것 처럼 보였다.
그리고 mycluster-1 Pod가 삭제되자 mycluster-2가 Primary로 승격된 것으로 확인된다.
아까는 없었던 mycluster-4 pod가 생성되고 있는 것을 확인할 수 있었다.
mycluster-2가 primary가 되고나서부터 정상적으로 데이터를 count하고, insert하는 것을 확인하였다.
[장애 Test 2] Primary Pod(인스턴스) 1대가 배포된 노드 1대 drain 설정 및 동작 확인
Operator의 log를 실시간으로 확인한다.
그리고 현재 Node를 확인해서 변수로 지정한 뒤에 node drain을 해준다.
drain이후에 node를 보면, primary pod가 있던 node가 drain된 것으로 보인다.
cnpg로 클러스터 상태 확인 시, status에 인스턴스가 active되기를 기다리고 있다는 것으로 보이고, mycluster-4가 primary로 승격된 것도 확인할 수 있었다.
uncordon을 해준 뒤에 cluster status를 다시 확인하니 정상적으로 Primary pod 1대, standby pod 2대를 확인할 수 있었다.
롤링 업데이트
롤링으로 업데이트를 진행하여도 DB는 문제 없이 정상적으로 잘 작동되어야 한다.
롤링 업데이트 진행 전에 터미널에 pod 상태, for문을 사용하여 데이터를 insert하여 롤링 업데이트를 진행해도 정상적으로 작동하는지 확인한다.
현재 사용중인 PostgerSQL의 버전은 15.3인 것으로 확인하였고, 15.4버전으로 롤링 업데이트를 진행하였다.
나의 Primary는 mycluster-3이고, standby pod들을 먼저 업그레이드 진행한다.
업데이트 할 때 아래와 같이 Pod들의 상태가 변하는 것을 볼 수 있다.
Pod들이 모두 Running 상태로 변경되면 업데이트가 완료되는 것으로 보였다.
그리고 cnpg status로 cluster를 확인하니 15.4 버전으로 업그레이드가 잘 된 것으로 확인되었다.
describe로 pod의 event도 확인했다.
참고
https://www.postgresql.org/about/
https://mangkyu.tistory.com/71
https://d2.naver.com/helloworld/227936
https://blog.ex-em.com/1653
'CloudNetaStudy > [Study] DOIK' 카테고리의 다른 글
[5주차] Kafka & Strimzi Operator (0) | 2023.11.17 |
---|---|
[4주차] Percona Operator for MongoDB (1) | 2023.11.06 |
[2주차] K8S Operator & Inno DB (0) | 2023.10.26 |
[2주차] 실습 환경 세팅하기 (0) | 2023.10.25 |
[1주차] Kubernetes 기초 (0) | 2023.10.17 |