AWS 기술 블로그
Amazon Aurora 및 Amazon RDS의 PostgreSQL 18: 보안, 모니터링 및 개발자 기능 향상
이 글은 AWS Blog의 “PostgreSQL 18 on Amazon Aurora and Amazon RDS: Security, monitoring, and developer enhancements” by Nazneen Jafri, Sukhpreet Kaur Bedi, Ranjan Burman, and Baji Shaik 게시글을 번역한 글 입니다.
이 시리즈의 1부에서는 스킵 스캔 최적화, 향상된 EXPLAIN 출력, 자동 셀프 조인 제거, vacuum/autovacuum 개선 사항을 포함한 PostgreSQL 18의 성능 향상에 대해 살펴보았습니다. 두 번째 파트인 이 글에서는 운영 효율성과 전반적인 개발자 경험을 개선하는 보안, 모니터링, 개발자 생산성 및 논리적 복제 기능 향상에 초점을 맞춥니다.
PostgreSQL 18은 더 안전한 인증 방법을 위해 MD5 비밀번호 인증을 지원 중단합니다. MD5 인증은 PostgreSQL 18에서 여전히 작동하지만, 향후 릴리스에서 제거될 예정입니다. SCRAM-SHA-256으로 마이그레이션하는 것을 권장합니다.
Amazon Relational Database Service(Amazon RDS) 또는 Amazon Aurora PostgreSQL-Compatible Edition(Aurora PostgreSQL)을 사용하는 경우, 기본 password_encryption은 이미 scram-sha-256으로 설정되어 있습니다. 그러나 여전히 MD5를 사용하고 있거나, 기본값이 업데이트되기 전부터 기존 사용자가 MD5 해시 비밀번호를 가지고 있는 경우에는 조치를 취해야 합니다. DB 파라미터 그룹을 통해 password_encryption 파라미터를 수정할 수 있습니다. 새로운 사용자 지정 파라미터 그룹을 생성하거나 기존 그룹을 수정하고, password_encryption을 scram-sha-256으로 설정한 다음, 파라미터 그룹을 인스턴스에 적용합니다. 이는 동적 파라미터이므로 재부팅 없이 변경 사항이 즉시 적용됩니다. 그런 다음 영향을 받는 사용자들의 비밀번호를 재설정하여 새로운 SCRAM 해시가 생성되도록 해야 합니다.
PostgreSQL 18은 또한 md5_password_warnings라는 새로운 GUC 파라미터를 도입했으며, 이는 기본적으로 활성화되어 있습니다. 이는 동적 파라미터(PostgreSQL 컨텍스트: superuser)로, 재부팅 없이 파라미터 그룹에서 활성화하거나 비활성화할 수 있습니다. 활성화되면, MD5 암호화를 사용하여 비밀번호가 저장될 때마다 CREATE ROLE 및 ALTER ROLE이 엔진 로그에 지원 중단 경고를 발생시킵니다. 이러한 경고를 신호로 활용하여 어떤 역할이 여전히 MD5 비밀번호를 가지고 있는지 식별하고, 해당 역할을 SCRAM-SHA-256으로 마이그레이션합니다. 마이그레이션이 완료되면 경고할 MD5 비밀번호가 더 이상 없으므로 경고가 자연스럽게 사라지며, 이 시점에서는 파라미터를 끄더라도 아무런 영향이 없습니다.
PostgreSQL 18은 병렬 워커 활동을 추적하는 두 개의 새로운 컬럼을 pg_stat_database와 pg_stat_statements 모두에 추가합니다:
| 컬럼 | 설명 |
parallel_workers_to_launch |
플래너가 실행하려고 의도한 병렬 워커 수 |
parallel_workers_launched |
실제로 실행된 병렬 워커 수 |
이 두 값 사이의 차이가 핵심 신호입니다. 플래너가 워커를 요청했지만 max_parallel_workers 또는 max_worker_processes가 소진되어 시스템이 워커를 제공할 수 없는 경우, 쿼리는 자동으로 더 적은 수의 워커 또는 직렬 실행으로 대체됩니다. PostgreSQL 18 이전에는 이를 확인할 수 없었습니다. Performance Insights에서 IPC 대기 이벤트를 사용하여 병렬 처리가 발생하고 있다는 것은 볼 수 있었지만, 워커가 얼마나 자주 실행에 실패했는지 또는 어떤 쿼리가 영향을 받았는지를 수치화할 방법이 없었습니다.
0으로 변경되어 기본적으로 병렬 쿼리가 비활성화됩니다. Aurora PostgreSQL 17 및 이전 버전에서는 이 파라미터가 2로 설정되어 있었습니다. 특정 워크로드 요구 사항을 지원하기 위해 이를 수정하여 병렬 쿼리를 다시 활성화할 수 있습니다. 자세한 내용은 Aurora PostgreSQL용 병렬 쿼리 또는 RDS for PostgreSQL용 병렬 쿼리를 참조하세요.
pg_stat_database 뷰는 데이터베이스의 쿼리 전반에 걸친 병렬 워커 활동을 누적합니다. 지속적인 worker_shortage는 max_parallel_workers 또는 max_worker_processes 한도에 지속적으로 도달하고 있음을 나타냅니다:
이 데이터베이스는 총 66개의 워커를 요청했지만 20개만 실행되었고, 46개의 워커가 실행에 실패했습니다. 이는 max_parallel_workers를 늘리거나, 경합을 줄이기 위해 일부 쿼리에서 병렬 처리를 비활성화해야 한다는 강력한 신호입니다.
pg_stat_statements는 쿼리별로 동일한 지표를 보여주므로, 어떤 특정 쿼리가 가장 큰 영향을 받는지 식별할 수 있습니다:
launch_success_rate가 100퍼센트 미만이라는 것은 시스템이 플래너가 요청한 워커를 지속적으로 제공하지 못하고 있음을 의미합니다. 예를 들어, 13번 실행되며 호출당 2개의 워커를 요청하지만(총 26개) 호출당 1개만 받는(총 13개) 쿼리는 50퍼센트의 성공률을 보입니다. 절반의 경우, 리소스 경합으로 인해 워커를 실행할 수 없었던 것입니다.
워커가 지속적으로 실행에 실패하는 쿼리를 찾으려면 다음 쿼리를 사용합니다. 영향을 받는 쿼리의 경우, 더 많은 워커 용량을 제공하기 위해 max_parallel_workers를 늘리거나, pg_hint_plan 또는 세션 수준의 SET max_parallel_workers_per_gather = 0을 사용하여 해당 특정 쿼리에 대한 병렬 처리를 비활성화하는 것을 고려하세요:
이러한 지표는 Amazon RDS Performance Insights에서 볼 수 있는 IPC 대기 이벤트(IPC:ExecuteGather, IPC:BgWorkerStartup, IPC:ParallelFinish)를 보완합니다. 대기 이벤트는 병렬 처리가 발생하고 있다는 것을 알려줍니다. 새로운 컬럼은 시스템이 플래너가 요청한 워커를 제공하고 있는지 여부를 알려줍니다.
PostgreSQL 18은 논리적 복제 적용 중에 발생하는 특정 충돌 유형을 추적하는 새로운 컬럼을 pg_stat_subscription_stats에 추가합니다. 이전에는 이 뷰가 집계된 오류 개수만 보고했습니다. 이제는 충돌을 카테고리별로 세분화하여 복제 문제를 진단하기가 훨씬 쉬워졌습니다.
7개의 새로운 confl_* 컬럼은 다음을 추적합니다:
| 컬럼 | 충돌 시나리오 |
confl_insert_exists |
INSERT가 구독자에서 고유 제약 조건을 위반함(행이 이미 존재함) |
confl_update_origin_differs |
다른 복제 오리진에 의해 수정된 행에 대한 UPDATE |
confl_update_exists |
UPDATE가 기존 데이터와 충돌함 |
confl_update_missing |
UPDATE 대상 행이 구독자에 없음 |
confl_delete_origin_differs |
다른 오리진에 의해 수정된 행에 대한 DELETE |
confl_delete_missing |
DELETE 대상 행이 구독자에 없음 |
confl_multiple_unique_conflicts |
단일 작업에서 여러 고유 제약 조건 위반 |
PostgreSQL 18 이전에는 충돌로 인해 논리적 복제가 중단되었을 때, 사용 가능한 정보는 총 apply_error_count와 로그의 오류 메시지뿐이었습니다. 충돌 유형을 파악하려면 로그 파일을 파싱해야 했습니다.
새로운 컬럼을 통해 충돌 패턴을 한눈에 파악할 수 있습니다:
- 높은
confl_insert_exists– 구독자에 이미 존재하는 행들 때문에 발행자가 데이터를 삽입하려고 할 때 충돌이 발생하는 경우로, 구독자에 데이터가 직접 기록되었거나 초기 동기화가 실패한 것이 원인일 가능성이 높습니다. - 높은
confl_update_missing또는confl_delete_missing– 행이 발행자에는 존재하지만 구독자에는 없습니다. 이는 구독자에서 수동으로 데이터를 조작한 후에 흔히 발생합니다. - 높은
confl_update_origin_differs– 행이 여러 출처에 의해 수정되고 있습니다. 이는 양방향 복제 구성에서 주로 나타나는 지표입니다.
기존에는 PostgreSQL 메이저 업그레이드를 할 때마다 옵티마이저 통계가 사라졌습니다. 업그레이드 후 플래너는 테이블 크기, 컬럼 분포 또는 인덱스 선택도에 대한 정보를 전혀 갖지 못한 상태가 됩니다. 데이터베이스 전반에 걸쳐 ANALYZE가 완료될 때까지 모든 쿼리는 기본 추정치에 의존하여 실행되었습니다. 특히 pg_upgrade(빠른 업그레이드 경로)의 경우, 그 영향이 더욱 심각했습니다. 업그레이드는 몇 분 안에 완료되지만, 대규모 데이터베이스에서 ANALYZE를 실행하는 데는 몇 시간이 걸릴 수 있으며, 그동안 쿼리 성능이 저하된 상태로 운영해야 했습니다.
이제 pg_upgrade는 업그레이드 프로세스의 일부로 이전 클러스터에서 새 클러스터로 옵티마이저 통계를 자동으로 전송합니다. 수동 개입이 필요하지 않습니다. 가져오기는 새로 추가된 두 카탈로그 함수 pg_restore_relation_stats()와 pg_restore_attribute_stats()가 처리합니다.
덤프/복원 방식으로 업그레이드하는 경우에는, pg_dump --with-statistics 옵션으로 통계 정보를 명시적으로 내보낼 수 있습니다.
PostgreSQL 18로 pg_upgrade를 수행한 후, 새 클러스터는 이전 클러스터와 동일한 플래너 통계를 갖게 됩니다. 이로 인해 첫 번째 연결부터 쿼리가 좋은 실행 계획으로 실행됩니다.
확장 통계(CREATE STATISTICS로 생성됨)는 보존되지 않습니다. 계산된 데이터가 아닌 객체 정의만 이전됩니다. 업그레이드 후 다음을 실행합니다:
참고: --all 플래그는 이 명령을 클러스터의 각 데이터베이스에 적용합니다.
새로운 --missing-stats-only 플래그(PostgreSQL 18에서 추가됨)는 누락된 통계만 수집합니다. 실제로 업그레이드 후 확장 통계만 이에 해당됩니다. 이 방식은 데이터베이스 전체를 대상으로 하는 ANALYZE보다 훨씬 빠릅니다.
모니터링 통계(pg_stat_* views)도 보존되지 않으므로, autovacuum과 autoanalyze는 어떤 테이블이 마지막으로 처리되었는지에 대한 기록을 잃게 됩니다. 사용량이 많은 대용량 테이블의 경우, 업그레이드 직후 수동으로 VACUUM (ANALYZE)를 실행하는 것을 고려합니다.
RDS 메이저 버전 업그레이드(예: PostgreSQL 16에서 18로)의 경우, 업그레이드 과정에서 내부적으로 pg_upgrade를 사용합니다. PostgreSQL 18을 대상으로 하면 옵티마이저 통계 정보가 자동으로 보존됩니다. 업그레이드가 완료된 후, 통계가 존재하는지 확인합니다.
특정 테이블에 확장 통계가 존재하는 경우, 해당 테이블을 식별하고 대상을 지정한 analyze를 실행합니다:
PostgreSQL 18은 RFC 9562에 정의된 UUID 버전 7을 생성하기 위한 네이티브 함수인 uuidv7()을 도입했습니다. UUIDv7은 밀리초 단위의 Unix 타임스탬프와 랜덤 비트를 결합하여, 전역적으로 고유하면서도 생성 시간순으로 자연스럽게 정렬할 수 있는 UUID를 생성합니다.
기존의 gen_random_uuid() 함수(이제 uuidv4()라는 별칭으로도 사용 가능)는 완전히 무작위적인 UUID를 생성합니다. 전역적으로 고유하지만, 무작위 UUID는 잘 알려진 B-tree 인덱스 문제를 일으킵니다. 새로운 행이 인덱스의 무작위 위치에 삽입되어 빈번한 페이지 분할(page split)과 캐시 미스(cache miss)를 유발합니다. UUID 기본 키를 사용하는 삽입 작업이 많은 워크로드에서는, 이로 인해 인덱스 비대화와 쓰기 성능 저하가 발생합니다.
패턴이 없으며, 각 UUID가 무작위로 흩어져 있습니다.
앞의 12개 문자(019d7445-07d1)는 밀리초 단위 타임스탬프를 인코딩합니다. 같은 밀리초 내에 생성된 UUID의 경우 이 부분이 동일합니다. 나머지 비트는 고유성과 밀리초 미만 단위의 단조성을 제공합니다. 그 덕분에 새 행은 항상 B-tree 인덱스의 끝부분 근처에 삽입되므로 무작위 페이지 분할이 사라집니다.
PostgreSQL 18은 uuid_extract_timestamp() 함수를 확장하여 UUIDv7을 지원합니다:
추출된 타임스탬프는 now()와 밀리초 단위까지 일치합니다. 값 자체에서 직접 모든 UUIDv7이 언제 생성되었는지 복원할 수 있습니다.
uuidv7()은 내부에 포함된 타임스탬프를 이동시키는 선택적 간격(interval) 인자를 받을 수 있습니다.
UUIDv7 값은 단조 증가하므로, ORDER BY id를 사용하면 행이 삽입 순서대로 반환됩니다. 타임스탬프가 키 자체에 포함되어 있어, 많은 경우 별도의 정렬용 컬럼이 필요 없어집니다.
PostgreSQL 18은 CREATE SUBSCRIPTION의 streaming 옵션 기본값을 off에서 parallel로 변경했습니다. 이로 인해 병렬 적용이 논리적 복제의 기본 동작이 되어, 처리량이 향상되고 대규모 트랜잭션의 지연이 줄어듭니다.
논리적 복제는 발행자의 WAL 스트림에서 트랜잭션을 재생하여 구독자에 변경 사항을 적용합니다. PostgreSQL 16 이전에는 대규모 트랜잭션이 적용되기 전에 구독자 측에 전체가 버퍼링되었습니다. 이로 인해 대량 작업 중에 복제 지연이 급증했고, 상당한 메모리 또는 임시 파일 사용이 필요했습니다.
PostgreSQL 16은 streaming = parallel을 옵션으로 도입하여, 대규모 트랜잭션이 발행자에서 커밋되기 전에 구독자로 스트리밍을 시작할 수 있으며, 여러 적용 워커가 변경 사항을 동시에 처리합니다. 그러나 이것이 기본값은 아니었기 때문에, DBA가 명시적으로 사용을 설정(opt in)해야 했습니다.
PostgreSQL 18은 parallel을 기본값으로 만들었는데, 이는 기능의 성숙도와 대부분의 워크로드에 제공하는 성능상 이점을 반영한 결정입니다.
PostgreSQL 18 이전에는 streaming의 기본값이 off였습니다.
PostgreSQL 18에서는 streaming의 기본값이 parallel입니다.
streaming = parallel을 사용하면:
- 대규모 트랜잭션이 발행자에서 커밋되기 전에 복제를 시작합니다.
- 여러 개의 적용 워커가 변경 사항을 동시에 처리합니다.
- 병렬 적용으로 대량 작업의 복제 지연이 줄어듭니다.
- 구독자가 트랜잭션 전체를 버퍼링하지 않으므로 메모리를 더 적게 사용합니다.
PostgreSQL 18로 업그레이드하기 전에 생성된 기존 구독은 이전 streaming 설정을 유지합니다. 현재 설정을 확인합니다:
substream 컬럼은 f(off), t(on), p(parallel) 중 하나로 표시됩니다.
기존 구독을 업데이트하려면 다음과 같이 실행합니다:
병렬 스트리밍은 논리적 복제 프로토콜 버전 4 이상을 필요로 하며, 이는 PostgreSQL 16 이상의 발행자와 구독자 간에 지원됩니다.
PostgreSQL 18은 지정된 시간보다 오래 비활성(inactive) 상태로 있던 복제 슬롯(replication slot)을 자동으로 무효화(invalidate)하는 새로운 파라미터인 idle_replication_slot_timeout을 도입했습니다. 이는 논리적 복제에서 가장 흔한 운영상의 위험 중 하나인 방치된 슬롯이 조용히 WAL을 누적하여 결국 디스크 고갈을 유발하는 문제를 해결해 줍니다.
복제 슬롯은 WAL sender가 해당 슬롯의 구독자에 의해 소비되지 않은 WAL 세그먼트를 폐기하지 못하도록 합니다. 네트워크 장애, 애플리케이션 충돌, 잘못된 구성으로 인해 구독자의 연결이 끊기면, 슬롯은 프라이머리에서 계속 활성 상태로 남아 WAL을 무기한 정리되지 못하게 만듭니다. 사용량이 많은 시스템에서는 이로 인해 몇 시간 안에 pg_wal 디렉터리가 가득 차서 프라이머리가 쓰기를 더 이상 받지 못하게 될 수 있습니다.
PostgreSQL 18 이전에는 유일한 보호 방법이 수동 모니터링뿐이었습니다. 즉, pg_replication_slots에서 active = false이면서 confirmed_flush_lsn이 오래된(stale) 슬롯을 주기적으로 조회한 뒤, 이를 수동으로 삭제해야 했습니다.
idle_replication_slot_timeout은 슬롯이 자동으로 무효화되기 전까지 얼마나 오래 비활성 상태로 유지될 수 있는지를 지정합니다.
기본값은 0(비활성화)입니다:
0이 아닌 값으로 설정하면, PostgreSQL은 지정된 기간보다 오래 활성 연결이 없었던 모든 슬롯을 자동으로 무효화합니다. 슬롯은 삭제되는 것이 아니라 무효(invalid)로 표시됩니다. invalidation_reason = 'idle_timeout'과 함께 pg_replication_slots에 계속 남아 있으므로, 이를 식별하고 정리할 수 있습니다.
파라미터 그룹에서 파라미터를 설정합니다(재부팅 필요 없음):
무효화된 슬롯 모니터링:
무효화된 슬롯 삭제:
WAL 보존(retention)이 스토리지 비용과 클러스터 가용성에 직접적인 영향을 미치는 Aurora PostgreSQL의 경우, 논리적 복제를 사용하는 모든 클러스터에 대해 이 파라미터를 활성화하는 것을 권장합니다.
PostgreSQL 18은 대량 데이터 적재 중 오류 처리에 대한 더 세밀한 제어 기능을 제공하는 두 가지 개선 사항을 COPY 명령에 추가했습니다: REJECT_LIMIT와 silent라는 새로운 LOG_VERBOSITY 수준입니다.
제어된 오류 허용치를 위한 REJECT_LIMIT:
PostgreSQL 18 이전에는 COPY FROM의 ON_ERROR = 'ignore' 옵션이 데이터 타입 변환 오류가 있는 모든 행을 상한 없이 건너뛰었습니다. 이는 가능한 만큼만 적재하는(best-effort) 로딩에는 유용했지만, 운영 환경에서는 위험했습니다. 손상된 파일이 아무런 보호 장치 없이 수천 개의 행을 조용히 버릴 수 있었기 때문입니다. 결국 첫 번째 오류에서 실패하거나(기본 동작) 무제한의 오류를 허용하는 것 중에서 선택해야 했습니다. PostgreSQL 18은 REJECT_LIMIT를 도입하여, COPY FROM이 중단되기 전에 허용할 최대 오류 개수를 설정합니다. 오류 개수가 지정된 값을 초과하면, ON_ERROR = 'ignore'가 설정되어 있더라도 명령이 실패합니다.
파일에 잘못된 형식의 행이 50개 포함되어 있으면, 로드가 완료되고 50개의 행을 건너뛰었다고 보고합니다. 잘못된 형식의 행이 101개 포함되어 있으면, 101번째 오류에서 명령이 실패합니다. 이를 통해 안전망을 확보할 수 있습니다. 즉, 실제 데이터에 가끔 문제가 있다는 점은 받아들이면서도, 원본 파일이 근본적으로 손상된 상황은 놓치지 않고 잡아낼 수 있습니다.
LOG_VERBOSITY silent:
ON_ERROR = 'ignore'가 활성화된 상태에서 PostgreSQL은 버려진 각 행마다 NOTICE를 출력시키거나(verbose 수준) 마지막에 요약 건수를 출력합니다(기본 수준). 알려진 오류율을 예상하고 수용하는 대규모 작업의 경우, 이러한 메시지는 실질적으로 유용한 정보를 제공하지 않으면서 로그에 노이즈를 추가합니다. PostgreSQL 18은 세 번째 LOG_VERBOSITY 수준인 silent를 추가했습니다. 이 수준은 마지막 요약 개수를 포함하여 버려진 행에 대한 모든 메시지를 억제합니다.
오류에 대한 엄격한 상한선을 두되 행 단위 로깅이 필요하지 않은 운영 ETL 파이프라인의 경우, REJECT_LIMIT과 LOG_VERBOSITY silent를 함께 사용합니다.
PostgreSQL 18은 INSERT, UPDATE, DELETE 및 MERGE 명령의 RETURNING 절에 OLD와 NEW 별칭을 도입했습니다. 이를 통해 단일 DML 문이 수정된 행의 이전 상태와 현재 상태를 모두 반환할 수 있게 되어, 변경 전후의 값을 얻기 위해 별도의 쿼리나 트리거 기반의 우회 방법을 쓸 필요가 없어졌습니다.
이전에는 RETURNING이 명령 유형에 따라 동작이 고정되어 있었습니다. INSERT는 새로 삽입된 행을 반환했고, UPDATE는 수정 이후의 행을 반환했으며, DELETE는 삭제 전에 존재했던 행을 반환했습니다. 단일 문에서 이전 값과 새 값을 모두 얻을 방법은 없었습니다.
동일한 구문이 MERGE에서도 작동하며, OLD/NEW를 merge_action()과 결합하면 단일 문에서 전체 변경 보고서를 얻을 수 있습니다:
이러한 기능에는 구성 변경이 필요하지 않습니다. PostgreSQL 18을 실행하는 Amazon RDS for PostgreSQL 및 Aurora PostgreSQL에서는 모든 DML 명령의 RETURNING 절에서 OLD와 NEW가 동작합니다. 이전에 변경 캡처를 위해 트리거나 다중 문 트랜잭션에 의존했던 애플리케이션은 단일 문으로 통합할 수 있어, 왕복 횟수(round trip)를 줄이고 애플리케이션 코드를 단순화할 수 있습니다.
이 시리즈의 두 번째 파트에서는 PostgreSQL 18의 보안, 모니터링, 개발자 및 논리적 복제 기능 향상에 대해 다루었습니다. 여기에는 SCRAM-SHA-256을 위한 MD5 비밀번호의 지원 중단, 개선된 병렬 워커 모니터링, 타임스탬프 정렬 UUID를 위한 새로운 uuidv7() 함수가 포함됩니다. 이러한 기능들은 함께 Aurora PostgreSQL 및 Amazon RDS for PostgreSQL에서의 운영 및 개발 경험을 강화합니다.
1부에서 다룬 성능 향상과 함께, PostgreSQL 18은 성능, 보안, 가시성 및 개발자 생산성 전반에 걸친 개선 사항을 제공합니다.
Aurora PostgreSQL 클러스터 또는 Amazon RDS for PostgreSQL 인스턴스를 버전 18로 업그레이드하세요. 자세한 내용은 Aurora PostgreSQL 업그레이드 설명서 또는 Amazon RDS for PostgreSQL 업그레이드 가이드를 참조하세요.