PostgreSQL
성능튜닝

PostgreSQL을 제대로 이해하게 만드는 명글 6선

중년개발자
중년개발자

@loxo

약 9시간 전

3

PostgreSQL을 제대로 이해하게 만드는 명문 6선

여러 자료를 살펴본 결과, 단순한 기능 나열보다 “PostgreSQL은 왜 이렇게 동작하는가”를 알려주는 글들이 오래 남습니다.

딱 하나만 읽는다면 PostgreSQL Wiki의 〈Don’t Do This〉, 성능까지 공부한다면 **〈Use The Index, Luke〉**를 가장 먼저 추천합니다.

1. PostgreSQL 개발자의 실전 금기사항

Don’t Do This — PostgreSQL Wiki

PostgreSQL을 사용하면서 습관적으로 내리는 잘못된 선택을 모은 글입니다. 짧지만 실무 영향력은 가장 큽니다.

핵심 내용은 다음과 같습니다.

  • 문자열은 무조건 varchar(100)으로 만들 필요가 없다.
  • text, varchar, varchar(n)은 같은 문자열을 저장할 때 공간과 성능 차이가 사실상 없다.
  • 길이 제한이 비즈니스 규칙이라면 CHECK 제약조건이 의도를 더 명확히 표현한다.
  • 새로운 테이블에서는 serial보다 표준 SQL인 IDENTITY를 권장한다.
  • 실제 순간을 기록할 때는 timestamp보다 timestamptz가 안전하다.
  • NOT IN은 목록이나 서브쿼리에 NULL이 하나만 있어도 예상 밖의 결과를 낼 수 있다.
  • 대문자가 포함된 테이블·컬럼명은 항상 큰따옴표를 요구하므로 snake_case가 편하다.
  • 시간 검색에 BETWEEN을 쓰면 종료 시각까지 포함되므로 경계값 오류가 생기기 쉽다.

예를 들어 날짜 범위는 다음처럼 반개방 구간으로 표현하는 편이 안전합니다.

sql
WHERE created_at >= TIMESTAMPTZ '2026-07-01 00:00:00+09' AND created_at < TIMESTAMPTZ '2026-08-01 00:00:00+09'

새 테이블의 기본키도 다음 형태가 좋습니다.

sql
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY

이 글의 가장 중요한 가르침은 이것입니다.

데이터 타입은 저장 공간의 크기를 정하는 장식이 아니라, 데이터가 무엇인지를 선언하는 약속이다.


2. 인덱스를 가장 쉽게, 그러나 깊게 설명한 고전

Use The Index, Luke — Markus Winand

PostgreSQL을 포함한 여러 관계형 데이터베이스의 인덱스와 실행계획을 설명하는 무료 웹북입니다. PostgreSQL 9.0부터 17까지 실제 테스트를 바탕으로 갱신되어 있습니다.

이 글에서 얻어야 할 핵심은 **“인덱스가 있는데 왜 느린가?”**입니다.

인덱스는 전화번호부와 비슷합니다. 이름순 전화번호부에서 성과 이름은 빠르게 찾을 수 있지만, 전화번호 끝 네 자리로 사람을 찾으려면 거의 전부 훑어야 합니다. 복합 인덱스도 마찬가지입니다.

sql
CREATE INDEX idx_orders_customer_date ON orders (customer_id, ordered_at);

다음 조건에는 매우 잘 맞습니다.

sql
WHERE customer_id = ? AND ordered_at >= ?

하지만 이것만 검색하면 전통적으로 효율이 떨어질 수 있습니다.

sql
WHERE ordered_at >= ?

PostgreSQL 18부터는 복합 B-tree의 앞쪽 조건이 빠진 경우에도 skip scan을 이용할 가능성이 생겼지만, 이것이 올바른 인덱스 설계를 대신하지는 않습니다. 실행계획과 데이터 분포를 확인해야 합니다. PostgreSQL 18 공식 발표

실무 원칙은 간단합니다.

자주 사용하는 WHERE, JOIN, ORDER BY를 먼저 보고 인덱스를 설계하라. 테이블 정의만 보고 인덱스를 만들지 마라.

그리고 인덱스를 추가한 뒤에는 반드시 확인해야 합니다.

sql
EXPLAIN (ANALYZE, BUFFERS) SELECT ...

ANALYZE는 실제로 쿼리를 실행하므로 운영 환경의 UPDATE, DELETE에는 주의해야 합니다.


3. 오래 열린 트랜잭션 하나가 DB를 망가뜨리는 과정

Postgres Job Queues & Failure By MVCC — Brandur Leach

PostgreSQL 테이블을 작업 큐로 사용했을 때, 처음에는 잘 작동하다가 시간이 지날수록 성능이 무너지는 과정을 설명한 명문입니다.

PostgreSQL에서 UPDATE는 기존 행을 그 자리에서 덮어쓰지 않습니다. 새 버전의 행을 만들고, 이전 버전은 다른 트랜잭션에서 더 이상 필요하지 않을 때 VACUUM이 정리합니다.

문제는 오래된 트랜잭션 하나가 계속 열려 있을 때입니다.

이 글의 통찰은 강렬합니다.

PostgreSQL에서 성능 문제는 느린 쿼리보다 끝나지 않은 트랜잭션에서 시작되기도 한다.

운영 환경에서는 다음 설정을 검토할 가치가 있습니다.

sql
ALTER ROLE doncp_app SET idle_in_transaction_session_timeout = '60s'; ALTER ROLE doncp_app SET statement_timeout = '30s'; ALTER ROLE doncp_app SET lock_timeout = '3s';

다만 배치, 대용량 적재, pg_dump 같은 작업용 계정에는 더 긴 제한이 필요할 수 있으므로 역할별로 나누는 것이 좋습니다.


4. 인덱스 내부로 직접 잠수하는 글

Discovering the Computer Science Behind Postgres Indexes — Pat Shaughnessy

2014년에 작성됐지만 여전히 가치가 높은 글입니다. PostgreSQL C 소스까지 내려가 B-tree 인덱스가 실제로 어떻게 저장되고 검색되는지 보여줍니다.

핵심 흐름은 다음과 같습니다.

  1. B-tree의 상위 노드에서 찾을 범위를 좁힌다.
  2. 자식 페이지로 내려간다.
  3. 한 페이지 안에서는 이진 탐색으로 값을 찾는다.
  4. 인덱스가 가리키는 heap tuple을 읽는다.

이 구조를 이해하면 다음 현상들이 자연스럽게 연결됩니다.

  • 인덱스가 있어도 너무 많은 행을 읽으면 순차 스캔이 더 빠를 수 있다.
  • 일반 인덱스 스캔은 인덱스와 테이블을 모두 읽을 수 있다.
  • INCLUDE를 사용한 covering index가 index-only scan에 도움이 될 수 있다.
  • 테이블의 visibility map 상태에 따라 index-only scan이어도 heap 접근이 발생할 수 있다.
  • 인덱스를 많이 만들수록 INSERTUPDATE 비용이 커진다.

결국 인덱스는 공짜 지름길이 아닙니다.

읽기 성능을 얻는 대신, 쓰기 비용과 저장 공간, VACUUM 부담을 지불하는 구조물이다.


5. Oracle 개발자라면 특히 좋은 최신 명문

Comparison of the Transaction Systems of Oracle and PostgreSQL — Laurenz Albe

PostgreSQL 코어 기여자인 Laurenz Albe가 2025년에 작성한 글입니다. 은행권처럼 Oracle 경험이 많은 환경에서 PostgreSQL로 넘어갈 때 특히 유용합니다.

중요한 차이는 다음과 같습니다.

항목OraclePostgreSQL
SQL 오류 발생해당 문장만 실패 가능트랜잭션 전체가 실패 상태로 전환
오류 후 계속 처리가능ROLLBACK 또는 SAVEPOINT 필요
DDL 트랜잭션DDL이 암묵적 커밋을 유발대부분의 DDL 롤백 가능
과거 행 버전주로 UNDO 영역테이블 내부에 여러 행 버전 유지
긴 트랜잭션 영향잠금·UNDO 부담잠금뿐 아니라 VACUUM까지 방해
기본 격리 수준Read CommittedRead Committed

PostgreSQL에서는 트랜잭션 중 한 SQL이 실패하면 이후 SQL도 계속 실패합니다.

sql
BEGIN; INSERT INTO account(id) VALUES (1); INSERT INTO account(id) VALUES (1); -- 오류 SELECT * FROM account; -- current transaction is aborted ROLLBACK;

대량 배치에서 일부 오류만 건너뛰려면 SAVEPOINT를 사용할 수 있지만, 지나치게 많은 서브트랜잭션은 성능 부담이 됩니다. 애플리케이션 단위의 작은 배치로 나누는 편이 대체로 낫습니다.


6. 지금 가장 최신의 미래 예고편

PostgreSQL 19 Beta 1 Feature Highlights

2026년 7월 28일 현재 안정 버전 계열은 PostgreSQL 18이며, PostgreSQL 19는 Beta 2 단계입니다. 운영 DB에는 아직 사용하면 안 됩니다. PostgreSQL 19 Beta 2 발표

PostgreSQL 19에서 눈여겨볼 변화는 상당히 큽니다.

  • SQL 표준 기반 property graph 질의인 SQL/PGQ
  • autovacuum의 병렬 작업 및 우선순위 개선
  • 테이블을 재구성하는 REPACKREPACK CONCURRENTLY
  • 외래키 검사 시 INSERT 성능 향상
  • 실행계획을 안정화하는 pg_plan_advice
  • 파티션을 직접 합치거나 나누는 MERGE PARTITIONS, SPLIT PARTITIONS
  • 복제본이 특정 WAL 위치까지 따라올 때까지 기다리는 WAIT FOR LSN
  • 논리 복제에서 sequence 값 복제
  • 복구 상황을 보여주는 pg_stat_recovery
  • 잠금 유형별 통계를 제공하는 pg_stat_lock
  • 기본 TOAST 압축 방식이 lz4로 변경
  • JIT 기본 비활성화
  • 온라인 데이터 체크섬 활성화·비활성화

개발자 입장에서 특히 흥미로운 것은 SQL/PGQ입니다. 그래프 데이터베이스를 별도로 두지 않고도 기존 테이블과 SQL 위에서 노드·간선 관계를 탐색할 수 있는 방향이기 때문입니다.

다만 PostgreSQL 19는 아직 베타이므로 기능 학습과 호환성 테스트용으로만 보는 것이 맞습니다.


이 글들을 하나의 문장으로 요약하면

PostgreSQL을 잘 다룬다는 것은 SQL 문법을 많이 아는 것이 아닙니다.

인덱스는 읽기와 쓰기의 거래이고, MVCC는 동시성과 청소의 거래이며, 트랜잭션은 편리함과 시간의 거래라는 사실을 이해하는 것이다.

제가 추천하는 읽기 순서는 다음과 같습니다.

  1. Don’t Do This — 잘못된 테이블 설계부터 제거
  2. Use The Index, Luke — 인덱스와 실행계획 이해
  3. Postgres Job Queues & Failure By MVCC — 트랜잭션과 VACUUM 이해
  4. Oracle vs PostgreSQL Transactions — 실무 운영 차이 이해
  5. Postgres Index Internals — 내부 구조까지 깊게 학습
  6. PostgreSQL 19 Feature Highlights — 앞으로의 방향 파악

이 여섯 편만 제대로 읽어도 PostgreSQL을 단순히 “SQL이 되는 DB”가 아니라, 오래 운영할수록 내부 원리를 이해해야 하는 살아 있는 시스템으로 보게 될 겁니다.

#PostgreSQL#Database#SQL#Database Performance#Backend

댓글 0

Ctrl + Enter를 눌러 등록할 수 있습니다
※ AI 다듬기는 내용을 정제하는 보조 기능이며, 최종 내용은 사용자가 확인해야 합니다.