PostgreSQL은 트랜잭션 및 분석 워크로드에 사용되는 오픈 소스 객체 DBMS 시스템이다.
1986년 UC 버클리의 POSTGRES 프로젝트의 일부로 시작된 이래 35년 이상 끊임없이 개발되었다.
Client - Server Architecture
다른 솔루션과 마찬가지로 PostgreSQL 아키텍처는 클라이언트-서버 모델을 따른다. 주요 프로그램은 데이터 구조 정의, 데이터 저장 및 쿼리 처리를 담당하는 서비스로 작동한다.
이 아키텍처를 통해 PostgreSQL 시스템은 로컬 또는 네트워크를 통해 연결된 여러 클라이언트를 처리할 수 있다. 마스터 프로세스가 클라이언트 연결을 수신하면 해당 연결에 전담된 새 프로세스를 포크(즉, CPU와 RAM을 소모하고 자체적으로 실행하는 독립 프로세스 생성)한다. 여러 클라이언트가 연결되면 각 클라이언트는 포크된 프로세스를 받는다.
각 포크된 프로세스는 CPU 코어와 RAM을 소모하므로, 동시에 서버에 연결하는 클라이언트 수는 사용 가능한 코어와 RAM에 의해 제한된다. 서버 리소스가 고갈된 후의 모든 새로운 클라이언트 요청은 거부된다. 이러한 경우 클라이언트는 다시 시도해야 한다.
연결 풀링이라는 메커니즘이 이 문제를 해결한다. 연결 풀러는 연결 풀러 시작 중에 서버에 여러 연결을 시작하고 요청이 들어오면 클라이언트에 제공한다. 사용 가능한 연결이 모두 제공되면 새 요청은 대기열에 추가되고 사용 가능해지면 즉시 제공된다. 연결 풀러는 연결 문제를 해결할 뿐만 아니라 각 새 클라이언트 요청에 대한 연결 생성 시간을 피함으로써 데이터베이스의 성능을 개선한다.
postgresql 은 클라이언트와 서버 프로세스가 일반적으로 서로 다른 호스트에 있는 클라이언트 서버 모델을 사용한다.
클라이언트와 서버 간의 통신은 일반적으로 TCP IP 프로토콜이나 Linux socket을 통해 수행된다. frontend 에는 Java program, cli, gui 프로그램 과 같은 어플리케이션으로 SQL Request를 데이터베이스 서버로 보내고 서버는 psql을 프론트엔드 클라이언트로 제공한다.
서버 프로세스는 데이터베이스 파일을 관리하고 클라이언트 어플리케이션의 연결을 수락하고 클라이언트를 대신하여 작업을 수행한다. 데몬프로세스라고 하는 서버의 기본 프로세스에는 많은 책임이있다.
예를 들어 Shared Memory를 초기화하고 AutoVacuum과 같은 Utility Process를 생성한다.
또한 데몬 프로세스에서는 Connection Request를 수신한 다음, 각 클라이언트에 대해 새 서버 프로세스를 생성한다.
데몬 자체는 사용자가 데이터베이스에 연결하기 위해 항상 실행되어야 하는 필수 프로세스이다.
Postgresql Process Type
postgresql의 프로세스 타입은 크게 3가지로 나뉜다.
- PostgreSQL Server Process
- Backend Process
- Background Worker Process
PostgreSQL 서버 프로세스(이전에는 Postmaster라고 함)
이 프로세스는 클라이언트 연결을 관리하고 새로운 백엔드 프로세스를 시작하는 주요 감독자이다. 들어오는 클라이언트 연결 요청을 수신하고 클라이언트와 데이터베이스 간의 원활한 통신을 조율한다.
백엔드 프로세스
각 클라이언트 연결에 대해 백엔드 프로세스가 시작된다. 클라이언트를 대신하여 쿼리를 실행하고 데이터베이스 트랜잭션을 처리한다. 백엔드 프로세스는 클라이언트와 직접 통신하여 효율적인 데이터베이스 작업을 보장한다.
백그라운드 워커 프로세스
PostgreSQL 서버 및 백엔드 프로세스와 달리 백그라운드 워커 프로세스에는 각각 특정 작업을 수행하는 여러 하위 유형의 프로세스가 있다. 이러한 프로세스는 데이터베이스 유지 관리 및 시스템 전체 관리를 수행하는 필수적인 백그라운드 작업을 수행하며 특정 사용자 연결과 관련이 없다. 백그라운드 프로세스는 백그라운드에서 작동하여 데이터베이스 상태를 유지하고 성능을 최적화하는 데 도움이 된다.
Postgresql Connection Request Workflow
client로 부터 connection request가 오면 postgresql은 내부적으로 다음과 같이 동작한다.
1. postgresql master 프로세스는 listener 역할을 하며 client로 부터 연결을 listen한다.
2. postgresql master 프로세스가 request를 receive하면 새로운 백엔드 프로세스(Child Process)가 분기된다.
3. 인증이 성공하면 백엔드 프로세스가 클라이언트의 요청 처리를 시작한다.
4. 최대 연결 설정에 도달하지 않는 한 모든 연결 요청에 대해 프로세스가 반복된다.
5. 이때 각각의 프로세스에는 work_mem 같은 Local memory가 개별 할당된다.
6. postgres 백엔드 프로세스가 client와 연결되면 postgresql master 프로세스는 client와의 연결을 해제하며 client 는 postgres 백엔드 프로세스에 query 를 전달한다.
7. postgres 백엔드 프로세스는 전달받은 query를 수행한 후 client에 결과를 반환한다.
Postgresql Query Request Workflow
postgres (backend) process는 데이터를 읽고 쓸 때 database buffer 또는 ram을 사용한다. 사용할 메모리 사이즈는 postgresql.conf의 shared_buffers 파라미터에 의해 결정되며 서버가 시작될 때 할당된다.
사용자 요청은 크게 SELECT 와 DML (INSERT, UPDATE, DELETE) 로 분류할 수 있다.
- 1. Shared Buffers에서 데이터 확인
- PostgreSQL 백엔드 프로세스는 사용자가 요청한 데이터 블록이 이미 메모리에 로드되어 있는지 확인한다. 이때 Shared Buffers라는 메모리 영역을 사용하며, 이는 postgresql.conf 파일의 shared_buffers 파라미터에 의해 크기가 결정된다. 서버가 시작될 때 할당된다.
- Shared Buffers는 PostgreSQL이 데이터 블록을 캐시하는 주 메모리 영역이다. 이곳에서 데이터를 찾으면 더 이상 디스크 접근 없이 바로 데이터를 반환한다.
- 2. OS 캐시 확인
- Shared Buffers에 요청한 데이터 블록이 없으면, PostgreSQL은 운영 체제(OS)에게 데이터 블록을 요청한다.
- OS 캐시(파일 시스템 캐시)에 해당 블록이 존재하면, OS는 이 데이터를 PostgreSQL의 Shared Buffers로 전달하여 디스크 I/O 없이 빠르게 데이터를 처리할 수 있다.
- 3. 디스크에서 데이터 로드
- Shared Buffers와 OS 캐시 어디에도 데이터가 없으면, PostgreSQL은 디스크에서 데이터를 가져와야 한다. 디스크에서 데이터 파일을 읽어 Shared Buffers로 로드하고, 이후 요청을 처리한다.
- 이 단계에서 물리적 I/O가 발생하며, 이는 성능 저하를 유발할 수 있기 때문에 가능한 캐시에서 데이터를 찾는 것이 중요하다.
- 4. 데이터 변경 작업 (DML)
- 변경 작업(INSERT, UPDATE, DELETE)의 경우, 변경 사항은 Shared Buffers 내에서 먼저 이루어진다. 즉, 디스크에 바로 기록되는 것이 아니라, 메모리 내에서 변경된 상태로 유지된다.
- 변경된 데이터는 Dirty Data로 불리며, 일정 조건이 충족되었을 때(예: CHECKPOINT 발생 시), 이 변경된 데이터가 디스크의 데이터 파일로 기록되고, WAL(Write-Ahead Logging) 파일에도 기록되어 데이터의 무결성과 복구 가능성을 보장한다.
이 과정에서 PostgreSQL은 성능 최적화를 위해 메모리 캐시를 적극 활용하며, 디스크 접근을 최소화하려고 한다. Shared Buffers와 OS 캐시의 적절한 활용이 쿼리 성능에 중요한 역할을 한다.
Transaction 처리 중 Dirty Data의 처리와 저장소로의 동기화 과정
PostgreSQL에서 데이터 변경 작업은 Shared Buffer에서 이루어지며, 이때 변경된 데이터(Dirty Block)는 일시적으로 메모리에 남아 있다. 이러한 데이터는 나중에 반드시 디스크에 기록되어야만 데이터의 영속성과 안정성이 보장된다. 이 작업을 수행하는 것이 BG Writer와 Checkpointer 프로세스이다.
BG Writer (Background Writer)
BG Writer는 정기적으로 일정량의 Dirty Block을 디스크로 기록한다. 이 프로세스는 지속적으로 실행되며, PostgreSQL이 메모리와 디스크 간의 데이터 흐름을 원활하게 유지하는 데 중요한 역할을 한다.
BG Writer는 메모리 내에 남아 있는 변경된 데이터가 일정 시간이 경과하면 이를 디스크로 내려쓰는 역할을 한다. 이로 인해 메모리 내에 변경된 데이터가 계속 쌓이지 않고, 데이터 쓰기 성능을 최적화할 수 있다.
일반적으로 BG Writer는 지속적으로 작동하여 다양한 시점에서 디스크 쓰기 작업을 수행하므로, 대규모 디스크 I/O 작업이 한 번에 몰리지 않게끔 한다.
Checkpointer
Checkpointer는 주기적으로 Checkpoint를 발생시켜 PostgreSQL의 전체 데이터 변경 사항을 디스크에 기록하는 역할을 한다. 모든 Dirty Block을 디스크로 내려쓰는 작업을 수행하며, 이는 데이터의 무결성을 보장한다.
Checkpoint는 데이터베이스 복구 시점에 중요한 역할을 한다. 특정 시점까지의 변경 사항이 디스크에 안전하게 저장되면, 이후 복구 시 그 시점부터 로그를 기반으로 복구를 시작할 수 있다.
PostgreSQL에서 Checkpointer는 변경된 데이터를 직접 디스크에 기록하는 역할도 하며, 이는 Oracle의 CKPT와 차이점이다. Oracle에서 CKPT는 단지 DBWn 프로세스에게 Dirty Block을 디스크에 기록하라고 지시하는 반면, PostgreSQL의 Checkpointer는 Dirty Block을 직접 디스크로 내려쓴다.
Transaction Log 관리
- Checkpointer: 데이터베이스 상태를 체크포인트 시점으로 저장하고, 시스템 오류 시 복구할 수 있는 지점을 기록한다.
- WAL Writer: WAL Buffer를 디스크로 플러시하며, 트랜잭션 로그를 기록하여 복구에 사용된다.
- Archiver: WAL 파일을 보관하여, 백업과 특정 시점 복구(PITR)에 사용할 수 있도록 한다.
Checkpointer 프로세스
- Dirty Block 쓰기: Checkpointer 프로세스는 주기적으로 메모리(Shared Buffers)에 있는 Dirty Block(변경된 데이터)을 디스크의 데이터 파일로 내려쓴다. 이 과정은 데이터 무결성을 보장한다.
- 체크포인트 기록: Checkpointer는 특정 시점에 대한 체크포인트 레코드를 작성하고, 이를 $PGDATA/global/pg_control 파일에 업데이트한다. 이 파일은 마지막 체크포인트의 위치를 저장한다.
- 복구 작업: 시스템이 다운되면, pg_control 파일에서 마지막 체크포인트 위치를 찾아 해당 시점부터 WAL 파일을 읽어 복구 작업을 시작한다. 복구 후, Checkpointer는 새로 복구된 상태를 기준으로 즉시 체크포인트를 수행해 그 상태를 기록한다.
WAL Writer 프로세스
- WAL Buffer 플러시: WAL Writer는 트랜잭션이 완료될 때, 또는 WAL Buffers가 가득 차면 그 내용을 디스크로 플러시한다. 이 데이터는 향후 데이터베이스 복구에 사용된다.
- WAL 파일 생성: WAL(Write-Ahead Log)은 데이터베이스 트랜잭션이 일어난 후 디스크에 저장되기 전에 먼저 WAL에 기록되며, 이를 통해 트랜잭션의 원자성을 보장한다.
Archiver 프로세스
- WAL Segment 보관: WAL Writer가 새로운 WAL Segment 파일을 작성할 때, Archiver는 해당 WAL 파일을 보관한다. 이 보관된 WAL 파일들은 주로 데이터베이스 백업과 PITR(Point-In-Time-Recovery), 즉 특정 시점으로 복구하는 데 사용된다.
References
'클라우드 컴퓨팅 > Database' 카테고리의 다른 글
Postgresql Architecture #2 Process, Memory (0) | 2024.10.27 |
---|