티스토리 뷰

카테고리 없음

PostgreSQL에서 오래 열린 트랜잭션과 VACUUM 지연을 함께 보는 이유

생각방 2026. 11. 11. 15:26

목차


    주문 상태를 자주 갱신하는 테이블에서 n_dead_tup이 계속 늘어납니다. 운영 담당자가 확인한 통계에는 최근 autovacuum 실행 시각이 남아 있습니다. 같은 DB에서 보고서 SQL은 끝났는데 트랜잭션이 idle in transaction 상태로 남은 세션도 보입니다.


    DB의 행 버전과 열린 세션

    PostgreSQL 18을 기준으로 구성한 설명용 업무 장면입니다. 먼저 대상 테이블의 VACUUM 이력과 진행 단계를 확인합니다. 실행을 마쳤는데도 지울 수 없었던 행 버전이 로그에 남아 있다면, 그 시각에 오래된 트랜잭션이 유지되었는지 대조합니다.

    VACUUM이 실행돼도 이전 행 버전이 남는 이유

    PostgreSQL은 UPDATE나 DELETE가 일어나도 이전 행 버전을 즉시 지우지 않습니다. 여러 트랜잭션이 각자의 시점에 맞는 데이터를 읽도록 하는 방식인 MVCC 때문입니다. 다른 트랜잭션에서 이전 버전을 볼 가능성이 남아 있으면 일반 VACUUM은 그 버전을 보존합니다. PostgreSQL 18 — 일상적인 VACUUM 관리

    장기 트랜잭션이 오래된 스냅샷을 유지하면 VACUUM이 테이블을 훑고 끝나도 일부 행 버전은 남을 수 있습니다. 스냅샷은 트랜잭션이 어느 시점의 데이터를 볼 수 있는지 정하는 기준입니다. 그래서 보고서용 읽기 세션도 오래된 스냅샷을 유지하고 있다면 점검 대상에 들어갑니다. PostgreSQL 18 — 유휴 트랜잭션 제한 시간

    스냅샷 이전 행 버전

    세션에서 트랜잭션 시작 시각과 xmin 확인

    문제가 있는 테이블의 데이터베이스에 연결한 뒤, 아래 조회를 자동 커밋 상태에서 실행합니다. xact_start는 트랜잭션 시작 시각이고, state_change는 현재 상태로 바뀐 시각입니다. idle in transaction 세션에서 두 값을 비교하면 트랜잭션 전체 경과 시간과 현재 유휴 상태의 지속 시간을 구분할 수 있습니다. 다른 계정의 세션 정보를 보려면 pg_read_all_stats 등 적절한 권한이 필요합니다. PostgreSQL 18 — 누적 통계와 세션 상태

    SELECT pid, usename, application_name, state,
           statement_timestamp() - xact_start AS xact_elapsed,
           state_change, backend_xid, backend_xmin,
           age(backend_xmin) AS xmin_age_in_xids
    FROM pg_stat_activity
    WHERE datname = current_database()
      AND backend_type = 'client backend'
      AND xact_start IS NOT NULL
      AND pid <> pg_backend_pid()
    ORDER BY xact_start;

    backend_xmin은 세션이 유지하는 현재 xmin 경계입니다. age(backend_xmin)은 트랜잭션 ID 기준 나이입니다. 경과 시간은 xact_elapsed에서 확인합니다. 시작 시각이 오래됐어도 backend_xmin이 비어 있을 수 있으므로, backend_xid도 함께 봅니다. 오래된 값이 잡히면 업무명과 시각을 기록해 VACUUM 결과와 대조할 후보로 둡니다. PostgreSQL 18 — 누적 통계와 세션 상태 · PostgreSQL 18 — 일상적인 VACUUM 관리

    테이블에서 VACUUM 실행과 정리 결과 대조

    아래 조회에서 public.orders를 실제 스키마와 테이블 이름으로 바꿉니다. last_autovacuum과 autovacuum_count는 실행 이력을, phase는 조회 순간의 진행 단계를 보여 줍니다. n_dead_tup은 추정치이므로, 새로 쌓인 행 버전과 VACUUM이 제거하지 못한 행 버전을 구분하려면 같은 테이블의 완료 로그가 필요합니다. PostgreSQL 18 — 누적 통계와 세션 상태

    SELECT s.last_autovacuum, s.autovacuum_count, s.n_dead_tup,
           s.n_tup_upd, s.n_tup_del,
           v.phase, v.heap_blks_scanned, v.heap_blks_total
    FROM pg_stat_user_tables AS s
    LEFT JOIN pg_stat_progress_vacuum AS v
           ON v.relid = s.relid
          AND v.datname = current_database()
    WHERE s.relid = to_regclass('public.orders');
    관찰 결과다음 확인
    last_autovacuum 갱신이 없고 n_dead_tup 증가자동 VACUUM 대상 기준과 테이블별 설정을 확인합니다. 로그가 남아 있다면 잠금 충돌로 건너뛴 기록도 찾습니다.
    phase에 진행 단계 표시phase와 heap_blks_scanned를 확인합니다. 블록 수는 scanning heap 단계에서만 증가하므로, 인덱스 정리 단계에서는 수치가 그대로일 수 있습니다.
    자동 VACUUM 완료 로그에 제거할 수 없었던 행 버전 표시같은 시간대의 오래된 backend_xmin·backend_xid와 대조합니다. 뚜렷한 세션이 없으면 오래된 준비 트랜잭션과 복제 슬롯의 xmin도 확인합니다.

    phase가 비어 있으면 조회 시점에 이 뷰에서 확인할 수 있는 진행 작업이 없는 상태입니다. 두 시점의 n_tup_upd, n_tup_del 차이로 그사이에 갱신·삭제가 얼마나 발생했는지 살펴봅니다. 완료 로그가 없다면 해당 시점의 log_autovacuum_min_duration 설정과 테이블별 재정의 여부를 확인합니다. PostgreSQL 18 — VACUUM 진행 상태 · PostgreSQL 18 — autovacuum 로그 설정 · PostgreSQL 18 — 일상적인 VACUUM 관리

    VACUUM 완료 기록 옆에 이전 행 버전이 남아 있는 테이블

    세션 종료 전 업무 확인과 재발 방지

    후보 세션을 찾으면 application_name과 접속 계정을 바탕으로 업무 담당자를 확인합니다. 진행 중인 업무와 미커밋 변경의 영향을 확인하고, 애플리케이션이 작업 결과에 맞춰 COMMIT 또는 ROLLBACK으로 트랜잭션을 끝내도록 합니다. 이후 같은 테이블의 다음 VACUUM 결과와 n_dead_tup 추이를 다시 확인합니다. PostgreSQL 18 — 일상적인 VACUUM 관리

    같은 세션이 반복해서 남으면 보고서 생성이나 외부 호출이 트랜잭션 안에서 대기하는지 살펴봅니다. idle_in_transaction_session_timeout을 적용할 때는 정상적인 대기 시간과 연결 풀의 재연결 동작을 검증한 뒤 대상 역할부터 적용합니다. 이 설정은 열린 트랜잭션에서 다음 요청을 기다리는 세션을 종료하므로, 실행 중인 긴 쿼리는 별도로 살펴야 합니다. PostgreSQL 18 — 유휴 트랜잭션 제한 시간

    결론

    점검 기록에는 테이블의 실행 이력, 완료 로그의 정리 결과, 같은 시간대 세션의 backend_xmin·backend_xid를 함께 남깁니다. 세션이 트랜잭션을 마친 뒤에도 같은 현상이 이어지면, 다음 VACUUM의 정리 결과를 확인하고 그 사이에 추가된 갱신·삭제량을 대조합니다.

    참고자료