비동기 I/O (AIO) ★
백엔드가 여러 read 요청을 큐에 쌓아 동시 처리. seq scan·bitmap heap scan·vacuum 가속. io_method로 worker·io_uring·sync 전환.
비동기 I/O부터 UUIDv7·시간 제약·OAuth까지 — 18 버전이 가져온 30개 신기능을 6개 카테고리로 한눈에.
백엔드가 여러 read 요청을 큐에 쌓아 동시 처리. seq scan·bitmap heap scan·vacuum 가속. io_method로 worker·io_uring·sync 전환.
멀티컬럼 인덱스에서 선행 컬럼에 = 조건이 없어도 인덱스 활용. prefix 컬럼을 생략한 쿼리의 실행 시간 단축.
WHERE 절의 OR 조건이 인덱스를 활용하도록 개선 — 기존 대비 큰 속도 향상.
hash join 성능 강화, merge join이 incremental sort를 지원해 정렬 비용 절감.
기존 B-tree·BRIN에 더해 GIN 인덱스도 병렬 빌드 지원 — 대용량 trgm/jsonb 인덱스 구축 가속.
bit_count()가 쓰는 popcount에 ARM NEON·SVE CPU intrinsic 추가 — ARM 서버에서 비트 연산 가속.
uuidv7()가 타임스탬프 정렬 UUID 생성 — 인덱스 지역성·캐시 효율↑. uuidv4()는 gen_random_uuid() 별칭.
저장 없이 쿼리 시점에 계산하는 생성 컬럼이 이제 기본값. stored 생성 컬럼은 논리 복제 지원.
기본값 = VIRTUALINSERT·UPDATE·DELETE·MERGE의 RETURNING에서 변경 전(OLD)·후(NEW) 값 동시 참조.
시간 범위 제약: PRIMARY KEY·UNIQUE에 WITHOUT OVERLAPS, FOREIGN KEY에 PERIOD 절 지원.
인스턴스 내 모든 DB에 대한 논리 복제본을 단일 명령으로 생성.
CREATE SUBSCRIPTION이 병렬 스트리밍을 기본 적용 — 트랜잭션 적용 처리량 개선.
복제 쓰기 충돌이 로그에 기록되고 pg_stat_subscription_stats 뷰에서 가시화.
idle_replication_slot_timeout으로 유휴 슬롯 자동 드롭 — WAL 파일 과적재 방지.
확장을 통한 oauth 인증 메커니즘 — SSO 시스템 연동.
postgres_fdw·dblink이 원격 인스턴스에 SCRAM passthrough 인증 지원.
pgcrypto가 SHA-2 지원. md5 인증은 deprecated(향후 제거 예정).
ssl_tls13_ciphers 파라미터로 서버 측 TLS 1.3 암호 스위트 지정.
암호화 함수의 FIPS 모드 동작 검증 지원 추가.
메이저 업그레이드 시 planner 통계 유지 — 업그레이드 직후 성능 회복 가속(ANALYZE 대기 제거).
--jobs 병렬 체크, 객체 많은 DB 가속, --swap 디렉토리 스왑 플래그.
EXPLAIN ANALYZE가 버퍼 접근·인덱스 lookup 횟수 자동 표시, VERBOSE 시 CPU·WAL·read 통계.
pg_stat_all_tables에 vacuum 소요 시간, 연결별 I/O·WAL 사용 통계 추가.
initdb로 만든 신규 DB는 page checksum이 기본 on — 데이터 손상 조기 탐지.
일반 vacuum 중 더 많은 페이지를 선제적으로 freeze — wraparound 위험·집중 vacuum 부담 완화.
2003년(7.4) 이후 첫 새 프로토콜 버전. libpq는 3.0 기본, 신규 클라이언트는 3.2 지원 추가.
완전한 유니코드 대소문자 변환 + 가속 비교. upper()·lower()·신규 casefold().
비결정적(nondeterministic) 콜레이션으로 LIKE 패턴 매칭 지원 — 복잡한 다국어 비교.
전문 검색이 libc 대신 클러스터 기본 콜레이션 provider 사용(reindex 필요할 수 있음).
로컬 테이블 정의를 그대로 빌려 외부 테이블 스키마 생성 — 보일러플레이트 제거.
쿼리 보유 약 400개 파일을 6개 도메인으로 나눠 전수분석한 뒤, beta(프로덕션) DB 실제 row 수로 각 이슈를 재판정했다. "코드상 안티패턴"이라도 대상 테이블이 작으면 기각, 클 때만 진짜 이슈로 채택. P0 없음.
측정된 beta 규모 — lead_contacts 130만 · emails 118만(ws당 53.6만) · leads 114만(ws당 66.5만) · sequence_enrollments 84.3만(시퀀스당 10.8만) · sequence_step_contents 47.6만 · visitor_sessions 5.8만(ws당 5.1만) · activity_logs 5.6만 · email_replies 5,934(ws당 1,810) · notifications 2.5만(user당 100 cap) · linkedin_prospects·utm_visits·iam_audit_logs·nudge_campaigns·buyer_search_analytics = 0
| 심각도 | 위치 | 유형 | 실측 규모 (beta) | 문제 → 수정 |
|---|---|---|---|---|
| P1 | sequence-enrollment.service.ts:138·186·323 | OFFSET | 시퀀스당 108,147 | user-facing 목록 3곳. 뒤 페이지일수록 앞부분 재스캔. → (enrolledAt desc, id desc) keyset |
| P1 | assessment.service.ts:314·330 | OFFSET | ws당 leads 665,389 | leadScore DESC 평가 화면을 66만 row에 OFFSET. → (leadScore, id) keyset |
| P1 | lead-search.service.ts:518 | OFFSET | ws당 leads 665,389 | 비-createdAt 정렬 fallback이 66만 row OFFSET. → 정렬키별 복합 keyset |
| P1 | visitor.service.ts:692 | OFFSET | ws당 51,421 | OFFSET + 매 호출 full count(*) 2중 비용. → (lastVisitAt, id) keyset |
| P1 | personalized-email-data.service.ts:101 | OFFSET | 전역 475,573 / lead당 4,299 | offset 값이 API로 직접 노출 → 깊은 페이지 호출 가능. → (leadId, stepOrder) 복합 keyset |
| P1 | lead-search.service.ts:85-146 | trgm GIN 부재 ILIKE | leads 114만 | website/country/businessType 등 무인덱스 컬럼 leading-wildcard ILIKE → 114만 row seq scan. → trgm GIN 추가 또는 검색 컬럼 축소 |
| P1 | weekly-digest-data.service.ts:105·249 | db→analyticsDb | emails 118만 ⋈ enroll 84만 | COUNT(DISTINCT)+대형 LEFT JOIN hashagg를 32MB db로 → 디스크 spill. → analyticsDb(256MB) |
| P1 | visitor.service.ts:897·1242 | db→analyticsDb | ws당 51,421 | count(distinct country)+5 FILTER / COUNT(DISTINCT ip)+GROUP BY를 db로 → spill. → analyticsDb |
sequence-auto-enroll 2곳(enrollment↔step_execution 비원자) · bulk-enrollment-scheduling(enrolled인데 execution 없는 영구 미발송 리드 위험) — enrollments 84만 규모라 정합성 깨지면 추적 난해. → db.transaction
sales-advisor.queries(행당 5쿼리×50 = ~250 round-trip) · benchmark-comparison · warmup-recovery.worker · sequence bulkUnenroll. → inArray / 단일 GROUP BY 배치화
board-email-broadcast: 미구독 전체 유저 limit 없이 로드 후 Promise.allSettled 무제한 동시 발송 → 메모리·SES rate 폭주. → keyset chunk + 동시성 cap
ui-events 집계 fan-out(activity_logs 5.6만, analyticsDb import 없음) · email-replies-revenue-insights(replies 6k이나 emails 118만 상관 EXISTS). 현재 규모선 spill 경계. → analyticsDb 선제 이전
email-replies.service.ts:158 — 현재 ws당 1,810으로 작지만 단조 증가. 임계 도달 전 keyset 전환 권장. → (createdAt, id) keyset
followup-email upsertSessionNudgePending: SELECT→INSERT인데 userId UNIQUE 부재 → 동시 이벤트 중복 발송. 현재 onboarding_session_nudge=0이라 미발현이나 코드 결함. → unique(userId)+ON CONFLICT
hot-lead prefilter · buyer-search(analytics 0 row) — 배치/소규모라 즉각 위험은 낮으나 동일 패턴. → businessType trgm GIN
sequence_steps.emailSignatureId · enrolledBy/stoppedBy · closedBy · createdBy/registeredBy. SET NULL FK 무인덱스 → 유저/시그니처 삭제 시 seq scan. 삭제 희소라 의도된 트레이드오프.
| 위치 | 코드상 의심 | 실측 | 기각 사유 |
|---|---|---|---|
| notification.service.ts:217 | OFFSET 무한증가 피드 | user당 100 | 피드에 100건 cap 존재 → OFFSET 깊이 한정, 사실상 무비용 |
| linkedin-sdr-prospect.service.ts:1577·1616 | OFFSET prospects | 0 row | linkedin_prospects 미사용 — 발현 불가 |
| buyer-search analytics/agent-analytics | OFFSET + ILIKE | 0 / 1.8k | buyer_search_analytics 0 row, lead_discovery_sessions 1.8k 소규모 |
| utm · iam · nudge OFFSET | OFFSET 증가 테이블 | 0 row | utm_visits·iam_audit_logs·nudge_campaigns 전부 빈 테이블 |
| admin-ai-sales-team:328 · impersonate:463 | OFFSET audit/mission | 576 / 72 | OFFSET 비용이 의미 없는 소규모 |
| weekly-digest:122 · history | OFFSET history | 3~6 row | 극소 — keyset 불필요 |
| billing/* OFFSET 6곳 | admin 목록 OFFSET | 소규모 | admin 전용 + 상품/플랜/구독 수십~수백 row |