PostgreSql의 성능과 아키텍처에 관한 책. 9.6 버전을 기반으로 하므로 최신 버전의 정보는 하기에 별도로 다룸
추가적으로 postegreSql 의 기본적인 정보를 추가할 예정
-
아키텍처

- Shared Memory 영역
- Shared Buffer: Disk IO를 최소화 하기 위한 영역
- WAL Buffer: 데이터베이스의 변경 사항을 잠시 저장하는 버퍼
- Process 종류
- Postmaster
- 기동 시 가장 먼저 시작되는 프로세스
- 복구 작업, Shared Memory 영역 초기화, 백그라운드 프로세스 구동 작업 수행
- Background
- logger: 에러 메시지 기록
- checkpointer: 체크포인트 발생 시 더티 버퍼를 파일에 기록한다
- writer: 주기적으로 더티 버퍼를 파일에 기록
- WAL writer: WAL Buffer의 내용을 WAL 파일에 기록
- autovacuum launcher: vacuum이 필요한 시점에 worker를 포크한다
- archiver: 아카이빙 로그 모드일 때 WAL 파일을 지정한 디렉토리에 복사한다
- stats collector: DBMS 사용 통계 정보를 수집한다
- Backend: 사용자 프로세스의 쿼리 요청을 수행한 후 결과를 전송하는 역할
- Client: 사용자의 프로세스
-
Shared Buffer
- IO 전략, Ring 버퍼, Clock Sweep 알고리즘 등을 이용하여 구현된 공유 메모리
- 구성 요소
- 버퍼 파티션
- LW(Light Weight)락의 성능 향상을 위해 해시테이블을 논리적으로 나눈 구역
- 공유 리소스는 Shared Buffer의 LW(Light Weight) 락을 이용하여 메모리에 엑세스
- 단일 LW락은 성능 저하를 발생시킬 수 있어 해시 테이블을 N개의 파티션을 분해하고 각 파티션마다 LW락을 가지도록 하여 성능을 올림
- 해시 세그먼트: 해시 테이블의 충돌 문제를 완화하기 위한 세그먼트(구역)
- (세그먼트) 해시 테이블
- 해시 테이블을 논리적인 N개의 세그먼트로 나누어서 관리하는 해시 테이블
- 디렉토리: 해시 세그먼트를 이용하기 위해 N개로 나눈 각 세그먼트의 시작 위치를 가르키는 별도의 배열
- 해시 엘리먼트: 해시 테이블의 값
- 버퍼 디스크립터: 버퍼 상태 관리
- 버퍼 풀: 실제 블록 저장
- LW(latch) 락과 Spin(mutex) 락
- Spin 락
- 메모리 구조체 내 변수를 액세스 할 때 사용
- TAS(Test And Set) 방식을 인라인 어셈블리어로 구현 ( mutex 방식도 존재)
- LW 락
- 메모리 구조체를 액세스 할 때 사용
- 큐&포스팅 방식을 이용하여 획득
- Clock Sweep 알고리즘
- Shared Buffer이 가득 찰 때 디스크에 내용을 기록하기 위한 알고리즘
- 이 때 기록되는 버퍼를 Victim, 이 버퍼를 선정하는 알고리즘을 Buffer Replacement라 부른다
- NFU(Not Frequently Used) 방식의 일종이다
- IO 전략 및 Ring Buffer
- 풀 스캔시에 Shared Buffer가 훼손됨을 방지하기 위한 전략
- IO 요청을 Normal(랜덤 액세스), Bulk Read, Bulk Write, Vacuum 용으로 구분하여 사용한다
- 이 때 Normal 요청을 제외학고는 Ring Buffer 방식을 이용한다
- Ring Buffer: 일정 크기를 가진 원형 형태의 버퍼로 Shared Buffer 1/4 크기 이상의 Bulk Read가 발생할 때 사용된다
-
Query Optimizer
- 유형
- RBO(Rule Base): 몇 가지 정해진 규칙에 의해 실행 계획을 생성
- CBO(Cost Base): 통계 정보를 이용하여 가장 적은 비용의 실행 계획을 생성
- 여기서 Cost는 미리 정의된 파라미터와 통계 정보를 이용해 계산된 숫자
- 통계 정보
- Cost 계산을 위해 데이터 베이스,테이블, 컬럼을 통해 수집된 정보
- 인덱스는 자동으로 생성되며 테이블 변경 시 함께 변경된다
- NDV(number of distinct value): 컬럼 값의 유니크한 정도
- correlation: 컬럼의 정렬 상태. 1이면 완벽 정렬, -1이면 역순 정렬 상태이다
- 쿼리 파싱
- 순서: 문법 체크(parse tree 생성) → 의미 체크 ( query tree 생성) → 쿼리 재작성 → 쿼리 최적화(plan tree 생성) → 쿼리 실행
- Plan Caching: 재작성된 쿼리를 Backend 프로세스 내에 저장하여 여러번 수행되는 쿼리에 대해 최초 1회만 하드 파싱하는 최적화
- Bind Peeking: Bind Sql에서 데이터 분포도가 비대칭인 칼럼을 액세스하거나 Between 범위가 일정하지 않을 때 히스토그램을 참조하는 방법
- 테이블 액세스 방법
- Seq Scan: 테이블 풀 스캔 방식. 테이블 크기에 따라 Bulk Read 전략을 사용한다
- index Scan: 인덱스 스캔 방식. 인덱스 키 순서대로 출력된다
- Bitmap index Scan
- 인덱스와 테이블의 정렬이 불안한 경우(correlation 값이 낮은 경우) 사용되는 방식
- 테이블 블록을 블록 번호 순으로 정렬한 후에 액세스하여 Disk IO를 줄이는 방식 → 이로 인해 인덱스 순서대로 출력되지 않는다
- index Only Scan(커버링 인덱스)
- 인덱스만을 이용하여 테이블 액세스 없이 쿼리를 수행
- 테이블의 블록의 ALL_VISIBLE 비트가 1일때만 동작한다
- Tid 스캔 방식: 레코드는 CTID(블록/레코드 번호 쌍)을 이용하여 액세스하는 방식 → 가장 빠름
-
조인 방법
- NL(nested loop)
- 첫 테이블(outer)의 추출된 레코드로 두 번째 테이블(inner)을 반복 액세스하는 방식
- 두 번째 테이블의 인덱스를 사용할 수 없거나 정렬 상태가 불량한 경우, 두 번째 테이블의 쿼리 결과를 임시 테이블에 저장 후 조회하는 meterialize가 발생하여 매우 느려진다
- Hash
- Build 단계에서 첫 번째 테이블 조인 컬럼에 해시함수를 적용한다
- Probe 단계에서 조인 컬럼에 같은 해시함수를 적용하여 조인하는 방식
- 이 때 데이터가 비대칭이라면 히스토그램을 참조한다
- 해시 충돌이 적을 수록 성능이 빠르다
- 메모리가 충분하면 In-memory 방식, 아니라면 Grace 또는 Hybrid 방식으로 조인한다
- in-memory가 불가능 할 때 조인 방식
-
쿼리 재작성
-
히스토그램
-
Vacuum 작업
-
Partial Index
-
BRIN(Block Range Index)
-
Partitioning
-
Parallel 프로세싱
번외 정보
- pg_global 테이블 스페이스는 클러스터 레벨에서 관리해야 할 데이터들을 저장하는 용도의 테이블 스페이스
- 포스티그리는 공유 버퍼에 없는 데이터를 디스크에서 읽을 때, 공유 버퍼에 먼저 올릴 후에 해당 버퍼를 읽어 데이터를 전송한다
- XID 프리징을 위해 오라클에서는 Wrap 번호를 추가적으로 앞에 붙여 증가시키는 방식을 이용한다