TECH 으로 돌아가기
TECH HACKER NEWS 2026.11.05 03:42 8분 읽기 32 READS

PostgreSQL이 왜 그렇게 동작하는지 궁금했다면: 무료 온라인 책 The Internals of PostgreSQL 함께 읽기

PostgreSQL을 쓰다 보면 한 번쯤 이런 일을 겪어요. 분명 데이터를 잔뜩 지웠는데 디스크 용량은 그대로이고, 어제까지 멀쩡하던 쿼리가 갑자기 느려지고, 로그에는 autovacuum이 어쩌고 하는 메시지가 찍혀 있는 거죠. 검색해서 설정값 몇 개 바꾸면 일단 넘어가긴 하는데, 왜 그런 일이 벌어졌는지는 끝내 모른 채로 남아요.

The Internals of PostgreSQL은 바로 그 “왜”를 풀어주는 무료 온라인 책이에요. 스즈키 히로노부(Hironobu Suzuki)가 쓴 자료로, 데이터베이스 관리자와 시스템 개발자를 대상으로 PostgreSQL 내부 구조를 그림과 함께 설명해요. 공식 문서가 “무엇을 설정할 수 있는가”를 알려준다면, 이 책은 “설정을 바꾸면 안에서 무슨 일이 벌어지는가”를 보여준다고 생각하면 돼요.

책이 다루는 범위

구성은 아래에서 위로 쌓아 올리는 방식이에요. 데이터가 디스크에 어떻게 놓이는지에서 출발해서 프로세스와 메모리 구조, 쿼리 처리, 외부 데이터 래퍼(FDW)와 병렬 쿼리, 동시성 제어, VACUUM, HOT와 인덱스 전용 스캔, 버퍼 매니저, WAL, 백업과 시점 복구(PITR), 스트리밍 복제까지 이어져요. 전부 읽기 부담스럽다면 실무에서 가장 자주 부딪히는 주제부터 보는 걸 추천해요.

UPDATE는 사실 “고쳐 쓰기”가 아니에요

이 책에서 가장 눈이 번쩍 뜨이는 부분이 MVCC(다중 버전 동시성 제어)예요. 이게 뭐냐면, 한 행의 여러 버전을 동시에 들고 있으면서 트랜잭션마다 자기가 봐야 할 버전을 보여주는 방식이에요.

장부에 비유해볼게요. 잘못 적은 줄을 지우개로 지우는 게 아니라, 기존 줄에 “몇 번 거래에서 폐기됨”이라고 표시하고 아래에 새 줄을 쓰는 거예요. PostgreSQL의 각 행 버전에는 자기를 만든 트랜잭션 번호(xmin)와 폐기한 트랜잭션 번호(xmax)가 붙어 있어요. 덕분에 읽는 쪽과 쓰는 쪽이 서로를 기다리지 않아도 되는 거죠.

직접 확인하는 것도 어렵지 않아요. 테스트 테이블에서 SELECT ctid, xmin, xmax, * FROM users;를 실행하고, 한 행을 UPDATE한 뒤 다시 조회해보세요. ctid는 행의 물리적 위치(페이지 번호와 페이지 내 순번)인데, 값이 바뀌는 게 보일 거예요. 같은 자리를 고친 게 아니라 새 버전이 생겼다는 증거거든요.

VACUUM이 필요한 이유

그럼 폐기된 옛 버전은 어떻게 될까요? 아무도 안 치우면 계속 쌓이겠죠. 이걸 치우는 청소부가 VACUUM이에요. 여기서 많이 오해하는 포인트가 하나 있는데요, 일반 VACUUM은 빈 공간을 재사용할 수 있게 표시할 뿐, 대부분의 경우 운영체제에 디스크를 돌려주지 않아요. 디스크를 실제로 줄이려면 테이블을 통째로 다시 쓰는 VACUUM FULL이 필요한데, 이건 실행 내내 테이블에 강한 잠금을 걸어서 운영 중에는 조심해야 해요. 처음에 말한 “지웠는데 용량이 그대로”의 정체가 바로 이거예요.

오래 열려 있는 트랜잭션이 위험한 이유도 여기서 나와요. 어떤 트랜잭션이 옛 버전을 볼 가능성이 남아 있으면 VACUUM은 그걸 치울 수 없거든요. 커밋을 잊은 세션 하나가 테이블 전체를 부풀릴 수 있다는 얘기예요. 트랜잭션 번호가 32비트라서 주기적으로 오래된 행을 “동결(freeze)”해야 한다는 내용도 이 부분에서 함께 다뤄요.

WAL, 먼저 적고 나중에 반영하기

WAL(Write-Ahead Logging)은 데이터 파일을 고치기 전에 변경 내역을 로그에 먼저 남기는 방식이에요. 서버가 갑자기 꺼져도 마지막 체크포인트 이후의 로그를 다시 재생하면 복구되죠. 재미있는 건 이 로그 하나가 크래시 복구, 시점 복구, 스트리밍 복제의 공통 기반이라는 점이에요. 세 기능을 따로 외우지 않고 “WAL을 언제, 어디서 재생하느냐의 차이”로 묶어서 이해할 수 있게 돼요.

다른 데이터베이스와 비교해보면

MySQL의 InnoDB는 행을 제자리에서 고치고 옛 버전을 undo 로그로 보내는 방식이에요. PostgreSQL은 옛 버전을 테이블 안에 남기기 때문에 VACUUM 부담이 생기고, 인덱스 갱신이 늘어나는 쓰기 증폭 문제도 따라와요. 2016년 Uber가 PostgreSQL에서 MySQL로 옮기며 공개한 글의 핵심 이유 중 하나도 이 부분이었어요. 책에 나오는 HOT는 이 부담을 줄이는 장치로, 인덱스 걸린 컬럼이 안 바뀌고 같은 페이지에 공간이 있으면 인덱스를 건드리지 않아요. OrioleDB처럼 undo 기반 저장 엔진을 PostgreSQL에 붙이려는 시도가 나오는 것도 같은 맥락이에요.

연결 구조도 달라요. PostgreSQL은 연결마다 프로세스를 하나씩 띄우기 때문에, 연결이 많은 서비스에서 PgBouncer 같은 커넥션 풀러가 사실상 필수가 되는 이유를 이 구조에서 이해할 수 있어요.

한국 개발자라면 이렇게 활용해보세요

백엔드 개발자라면 5~7장(동시성 제어, VACUUM, HOT)을 먼저 읽는 걸 추천해요. 읽고 나서 운영 DB의 pg_stat_user_tables에서 n_dead_tup을 확인하고, pg_stat_activity에서 idle in transaction 상태로 오래 머무는 세션이 있는지 찾아보세요. 업데이트가 잦은 테이블이라면 fillfactor를 낮춰서 HOT가 동작할 여지를 주는 튜닝도 이해하고 쓸 수 있게 돼요. 느린 쿼리를 잡을 일이 많다면 3장을 읽고 EXPLAIN (ANALYZE, BUFFERS) 결과를 다시 보세요. 숫자가 전혀 다르게 읽힐 거예요.

면접 준비용으로도 좋아요. “트랜잭션 격리 수준을 설명해보세요”라는 질문에 내부 동작까지 답할 수 있으면 확실히 달라 보이거든요.

마무리

한 줄로 정리하면, 이 책은 PostgreSQL의 이상한 증상들이 사실 몇 가지 설계 선택의 결과라는 걸 보여주는 지도예요.

여러분은 PostgreSQL을 운영하면서 “왜 이러지?” 싶었던 순간이 언제였나요? 그때 원인을 결국 찾아내셨는지 궁금해요.


🔗 출처: Hacker News

SOURCE · HACKER NEWS
원문 전체 보기 → https://www.interdb.jp/pg/index.html
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...