본문으로 건너뛰기

"릴리스" 태그로 연결된 3개 게시물개의 게시물이 있습니다.

모든 태그 보기

PG19 Beta 4 기능 철회

· 약 17분

PostgreSQL 19 Beta 4에서 신기능 네 가지가 통째로 빠졌어요. SQL/PGQ, 온라인 체크섬 전환, FOR PORTION OF, MERGE/SPLIT PARTITIONS예요. 이 블로그에서 PostgreSQL 19 신기능으로 소개했던 글 다섯 편이 이 기능들과 관련되어 있어서, 무엇이 왜 빠졌는지 1차 출처를 바탕으로 다시 정리해 봤어요.

배경: Beta 4 공지​

PostgreSQL Global Development Group은 2026-09-24 PostgreSQL 19 Beta 4 공지를 냈습니다. 공지는 이번 베타에서 19에 포함하려던 기능 몇 가지를 revert했다고 밝히고, 이유를 이렇게 적었습니다.

The PostgreSQL community strongly believes that, first and foremost, PostgreSQL must be reliable. Additionally, the community strives for a predictable release schedule.

신뢰성과 예측 가능한 릴리스 일정을 위해 일부 기능을 19에서 빼고, 이후 메이저 릴리스에서 다시 다룰 여지를 남긴다는 설명입니다. 같은 공지는 다음 단계인 release candidate를 10월 초로 예고하고, GA도 10월에 나올 수 있다고 안내합니다.

공지의 "Changes Since Beta 3" 목록 가운데 revert/remove 항목은 여섯 개입니다.

공지 항목revert 커밋커밋 날짜 (UTC)삭제 줄 수
SQL/PGQ (property graph query)2b9e1aff4d2026-09-0715,857
온라인 data checksums 활성/비활성c05d5ce1232026-09-166,673
UPDATE/DELETE ... FOR PORTION OFa9d2f728242026-09-1512,938
ALTER TABLE ... MERGE/SPLIT PARTITION(S)3e8bcc86442026-08-279,379
pg_get_role_ddl() 등 3개 함수db169985c12026-09-131,500
postmaster의 LC_COLLATE=C 강제1956251afa2026-09-0142

삭제 줄 수는 GitHub API가 각 revert 커밋에 산정한 deletions 값을 그대로 옮긴 것입니다. 문서, 회귀 테스트, 릴리스 노트 항목까지 포함한 수치라 기능 코드의 크기와 같지 않습니다.

REL_19_BETA4 태그 커밋(Stamp 19beta4)은 2026-09-21입니다. 위 여섯 커밋은 모두 그 전에 REL_19_STABLE에 반영되었습니다.

GROUP BY ALL은 이보다 앞서 빠졌습니다. Beta 3 공지(2026-08-13)의 첫 항목이 "Revert GROUP BY ALL"이고, revert 커밋은 a32733d8f1(2026-07-17)입니다. 커밋 메시지에 따르면 ORDER BY에 함께 나오는 항목의 특수 처리를 고려하지 않아, ORDER BY가 기본과 다른 equality 의미를 지정하면 잘못된 결과를 반환했습니다. 수정하려면 코드 변경이 너무 커서 v20에서 다시 시도한다고 적혀 있습니다.

철회된 기능과 사유​

사유는 revert 커밋 메시지와 커밋이 가리키는 pgsql-hackers 메일로 확인했습니다. 메일 인용은 원문 그대로 두고, 해석은 따로 적었습니다.

SQL/PGQ​

revert 커밋은 원 커밋 2f094e7ac69와 후속 수정 46건을 한 번에 되돌렸습니다. 커밋 메시지는 되돌린 커밋 목록과 논의 링크만 담고 있습니다.

그 링크는 Melanie Plageman(멜라니 플라게만)이 2026-09-02 "PGQ catalog representation and pg_dump support" 스레드에 남긴 메일입니다. RMT(Release Management Team)의 의견으로 설계 문제를 네 가지로 정리했습니다.

  • DROP TABLE ... CASCADE 같은 외부 CASCADE가 graph 메타데이터를 고아로 남기는 문제
  • label과 property의 범위를 전역으로 볼지 label 단위로 볼지 정해지지 않은 문제
  • property graph를 pg_class에 둘지에 관한 카탈로그 표현 문제
  • materialized view가 GRAPH_TABLE을 조회할 때 pg_dump에 생기는 의존 순환

결론 문장은 다음과 같습니다.

As such, unless we are misunderstanding something about the discussion, we feel it would be better to revert this in 19. That would take off the time pressure now and would make it easier to fix these things properly in 20 without having to be burdened by backwards compatibility and backpatching.

19에 넣은 채 고치면 하위 호환과 backpatch 부담이 생기므로, 19에서 빼고 20에서 제대로 고치자는 판단입니다.

온라인 data checksums 전환​

이 revert는 커밋 메시지에 사유가 직접 적혀 있습니다.

The feature to enable, or disable, data checksums in an online cluster saw a number of postcommit fixes during the beta period. Suspicions were raised about the risk of more issues surfacing after GA.

베타 기간에 사후 수정이 많았고 GA 이후에도 문제가 더 발견될 위험이 제기돼, 버그가 있을 수 있는 코드를 출시하지 않으려고 되돌렸다는 내용입니다. 원 커밋 f19c0eccae9를 포함해 30개 커밋을 전부 또는 일부 되돌렸습니다.

전부 사라지지는 않았습니다. 커밋 메시지는 유지한 네 가지를 밝힙니다. 오프라인 pg_checksums가 pg_control_init에서 초기 상태를 보존하지 못하던 버그의 수정, page 검증 실패 로그에 buffer를 0으로 채우는지 알려 주는 보조 메시지, 현재 상태에 맞게 다시 쓴 Data Checksum 문서 절, 체크섬 상태 enum입니다. 이후 4a9a6c5a69(09-16)와 6b0760a10a(09-17)가 revert 과정에서 누락한 pg_control_checkpoint 컬럼, XLOG_PAGE_MAGIC 증가, 문서 잔여분을 정리했습니다.

FOR PORTION OF​

revert 커밋은 원 커밋 8e72d914c52와 후속 22건을 되돌렸습니다. 논의 링크는 FOR PORTION OF code review 스레드입니다.

스레드에서 문제가 된 것은 READ COMMITTED에서의 동시성입니다. FOR PORTION OF는 범위의 일부만 고치고 나머지 구간을 leftover 행으로 다시 삽입하는데, 동시에 실행된 UPDATE/DELETE가 서로의 leftover를 보지 못하는 경우가 있었습니다. Andres Freund(안드레스 프로인트)는 2026-09-11 문서화된 우회책인 SELECT ... FOR UPDATE가 temporal 조건에 걸려 대상 행을 잠그지 못하는 경우가 많다고 지적했고, DELETE가 끼면 전체 키를 잠가도 lost update가 난다고 했습니다. Paul Jungwirth(폴 융비르트)는 READ COMMITTED를 허용하지 않는 대신 serialization failure를 내서 재시도하게 하자고 제안했고, Andres는 이렇게 답했습니다.

Personally I don't find that it's a convincing feature with that limitation... READ COMMITTED is extremely widely used, with one reason for that being not needing to retry, and making FPO not really work that way doesn't seem convincing to me.

Peter Eisentraut(페터 아이젠트라우트)는 09-14 "I was trying to nudge toward reverting without saying it."라고 쓴 뒤 09-15 revert했습니다.

MERGE/SPLIT PARTITIONS​

커밋 메시지의 사유는 한 문장입니다.

The feature is reverted due to multiple design issues which are too late to address in this release cycle.

원 커밋 f2e4cc4279, 4b3d173629와 후속 수정 12건을 되돌렸습니다. 논의 링크는 Zsolt Parragi(졸트 파라기)가 2026-07-23 올린 MERGE/SPLIT PARTITIONS issues/questions입니다. 옮겨진 행이 새 파티션에 대한 INSERT로만 decode돼 짝이 되는 DELETE 없이 logical replication에 전달되는 문제가 첫 항목입니다. 새 파티션이 기존 파티션의 publication 소속과 replica identity를 물려받아야 하는지도 질문으로 제기되었습니다. generated column 값이 재계산되며 바뀌는 문제도 함께 제기되었습니다. 파티션별 인덱스, 제약, 트리거, reloptions, ACL, RLS 정책 같은 메타데이터가 조용히 사라지는 문제도 같은 메일에 적혀 있습니다.

pg_get_role_ddl(), pg_get_tablespace_ddl(), pg_get_database_ddl()​

세 함수와 공통 인프라 커밋을 포함해 13건을 되돌렸습니다. 논의 링크는 Noah Misch(노아 미시)가 2026-08-27 올린 pg_get_*_ddl() needs a redesign입니다. 지적은 세 가지입니다. 마이그레이션과 백업 용도라면서 비밀번호 hash를 출력하지 않는 점, role, database, IN DATABASE ... SET의 의존 순서를 다루지 않는 점, 그리고 catalog 상태를 SQL로 되살리는 구현이 pg_dump와 별도로 하나 더 생긴다는 점입니다.

PostgreSQL should not carry two independent implementations of this logic.

postmaster의 LC_COLLATE=C 강제​

되돌린 커밋 5e6e42e(2025-07)는 postmaster에서 LC_COLLATE를 C로 고정해 collation 호출이 libc 전역 설정에 우연히 의존하지 않게 하려던 변경입니다. 원 커밋 메시지는 backend 동작 변화는 없지만 일부 확장 모듈이 영향을 받을 수 있다고 적었습니다. revert 커밋 메시지는 "Out of an abundance of caution."이 전부이고, Daniel Verite(다니엘 베리테)와 Noah Misch가 요청자로 올라 있습니다.

논의 메일에서 Noah Misch는 영향을 받는 쪽이 변화를 알아차리기 어렵다는 점을 문제로 봤습니다.

Also important to me: the disruption is silent. Applications are on their own to even figure out the change affected them.

이 항목은 SQL 문법이 아니라 확장 모듈의 libc 호출 동작에 관한 것이라, 아래 측정에서는 다루지 않았습니다.

공지 목록 밖의 revert​

REL_19_STABLE 커밋 로그에는 Beta 4 공지 목록에 없는 revert도 있습니다. 공지가 모든 변경을 나열하지는 않으니, 19를 검증 중이라면 커밋 로그도 함께 보는 편이 안전합니다.

  • CREATE SCHEMA 안에서 쓸 수 있는 객체 종류 확대와 하위 명령 재정렬 중단: 3c5d28ba64, e0fdc3f54b (09-11, 호환성 영향이 이득보다 크다는 이유)
  • default locale 기준 identifier casefolding: c58566d06f (09-11, builtin provider의 C locale과 single-byte encoding 조합에서 의도하지 않은 동작 차이)
  • RI fast-path 검사의 batching 제거: 25649d6e79 (09-10). per-row fast path는 남았고, 공지에는 "Several fixes to the performance improvement feature for the foreign key constraint checks"로만 묶여 있습니다

scary patch contest​

revert 논의와 같은 시기에 나온 글이 Robert Haas(로버트 하스)가 2026-08-25 pgsql-hackers에 올린 "scary patch contest" 메일입니다. LWN의 Joe Brockmeier(조 브록마이어)가 2026-09-10 기사로 이 스레드를 다뤘습니다. 메일 첫 문장은 다음과 같습니다.

I asked Claude to evaluate which v19 patches were the scariest based on the number and type of bugs fixed post-freeze.

기능 동결 이후 고친 버그의 수와 종류로 v19 패치를 평가하게 했고, 결과 여섯 개를 싣되 LLM의 신랄한 논평 일부는 뺐다고 적었습니다. 메일에 실린 순서대로 옮기면 이렇습니다. 마지막 열의 경과는 시기가 겹친다는 사실만 보여 주며, 이 메일 때문에 revert됐다는 뜻은 아닙니다.

순서대상메일에 적힌 사후 수정이후 경과
1RI fast-path FK 검사와 batching약 16건batching 제거
2REPACK / REPACK CONCURRENTLY28건유지, Beta 4에서 수정 다수
3온라인 data checksums약 25건revert
4UPDATE/DELETE FOR PORTION OF17건 (보안 3건)revert
5SQL/PGQ17건revert
6postgres_fdw 통계 가져오기7건이 글에서 확인하지 않음

온라인 체크섬 항목에는 "A corruption-detection feature producing false positives is exactly the wrong failure mode."라는 문장이 붙어 있습니다. 손상을 감지하려는 기능이 멀쩡한 페이지를 실패로 판단하면, 그 기능을 믿고 쓸 근거가 사라진다는 지적입니다.

Robert Haas 본인은 메일 끝에서 "I don't currently have a firm position on what we should do here."라고 해서 revert를 주장하지 않았습니다. 1~3번에 아직 찾지 못한 버그가 오랫동안 남아 있을까 걱정된다고만 적었습니다. 이 메일이 각 revert를 직접 이끌었는지는 확인하지 못했습니다. revert 커밋이 가리키는 논의 스레드가 각각 따로라서, 이 글에서는 시기가 겹친다는 사실까지만 적습니다. MERGE/SPLIT PARTITIONS와 pg_get_*_ddl()은 이 메일의 여섯 항목에 들어 있지 않습니다.

beta3와 beta4 비교 측정​

측정 환경​

  • 호스트: macOS 26.6.2, Docker Engine 28.3.3 VM (Linux aarch64, 2 CPU, 약 11.7GiB, 다른 작업과 공유)
  • 이미지: Docker Hub 공식 postgres:19beta3, postgres:19beta4 (Debian 19~beta3-1.pgdg13+1, 19~beta4-1.pgdg13+1)
  • 컨테이너별 --cpus 0.5 --memory 512m, 테이블은 행 몇 개 수준

같은 SQL 파일을 두 컨테이너의 빈 데이터베이스에서 실행했습니다. 전체 로그와 스크립트는 저장소의 docs/postgresql-19-beta4-feature-reverts-measurements.md에 남겼습니다.

문법과 함수​

구문19beta319beta4
CREATE PROPERTY GRAPH성공syntax error at or near "PROPERTY"
GRAPH_TABLE (g MATCH ...)2행 반환syntax error at or near "MATCH"
UPDATE ... FOR PORTION OFUPDATE 1, 행이 3개로 나뉨syntax error at or near "FOR"
ALTER TABLE ... SPLIT PARTITION성공syntax error at or near "SPLIT"
ALTER TABLE ... MERGE PARTITIONS성공syntax error at or near "MERGE"
pg_get_role_ddl('postgres')CREATE ROLE postgres ... 반환function does not exist
pg_get_database_ddl('postgres')2행 반환function does not exist
pg_disable_data_checksums()data_checksums가 off로 바뀜function does not exist
pg_enable_data_checksums()data_checksums가 on으로 돌아옴function does not exist
GROUP BY ALLsyntax errorsyntax error

문법 계열은 전부 파서 단계에서 막힙니다. 함수 계열은 파싱은 통과하지만 함수 해석 단계에서 "function ... does not exist"로 실패합니다. pg_get_keywords()로 보면 19beta3에 있던 property, graph, graph_table, vertex, edge, destination, portion, split, partitions 키워드가 19beta4에서는 모두 사라지고 merge만 남았습니다. merge는 PostgreSQL 15의 MERGE 문 때문에 원래 있던 키워드입니다.

체크섬 관련 내용을 덧붙이면, 두 이미지 모두 initdb 기본값으로 data_checksums = on이었습니다. PostgreSQL 18부터 initdb가 checksum을 기본으로 켜기 때문입니다. 19beta4에서 이미 켜진 checksum을 운영 중에 끄는 방법은 없고, 오프라인 pg_checksums를 사용해야 합니다. 19beta3 측정에서는 DB에 행이 몇 개뿐이라 3초, 5초 대기 안에 off/on이 끝났습니다. 실제 크기에서 걸리는 시간은 재지 않았습니다.

pg_get_tablespace_ddl('pg_default')는 19beta3에서도 tablespace name "pg_default" is reserved 오류였습니다. 시스템 tablespace는 대상이 아니었던 것으로 보이며, 사용자 tablespace로는 확인하지 않았습니다.

beta3 데이터 디렉터리와 dump/restore​

베타 사이에는 원래 catalog 호환이 보장되지 않습니다. 이번에도 pg_controldata 값이 달랐습니다.

항목19beta319beta4
pg_control version number19021905
Catalog version number202607272202609165
Latest checkpoint's data_checksum_version 줄있음없음

19beta3로 initdb한 볼륨을 19beta4 이미지로 실행하면 바로 멈춥니다.

FATAL: database files are incompatible with server
DETAIL: The database cluster was initialized with PG_CONTROL_VERSION 1902, but the server was compiled with PG_CONTROL_VERSION 1905.
HINT: It looks like you need to initdb.

그래서 dump/restore를 두 방향으로 확인했습니다. 19beta3 DB에는 property graph g, FOR PORTION OF로 3행이 된 price, MERGE 결과인 파티션 sales_all2를 만들어 두었습니다.

첫째, 19beta3의 pg_dump로 뜬 파일을 19beta4에 넣었습니다. CREATE PROPERTY GRAPH public.g와 ALTER PROPERTY GRAPH public.g OWNER TO postgres 두 문장만 syntax error로 실패하고 나머지는 복원됐습니다. psql -f 기본값은 오류가 나도 계속 진행하므로, 로그를 보지 않으면 graph가 빠진 줄 모르고 넘어갈 수 있습니다.

둘째, 19beta4의 pg_dump로 19beta3 서버를 직접 dump했습니다. 종료 코드는 0이었고, stderr까지 함께 저장한 출력 파일에 PROPERTY GRAPH라는 문자열이 한 줄도 없었습니다. 복원한 DB에는 person, knows, price, sales, sales_all2만 있습니다. 새 버전 pg_dump로 옛 서버를 dump하는 일반적인 업그레이드 순서에서는 graph를 언급하는 메시지 없이 graph만 빠진다는 뜻입니다.

반면 이미 만들어진 결과물은 일반 데이터라 그대로 옮겨졌습니다. FOR PORTION OF로 나뉜 price 3행, MERGE로 생긴 파티션 sales_all2는 19beta4에서도 같은 모습으로 복원됐습니다. pg_upgrade 경로는 두 버전 바이너리를 한 환경에 두지 않아 측정하지 않았습니다.

베타 클러스터 점검 쿼리​

19 베타에서 이 기능들을 시험해 본 클러스터가 있다면, RC나 GA로 옮기기 전에 아래 쿼리로 남은 흔적을 찾을 수 있습니다. 세 쿼리 모두 19beta3에서 예상한 객체를 찾았고, 19beta4에서는 0행이었습니다.

-- 1) property graph 객체 (relkind 'g')
SELECT n.nspname, c.relname
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'g';

-- 2) property graph에 의존하는 view, materialized view
SELECT DISTINCT v.oid::regclass AS dependent_view, g.oid::regclass AS graph
FROM pg_depend d
JOIN pg_rewrite r ON r.oid = d.objid AND d.classid = 'pg_rewrite'::regclass
JOIN pg_class v ON v.oid = r.ev_class
JOIN pg_class g ON g.oid = d.refobjid AND d.refclassid = 'pg_class'::regclass
WHERE g.relkind = 'g' AND v.oid <> g.oid;

-- 3) 함수 본문에서 철회된 문법과 함수 이름 검색
SELECT p.oid::regprocedure AS func,
array_remove(ARRAY[
CASE WHEN p.prosrc ~* 'for\s+portion\s+of' THEN 'FOR PORTION OF' END,
CASE WHEN p.prosrc ~* 'graph_table|property\s+graph' THEN 'SQL/PGQ' END,
CASE WHEN p.prosrc ~* '(merge|split)\s+partitions?' THEN 'MERGE/SPLIT' END,
CASE WHEN p.prosrc ~* 'pg_get_(role|tablespace|database)_ddl' THEN 'pg_get_*_ddl' END,
CASE WHEN p.prosrc ~* 'pg_(en|dis)able_data_checksums' THEN 'online checksums' END
], NULL) AS hits
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
AND p.prosrc ~* 'for\s+portion\s+of|graph_table|property\s+graph|(merge|split)\s+partitions?|pg_get_(role|tablespace|database)_ddl|pg_(en|dis)able_data_checksums';

19beta3에서 1번은 g, 2번은 GRAPH_TABLE을 감싼 view v_edges, 3번은 FOR PORTION OF를 쓰는 PL/pgSQL 함수 fix_price()와 pg_get_role_ddl()을 호출하는 dump_role(text)를 찾았습니다.

3번이 필요한 이유는 PL/pgSQL의 검증 시점이 두 가지로 나뉘기 때문입니다. 19beta4에서 FOR PORTION OF가 든 PL/pgSQL 함수를 만들면 check_function_bodies = on(기본값)인 동안 CREATE 시점에 syntax error가 납니다. 그러나 pg_get_role_ddl()을 호출하는 PL/pgSQL 함수는 CREATE가 성공하고, 호출 시점에야 "function pg_get_role_ddl(regrole) does not exist"로 실패했습니다. 함수 이름은 실행 시점에 해석되기 때문입니다. 애플리케이션 SQL이나 배치 스크립트 안의 호출은 이 쿼리로 찾을 수 없으므로 코드 저장소도 따로 검색해야 합니다.

prosrc 정규식 검색이므로 주석이나 문자열 안의 단어도 검색됩니다. 결과는 후보 목록으로 보고 하나씩 확인하는 용도입니다.

기존 글 정정 범위​

이 블로그의 PostgreSQL 19 글 중 아래 부분이 19 GA 기준에 맞지 않게 되었습니다. 기존 글 본문은 이 글에서 고치지 않았고, 각 글에는 따로 안내를 추가합니다.

글맞지 않게 된 부분유효한 부분
PG19 SQL/PGQ 그래프 쿼리"PostgreSQL 19에 들어갔다"는 전제 전체SQL:2023 SQL/PGQ 표준과 문법 설명 (20 이후 참고용)
PG19 온라인 체크섬 전환"PostgreSQL 19의 온라인 전환" 절 이후data_checksums의 역할, 오프라인 pg_checksums 전환 역사
PG19 파티션 MERGE/SPLITPostgreSQL 19 문법 절, 비교 표와 락 절의 PostgreSQL 19 쪽기존 PostgreSQL의 수동 분할/병합 방식, Oracle 문법 설명
PG19 Beta 핵심 변화 4가지3절 FOR PORTION OF 전체, 체크리스트의 해당 항목, 9월 출시 예정 문구1절 MultiXact, 2절 병렬 autovacuum, 4절 JIT 기본값
PostgreSQL 19 새 기능 총정리GROUP BY ALL 절, MERGE/SPLIT PARTITIONS 절, 그래프 쿼리(SQL/PGQ) 절, 마무리 소감 중 GROUP BY ALL과 SQL/PGQ 언급, 9월 출시 예정 문구나머지 항목 (Beta 4 공지 revert 목록 기준)

"유효한 부분"은 Beta 4 공지와 이 글에서 확인한 revert 목록에 포함되지 않는다는 뜻입니다. 각 글의 다른 서술을 RC 기준으로 다시 검증했다는 뜻은 아닙니다. pg_get_*_ddl(), LC_COLLATE, CREATE SCHEMA 변경은 이 블로그의 기존 글에서 다루지 않았습니다.

마무리​

네 번째 베타에서 신기능이 여럿 제외되는 것을 보면서, 저도 19 글을 쓸 때 "베타에 들어갔다"를 "릴리스에 들어간다"로 너무 쉽게 받아들였다는 생각이 들었어요. RC가 나오면 릴리스 노트를 기준으로 남은 기능을 한 번 더 확인해 볼게요.

참고​

PostgreSQL 18.5 결번

· 약 8분

버전 번호가 하나 비어 있으면 신경이 쓰여요. 8월 13일에 나온 PostgreSQL 마이너 릴리스는 18.6인데, 그 앞은 18.5가 아니라 18.4입니다. 제 경우엔 패키지 목록을 보다가 "내가 하나 놓쳤나" 싶어 한참을 되짚었습니다. 결론부터 적으면 놓친 것은 없습니다. 18.5는 만들어졌지만 세상에 나오지 않았습니다.

공식이 말한 것은 한 줄뿐입니다​

사유를 언급한 곳은 두 군데인데, 표현이 미묘하게 다릅니다.

18.6 릴리스 노트에는 이렇게 적혀 있습니다.

18.5 was never released, due to a regression discovered post-wrap.

릴리스 공지는 좀 더 건조합니다.

This release skips PostgreSQL 18 versions from PostgreSQL 18.4 to 18.6. 18.5 was not shipped due to a regression.

공지는 "shipped"라고만 했고, 릴리스 노트는 post-wrap이라는 단어를 하나 더 얹었습니다. 이 단어가 사실상 전부를 설명합니다. 무엇이 wrap이고, 그 이후라는 게 왜 중요한지 알면 18.5가 사라진 경로가 그대로 보입니다.

마이너 릴리스는 이런 순서로 만들어집니다​

PostgreSQL의 Release process 위키에 절차가 공개돼 있습니다. 요약하면 committer가 버전을 찍고, 서버에서 tarball을 말고, packager들이 먼저 써 보고, 마지막에 태그를 미는 순서입니다.

2단계가 wrap입니다. pgsql 계정으로 borka.postgresql.org에 들어가 mk-release-bundle을 실행하면 tarball과 체크섬이 staging 디렉토리에 만들어집니다. 이 단계를 특정 서버에서만 하는 이유는 재현성 때문입니다. bison, flex, docbook 버전이 다르면 결과물이 달라지니 빌드 환경을 한 곳으로 고정해 둔 것입니다.

주목할 곳은 3단계와 4단계 사이입니다. 위키가 태그를 두고 분명하게 적어 뒀습니다.

Pushing a tag is more or less irreversible, so don't do this until packager preliminary testing is over.

태그를 밀면 되돌리기 어렵습니다. 그래서 packager들이 최소 24시간 먼저 만져 보고, 이상이 없어야 태그가 나갑니다. 18.5는 그 구간을 통과하지 못했습니다.

취소는 되는데 번호는 돌아오지 않습니다​

같은 위키에 사고 처리 절차도 한 줄로 적혀 있습니다.

In event of disaster, fix code as needed then repeat the wrap process, with or without a version number bump as seems appropriate.

문제가 생기면 코드를 고쳐 wrap을 다시 하되, 번호를 올릴지 말지는 그때 적절히 판단하라는 뜻입니다. 규정이 아니라 재량입니다. 18.5 건에서는 올리는 쪽을 택했습니다.

왜 그랬을까요. version_stamp.pl이 이미 Stamp 18.5. 커밋을 남겼고 그 번호로 tarball이 만들어졌기 때문입니다. staging에 있던 물건이라 일반 사용자에게 내려간 것은 아니지만, packager들 손에는 18.5라는 이름표가 붙은 파일이 이미 가 있었습니다. 같은 번호로 내용이 다른 tarball을 한 번 더 돌리는 것보다 번호를 하나 올리는 편이 안전합니다. 태그를 밀기 전이라 릴리스 자체는 멈출 수 있었지만, 번호는 그 시점에 이미 쓰인 것입니다.

8월 9일까지는 18.5였습니다​

타임라인도 이 해석과 맞습니다. Jonathan Katz(조너선 카츠)가 pgsql-hackers에 올린 8월 13일 릴리스 공지 초안은 2026년 8월 9일자인데, 거기 적힌 버전은 아직 18.5입니다.

마이너 릴리스는 관례상 목요일에 공개하고 tarball은 그 주 초에 맙니다. 8월 13일이 목요일이니 wrap은 10일 전후, 공지 초안은 그 직전에 돌린 셈입니다. 초안이 나가고 며칠 사이에 번호가 하나 올라갔습니다.

결번은 여섯 개뿐입니다​

그럼 이런 일이 자주 있었을까요. 세어 봤습니다. bucardo가 모든 버전의 릴리스 노트를 한 페이지에 모아 두는데, 여기서 never released를 전수 검색하면 여섯 건이 나옵니다.

결번직전 릴리스대체 릴리스
7.4.207.4.19 (2008-01-07)7.4.21 (2008-06-12)
8.0.168.0.15 (2008-01-07)8.0.17 (2008-06-12)
8.1.128.1.11 (2008-01-07)8.1.13 (2008-06-12)
8.2.88.2.7 (2008-03-17)8.2.9 (2008-06-12)
8.3.28.3.1 (2008-03-17)8.3.3 (2008-06-12)
18.518.4 (2026-05-14)18.6 (2026-08-13)

여섯 개지만 사건은 두 건입니다. 위의 다섯은 대체 릴리스 날짜가 전부 2008년 6월 12일로 같습니다. 한 번의 사고로 당시 지원하던 브랜치가 통째로 결번된 것입니다.

18.5는 혼자입니다. 같은 날 나온 17.11, 16.15, 15.19, 14.24는 번호가 정상입니다. 회귀가 18 브랜치에만 있었다는 뜻입니다.

2008년에는 무슨 일이 있었나​

8.3.2 릴리스 노트를 열면 Release Date 필드가 never released입니다. 그런데 본문은 멀쩡합니다. "This release contains a variety of fixes from 8.3.1"로 시작하고, Migration to Version 8.3.2 절도 붙어 있고, Changes 목록에 마흔 건 가까운 수정이 그대로 나열돼 있습니다. Windows에서 UTF-8 인코딩일 때 나던 크래시, %r 매크로의 archive 절단 지점 오산, GIN의 "too many LWLocks taken" 실패, SIGTERM으로 backend를 개별 종료했을 때 shared memory가 오염되던 문제가 보입니다.

그중 %r 항목은 지금 봐도 아찔합니다. warm standby 스크립트가 그 값을 믿고 WAL segment 파일을 버리면 데이터를 잃을 수 있었습니다. 이런 수정을 담은 릴리스가 문턱까지 갔다가 멈췄습니다.

노트에 사유는 적혀 있지 않습니다. 대신 대체 버전 노트의 첫 문장이 단서를 흘립니다.

버전첫 문장
8.3.3one serious and one minor bug fix over 8.3.2
8.2.9one serious and one minor bug fix over 8.2.8
7.4.21one serious bug fix over 7.4.20

세상에 나온 적 없는 버전을 기준점으로 삼아 "그 대비 몇 건"이라고 적었습니다. 결번본이 실재했다는 자백입니다. 그리고 여기서 말하는 "one serious"가 무엇인지는 다섯 브랜치 공통 항목으로 노트에 남아 있습니다.

Make pg_get_ruledef() parenthesize negative constants (Tom Lane)

Before this fix, a negative constant in a view or rule might be dumped as, say, -42::integer, which is subtly incorrect: it should be (-42)::integer due to operator precedence rules. Usually this would make little difference, but it could interact with another recent patch to cause PostgreSQL to reject what had been a valid SELECT DISTINCT view query. Since this could result in pg_dump output failing to reload, it is being treated as a high-priority fix.

view나 rule 안에 들어 있는 음수 상수를 괄호 없이 덤프하던 문제입니다. 연산자 우선순위상 (-42)::integer여야 하는데 -42::integer로 나왔습니다. 그 자체로는 대개 차이가 없지만, 당시 함께 들어간 다른 패치와 맞물리면 멀쩡하던 SELECT DISTINCT view 쿼리를 PostgreSQL이 거부했습니다.

여기서 심각도가 뛴 지점은 마지막 문장입니다. pg_dump 출력이 다시 적재되지 않을 수 있다는 결론이었습니다. 백업 파일이 복원되지 않는 문제는 다른 어떤 버그와도 무게가 다릅니다. 준비돼 있던 다섯 개 tarball을 버리고 이 수정을 얹어 다시 낸 이유로 충분합니다.

두 사건이 남긴 기록의 차이​

구조는 같습니다. wrap이 끝난 뒤 회귀가 잡혔고, 고쳐서 번호를 올려 다시 냈습니다. 달라진 것은 그 사실을 어떻게 적어 두느냐입니다.

18.6 노트는 결번 사유를 한 줄로 명시했습니다. 짧지만 왜 번호가 비었는지가 문서 안에 적혀 있습니다. 2008년 노트들은 never released라는 사실만 남기고 사유를 적지 않았습니다. 지금 그 이유를 재구성할 수 있는 것은 대체본이 "over 8.3.2"라는 표현을 쓴 덕분입니다. 의도한 기록이라기보다 흔적에 가깝습니다.

공통점도 있습니다. 여섯 개 전부 릴리스 노트 페이지가 지금도 살아 있습니다. 나오지 않은 버전의 문서를 지우지 않는 관행이 적어도 2008년부터 이어지고 있습니다. 덕분에 18년 전 사건을 1차 출처로 따라갈 수 있었습니다.

검색하면 섞여 나오는 이야기 하나​

18.5 결번을 검색하면 "standby가 구버전 마이너의 WAL을 재생하다 self-deadlock에 빠진다"는 내용이 사유처럼 딸려 나옵니다. 별개 사건입니다.

credativ가 정리한 그 버그는 MultiXactOffsetSLRU deadlock이고, 2026년 5월 14일에 나온 14.23, 15.18, 16.14에서 유입됐습니다. 17과 18은 영향이 없습니다. 증상은 standby의 startup 프로세스가 pg_stat_activity에서 LWLock/MultiXactOffsetSLRU 대기로 멈추는 형태입니다.

같은 8월 13일 릴리스에서 함께 고쳐졌을 뿐, 18.5를 취소시킨 원인이 아닙니다. 두 이야기를 붙여 쓰면 틀린 글이 됩니다.

지금 확인할 것​

운영 관점에서 할 일은 많지 않습니다.

  1. 18.4에서 18.6으로 바로 올립니다. 중간에 빠뜨린 버전은 없습니다
  2. 자동화 스크립트가 마이너 번호를 순차 증가로 가정하고 있다면 확인합니다. 18.4 + 1 = 18.5를 기대하는 코드는 여기서 멈춥니다
  3. 사내 문서나 지원 버전 표에 18.5를 적어 둔 곳이 있으면 지웁니다

3번은 사소해 보이지만 실제로 혼란을 만듭니다. "18.5 적용 예정"이라고 써 둔 계획서가 남아 있으면 나중에 읽는 사람이 없는 버전을 찾게 됩니다.

남은 질문​

18.5를 취소시킨 회귀가 정확히 무엇이었는지는 공개되지 않았습니다. 공식 문구는 "a regression"이 전부입니다. 2008년 사건에서도 회귀를 유발한 "another recent patch"가 무엇인지는 노트에 적혀 있지 않습니다.

저는 이 공백이 문제라고 보지는 않아요. 세상에 나가지 않은 코드의 버그를 상세히 적는 것은 그 자체로 이상한 일이니까요. 다만 번호가 하나 비면 사람은 반드시 이유를 찾습니다. 18.6 노트가 한 줄이라도 남겨 둔 덕분에 저 같은 사람이 패키지 목록 앞에서 오래 헤매지 않았습니다. 기록은 그 정도만 해 줘도 충분히 일을 합니다.

참고​

PostgreSQL 18.6 보안

· 약 5분

이번 분기 마이너 릴리스는 조용히 지나갈 수 없는 규모예요. 8월 13일 공지로 PostgreSQL 18.6, 17.11, 16.15, 15.19, 14.24가 한꺼번에 나왔는데, 보안 취약점 수정이 28건입니다. 통상 마이너 릴리스의 보안 수정이 손에 꼽는 수준인 걸 생각하면 이례적인 숫자입니다.

버전 번호부터 눈에 걸립니다. 18 계열의 직전 버전은 18.4인데 이번이 18.6입니다. 18.5는 릴리스 준비 중 회귀(regression)가 발견되어 배포 없이 결번 처리됐습니다. 18.4에서 바로 18.6으로 올라가면 되고, 중간에 놓친 버전은 없습니다.

CVSS 8점대만 12건​

28건 전체 중 CVSS 8.0 이상이 12건입니다. 성격별로 묶으면 그림이 보입니다.

계열대표 CVECVSS내용
클라이언트 도구CVE-2026-64648.1psql COPY FROM STDIN, 데이터 줄을 psql 명령으로 처리
클라이언트 도구CVE-2026-184088.8psql \unrestrict, 조작된 덤프 원본에서 임의 코드 실행
클라이언트 도구CVE-2026-193858.8pg_dump heap buffer overflow
heap overflowCVE-2026-146648.8regexp 처리, 임의 코드 실행 가능
heap overflowCVE-2026-146698.8to_char()
heap overflowCVE-2026-146768.8pg_stat_statements
type confusionCVE-2026-146718.8refint plan cache
type confusionCVE-2026-162388.8pg_restore_attribute_stats()
type confusionCVE-2026-162398.8cursor CLOSE + DECLARE
type confusionCVE-2026-146808.8"internal" 인자 처리
기타CVE-2026-146628.8tsvector/tsquery integer wraparound
기타CVE-2026-157428.8fuzzystrmatch, 임의 주소 기록

이 중 DBA가 특히 무겁게 볼 것은 psql 계열입니다. 나머지는 대체로 "DB에 로그인한 공격자가 권한을 넘어서는" 유형이라 접속 통제가 1차 방어선이 되지만, psql 취약점은 방향이 반대입니다. 신뢰할 수 없는 덤프나 SQL 파일을 psql로 읽어들이는 쪽이 피해자가 됩니다.

CVE-2026-6464는 COPY FROM STDIN 처리 중 초기 실패가 발생하면 뒤따르는 데이터 줄을 psql 명령으로 해석하는 문제입니다. 누군가 건네준 덤프 파일을 복원하는 일상 작업이 곧 공격 경로입니다. CVE-2026-18408도 같은 결로, pg_dump 출력에 포함되는 \unrestrict 처리를 악용하면 덤프를 만든 쪽(superuser 권한으로 조작된 서버)이 복원하는 쪽 클라이언트에서 임의 코드를 실행할 수 있습니다. 외부에서 받은 덤프를 복원할 일이 있는 조직이라면 클라이언트 패키지 업데이트를 서버만큼 서둘러야 합니다.

일반 서버 취약점과 psql 취약점의 공격 경로 비교

낮은 점수 중에도 운영에 직접 닿는 항목이 있습니다. CVE-2026-14663은 pgcrypto에서 비활성화된 cipher로 암복호화를 요청하면 조용히 평문으로 처리하던 문제입니다. 점수는 6.5지만 "암호화됐다고 믿었는데 평문이었다"는 유형이라 감사 관점에서는 확인해 볼 가치가 있습니다. CVE-2026-14672는 SCRAM 인증에서 scram_iterations 응답 차이로 계정 존재 여부가 노출되는 문제입니다.

업데이트만으로 끝나지 않는 세 가지​

이번 릴리스의 함정은 바이너리 교체 후에도 남는 숙제입니다. 릴리스 노트가 세 가지 후속 조치를 명시합니다.

첫째, parallel GIN index build를 쓴 적이 있다면 reltuples 오염을 확인합니다. PostgreSQL 14, 15, 16, 18에서 parallel worker가 초기화되지 않은 row count를 보고해 pg_class.reltuples가 Infinity나 NaN이 되는 버그가 있었습니다. 이 상태가 되면 autovacuum과 autoanalyze가 해당 테이블을 건너뛰고, 자연적으로는 복구되지 않습니다. GIN 인덱스를 가진 테이블을 이 쿼리로 확인합니다.

SELECT DISTINCT t.oid::regclass, t.reltuples
FROM pg_class t
JOIN pg_index i ON t.oid = i.indrelid
JOIN pg_class ic ON i.indexrelid = ic.oid
WHERE t.relhasindex AND ic.relam = 2742; -- 2742 = gin

reltuples가 Infinity, NaN, 혹은 말이 안 되는 값이면 해당 테이블에 ANALYZE를 실행합니다. vacuum이 안 도는 테이블은 wraparound 위험까지 이어지니, GIN을 쓰는 클러스터라면 이 확인을 빼먹지 않는 게 좋습니다.

둘째, btree_gist 인덱스의 REINDEX입니다. float4/float8 컬럼에서 NaN 처리, bit/bit varying 컬럼에서 정렬이 잘못되던 문제가 고쳐졌습니다. 해당 타입 컬럼에 btree_gist 인덱스가 있다면 REINDEX가 필요합니다.

셋째, 큰 ltree 값의 인덱스도 REINDEX 대상입니다. 약 14,653개 이상의 label을 가진 ltree 값에서 비교 연산이 틀려 B-tree 인덱스가 손상될 수 있었습니다. 그 정도 깊이의 ltree를 쓰는 곳은 드물겠지만, 해당된다면 REINDEX 대상입니다.

이 밖에 버그 수정 중에는 standby가 구버전 마이너의 WAL을 재생하다 멈추는 deadlock 수정(14~16), DEFAULT partition이 pruning에서 잘못 제외되던 문제, REINDEX CONCURRENTLY와 deferred unique constraint 조합 오류 같은 굵직한 것들이 포함됐습니다. 전체 목록은 릴리스 노트에 있습니다.

19 Beta 3와 14 EOL​

같은 날 PostgreSQL 19 Beta 3도 나왔습니다. 눈에 띄는 변화는 GROUP BY ALL 문법이 revert 된 것입니다. Beta 1에 들어왔던 편의 문법인데 최종 릴리스 전에 빠졌습니다. 그 외 FOR PORTION OF(temporal 문법) 수정 다수, logical replication의 sequence 동기화 race condition 수정 등이 반영됐습니다. 19 신기능을 검증 중이라면 Beta 3 기준으로 다시 확인하는 게 안전합니다.

그리고 PostgreSQL 14의 지원 종료가 2026년 11월 12일로 예고됐습니다. 이번 14.24를 포함해 앞으로 한 번의 릴리스만 남았습니다. 아직 14로 운영 중인 클러스터는 마이그레이션 일정을 지금 세워야 합니다.

지금 해야 할 일​

우선순위로 정리하면 이렇습니다.

  1. 서버 바이너리 업데이트 (마이너 업데이트라 dump/reload 불필요, 재시작만)
  2. 클라이언트 패키지(psql, pg_dump)도 같이 업데이트 - 이번 릴리스에서는 클라이언트가 서버만큼 급합니다
  3. GIN 인덱스 테이블의 reltuples 확인, 이상 시 ANALYZE
  4. btree_gist(float/bit 컬럼), 깊은 ltree 인덱스 REINDEX
  5. PostgreSQL 14 사용 중이면 업그레이드 계획 수립

28건이라는 숫자에 놀랐지만, 뒤집어 보면 fuzzing과 코드 감사가 코어에 촘촘히 들어가고 있다는 뜻이기도 합니다. 저는 클라이언트 도구가 공격면이 되는 흐름이 이번 릴리스의 진짜 교훈이라고 봐요. 서버는 잘 잠가 두는데 psql은 아무 파일이나 여는 습관, 이번 기회에 같이 고치면 좋겠습니다.

참고 자료​