백엔드 데이터 인프라 3편 — PostgreSQL 아키텍처 (클라이언트·서버·DB·테이블)

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 3편. PostgreSQL의 클라이언트·서버 아키텍처와 클러스터·데이터베이스·스키마·테이블 4단계 계층을 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 3편 — PostgreSQL 아키텍처 (클라이언트·서버·DB·테이블)

이 글은 백엔드 데이터 인프라 시리즈 70편 중 3편이에요. 2편 에서 PG(PostgreSQL 약칭)를 로컬에 띄웠으니, 이번 3편은 "우리가 띄운 그 PG가 어떻게 생긴 시스템인지" 큰 그림을 풀어 가요.

아키텍처가 헷갈리는 이유

처음 PG 접하면 — "서버? 데이터베이스? 클러스터? 스키마? 다 다른 거야?" 가 헷갈려요. 같은 단어 "데이터베이스" 가 운영체제 관점·SQL 관점·앱 관점에서 의미가 미묘하게 달라요.

이 글에서는 두 가지 그림으로 풀어 가요. 하나는 클라이언트·서버 그림 — 누가 누구에게 뭘 요청하나. 또 하나는 계층 그림 — 데이터가 어디에 어떻게 묶여 있나.

클라이언트·서버 그림

PG는 C/S 모델(클라이언트·서버 구조). 단순화하면:

[애플리케이션 / psql]  →  TCP/IP 5432  →  [PostgreSQL 서버]
        클라이언트                              서버 프로세스

여기서 클라이언트는 SQL 쿼리를 보내는 누구든 — Spring 백엔드·psql·DBeaver·Python·다른 데이터베이스 다 포함이에요. 서버postgres 프로세스, 5432 포트를 listen 해요. 둘 사이는 PostgreSQL Frontend/Backend Protocol(TCP 위에서 동작하는 PG 전용 통신 규약)로 오가요.

프로세스 모델 — 멀티 프로세스

MySQL이 "스레드 기반" 이라면 PG는 프로세스 기반.

postgres (postmaster 프로세스 = 부모)
├── postgres: writer process
├── postgres: wal writer process
├── postgres: autovacuum launcher
├── postgres: stats collector
├── postgres: logical replication launcher
├── postgres: backend process (client 1)    ← 클라이언트 1 접속
├── postgres: backend process (client 2)    ← 클라이언트 2 접속
└── postgres: backend process (client N)    ← 클라이언트 N 접속

클라이언트가 접속하면 postmaster(연결을 받아 자식 프로세스를 만드는 부모)가 "새 backend 프로세스" 를 fork(부모 프로세스를 복제해 자식 생성) 해요. 클라이언트와 backend는 1:1 로 묶이고, 보조 프로세스(writer·WAL·autovacuum 등)는 그 옆에서 항상 떠 있어요.

시사점: - 접속이 많으면 프로세스도 많아 (수백 ~ 수천 가능) - 프로세스 생성 비용이 "스레드보다 무거움" — 연결 풀(HikariCP·PgBouncer) 사용 거의 필수

계층 그림 — 데이터의 4단계

PG 안 데이터를 "가장 큰 단위 → 가장 작은 단위" 로 나누면 4단계.

1. Cluster      = 한 PG 서버 인스턴스 전체 (port 5432 하나)
2. Database     = 클러스터 안 격리된 데이터베이스 (psql 명령 \l 로 보이는 목록)
3. Schema       = 데이터베이스 안 네임스페이스 (psql 명령 \dn)
4. Table·Index  = 스키마 안 객체

같은 단어 "데이터베이스" 가 헷갈리는 이유 — 운영체제 관점에선 "서버 전체" 를 가리키기도 하고, SQL 관점에선 "클러스터 안 한 개" 를 가리키기도.

1단계 — Cluster

클러스터 = 한 PG 서버 인스턴스. 데이터 디렉토리(/var/lib/postgresql/data 또는 PGDATA) 한 개에 해당.

오해 주의 — "여러 서버 모은 거" 가 아님. PG에서 "클러스터" = "한 서버에서 관리하는 데이터베이스들의 묶음". (분산 시스템의 클러스터 개념과 다름.)

2단계 — Database

클러스터 안에 여러 개. 각 데이터베이스는 서로 격리 — 다른 DB의 테이블을 "SELECT * FROM other_db.users" 같이 직접 못 봄 (외부 연결 필요).

-- 클러스터 안 DB 목록
\l

-- DB 만들기
CREATE DATABASE myappdb;

-- 접속 변경
\c myappdb

기본 = postgres 라는 관리용 DB가 있어요. 새 프로젝트 = 새 DB 만들기.

3단계 — Schema

DB 안 네임스페이스. "테이블 이름 충돌 방지" + "논리적 그룹화".

-- 스키마 목록
\dn

-- 기본 = public 스키마
SELECT * FROM users;   -- public.users 와 같음

-- 별도 스키마 만들기
CREATE SCHEMA app;
CREATE TABLE app.users (...);

-- 스키마 명시
SELECT * FROM app.users;

기본 = public 스키마. 그래서 처음엔 "스키마가 있는지도 모르고" 사용 가능. 큰 시스템 = 스키마로 그룹화 (예: app·audit·reporting).

4단계 — Table·Index·View·Sequence

스키마 안에 진짜 객체들. 테이블 이 가장 많이 다루는 객체.

-- public 스키마 테이블 목록
\dt

-- 또는 다른 스키마
\dt app.*

테이블 외 — 인덱스(34~36편)·뷰(16편)·시퀀스·트리거·함수·타입 등 다양한 객체 존재.

데이터가 어디에 저장되나

클러스터 = 호스트의 한 디렉토리 (PGDATA). 그 안에:

/var/lib/postgresql/data/
├── base/                 # 데이터베이스별 디렉토리 (숫자 OID)
│   ├── 1/                 # template1
│   ├── 13520/             # postgres
│   └── 16384/             # myappdb
├── global/                # 클러스터 전체 공유 (사용자·DB 카탈로그)
├── pg_wal/                # Write-Ahead Log (트랜잭션 로그)
├── pg_stat/               # 통계
├── postgresql.conf        # 메인 설정
├── pg_hba.conf            # 인증 설정
└── ...

여기서 디렉토리 이름에 붙은 OID(Object Identifier, PG 내부 객체 식별 번호)가 곧 데이터베이스 한 개를 가리켜요. pg_wal 이 가장 중요 — 모든 변경이 먼저 여기 적힘 (Write-Ahead Logging, 데이터 파일보다 로그를 먼저 기록하는 방식). 6편 MVCC(Multi-Version Concurrency Control, 다중 버전 동시성 제어) 트랜잭션 격리와 함께 깊이 풀어요.

인증·연결 흐름

1. 클라이언트가 5432로 TCP 연결
2. postmaster 가 받음
3. pg_hba.conf 룰로 인증 검증
4. 통과 시 새 backend process fork
5. 클라이언트 ↔ backend 1:1 통신 시작
6. 클라이언트 종료 시 backend 도 종료

pg_hba.conf"누가 어디서 어떤 방법으로 접속할 수 있나" 정의. 운영 환경 보안의 첫 관문.

Shared Buffer·캐시

PG는 자체 메모리 영역(Shared Buffer, PG 전용 페이지 캐시 영역)에 데이터·인덱스 페이지 캐시. 일반적으로 호스트 메모리의 25% 정도 할당. 보조로 OS 페이지 캐시도 사용.

# postgresql.conf
shared_buffers = 2GB           # 8GB 호스트면 2GB 추천
work_mem = 16MB                 # 정렬·해시당
effective_cache_size = 6GB     # OS 캐시 포함 가용 메모리 추정치

Spring Boot 관점 — 우리가 다루는 것

자바 백엔드 입문 44편 JPA(Java Persistence API, 자바 객체-DB 매핑 표준) 에서 본 application.yml 의 jdbc URL — jdbc:postgresql://localhost:5432/postgres — 가 정확히 어디를 가리키나:

  • localhost:5432 = 클러스터 (1단계)
  • postgres = 데이터베이스 (2단계)
  • 명시 안 했지만 = public 스키마 기본 (3단계)
  • 코드에서 다루는 @Entity = 테이블 (4단계)

이 4단계가 머릿속에 박혀 있으면 — 운영 환경 트러블슈팅·복구·마이그레이션 시 매번 "내가 지금 어느 단계 다루고 있나" 명확해져요.

함정 5가지

(1) "DB"라는 단어의 모호함

"PG라는 DB" (= 클러스터·서버) vs "내 DB 마이그레이션" (= 클러스터 안 한 개 DB) 헷갈리지 말 것. 문맥에 따라 의미 다름.

(2) 다른 DB 직접 조회 불가

SELECT * FROM otherdb.public.users;   -- ❌ 같은 클러스터라도 다른 DB는 직접 조회 X

해결은 postgres_fdw(Foreign Data Wrapper, 외부 데이터 연결 확장) 를 쓰거나 별도 연결을 만들어야 해요.

(3) public 스키마의 함정

기본 스키마라 너무 자유로워 — 큰 시스템에선 "모든 테이블이 public에 박혀 충돌". 일찍 스키마로 분리하는 게 운영 단계에서 편함.

(4) 프로세스 수 폭발

연결 풀 없이 매 요청마다 새 연결을 만들면 — 수백 ~ 수천 프로세스 → 메모리·CPU 부담. HikariCP (자바 백엔드 입문 41편 JDBC(Java Database Connectivity, 자바 DB 연결 API) 참고) + PgBouncer (앞단 풀) 가 운영 표준.

(5) pg_hba.conf 무시

기본값으로 두면 — 외부 접속 막혀 있거나, "모든 호스트에 trust" 같이 위험. 운영 = IP 화이트리스트 + scram-sha-256(PG가 권장하는 챌린지-응답 인증 방식) 인증.

🎯 4단계 계층 외우기

Cluster → Database → Schema → Table. 같은 단어 "데이터베이스" 가 문맥에 따라 클러스터 또는 한 개 DB 가리킬 수 있어요. 머릿속에 4단계를 그려 두면 운영 단계 트러블슈팅이 한결 깔끔.

한 줄 정리 — PG = C/S 모델 + 멀티 프로세스. Cluster → Database → Schema → Table 4단계 계층. shared_buffers·pg_wal·pg_hba.conf 세 가지가 핵심 운영 영역. HikariCP·PgBouncer 가 연결 풀 표준.

시험 직전 한 번 더 — PostgreSQL 아키텍처 입문자가 매번 헷갈리는 것

  • C/S 모델 = 클라이언트가 SQL 보내고 서버가 처리
  • TCP 5432 = 기본 포트
  • 프로세스 모델 = 멀티 프로세스 (MySQL은 스레드)
  • postmaster = 부모 프로세스, backend = 자식 (클라이언트 1:1)
  • 보조 프로세스 = writer·WAL writer·autovacuum·stats collector
  • 4단계 계층 = Cluster → Database → Schema → Table
  • Cluster = 한 PG 서버 인스턴스 (PGDATA 한 개)
  • Database = 클러스터 안 격리된 DB
  • Schema = DB 안 네임스페이스 (기본 = public)
  • 다른 DB 직접 조회 X — postgres_fdw 필요
  • 데이터 저장 = /var/lib/postgresql/data (PGDATA)
  • pg_wal = Write-Ahead Log (트랜잭션 변경)
  • postgresql.conf = 메인 설정 (43편)
  • pg_hba.conf = 인증 설정 (보안의 첫 관문)
  • Shared Buffer = PG 자체 메모리 캐시 (호스트 메모리 25% 권장)
  • 연결 풀 = HikariCP (앱) + PgBouncer (서버 앞단)
  • public 스키마의 함정 = 큰 시스템에선 일찍 분리
  • 운영 인증 = scram-sha-256 + IP 화이트리스트
  • jdbc URL = jdbc:postgresql://host:port/dbname (3단계까지 명시)
  • Spring @Entity = 테이블 (4단계)
  • shared_buffers = 호스트 메모리 25%
  • work_mem = 정렬·해시당 메모리 (16MB ~)

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!