PostgreSQL 오류: cannot merge attstreams with duplicate TIDs

반응형

PostgreSQL cannot merge attstreqms with duplicate TIDs

PostgreSQL 오류: cannot merge attstreams with duplicate TIDs — 원인·분석·조치

PostgreSQL 오류: cannot merge attstreams with duplicate TIDs — 원인·분석·조치

특정 테이블에서 쿼리/인덱스 작업/테이블 리라이트 시 cannot merge attstreams with duplicate TIDs 오류가 발생할 수 있습니다. 이는 TID(튜플 식별자) 충돌 또는 테이블 접근 방법(Access Method, AM)과 저장 포맷 간 불일치·손상으로 인해 열 스트림(TOAST/압축·컬럼 스트림 등)을 병합할 때 중복 TID가 검출되는 상황에서 보고됩니다.

핵심 요약
- 주로 비-heap AM(예: 실험적 columnar/zheap/서드파티 AM)로 생성된 테이블에서 발생 → heap AM로 전환 시 해결되는 사례 다수
- 인덱스/TOAST 손상·이상 포맷, 반쪽짜리 리라이트(중단)로도 재현 가능 → REINDEX / CTAS 리빌드 필요

1️⃣ 원인

  • 접근 방법(AM) 불일치: 확장/실험적 AM로 만들어진 테이블을 일반 연산(ANALYZE/VACUUM/인덱스 스캔/병합)이 처리하다가 내부 스트림 병합 로직과 충돌.
  • 중복 TID 검출: 잘못된 리라이트·복제·도중 중단으로 동일 TID를 가리키는 스트림이 생김(TOAST/압축 조각 포함).
  • 손상/부적합 인덱스: 인덱스가 손상되어 잘못된 TID를 반환 → 병합 시 중복으로 간주.

2️⃣ 증상/로그 예시

# 예시 (서버 로그)
ERROR:  cannot merge attstreams with duplicate TIDs
CONTEXT:  while reading column streams for relation "public.sales_hist"
STATEMENT: SELECT ... FROM public.sales_hist WHERE ...;

3️⃣ 진단 절차

  1. 테이블의 Access Method 확인
    SELECT c.relname, a.amname
    FROM pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    JOIN pg_am a ON a.oid = c.relam
    WHERE n.nspname = 'public' AND c.relname = '테이블명';

    결과가 heap이 아니라면(예: zheap, columnar 등) AM 전환 고려.

  2. TOAST/인덱스 상태 점검
    -- 인덱스 재구성 필요 여부
    SELECT relname, relkind FROM pg_class
    WHERE relkind IN ('i') AND relnamespace = 'public'::regnamespace
      AND relname LIKE '테이블명%';

    해당 인덱스들에 대해 REINDEX 수행 계획 수립.

  3. 최근 리라이트/확장 사용 여부 확인

    논리복제/확장 모듈/서드파티 AM 사용 이력, 중단된 VACUUM FULL/CLUSTER 로그 확인.

4️⃣ 조치 (권장 순서)

4-1. PostgreSQL 15 이상: AM 전환 (다운타임 필요)

주의: ALTER TABLE ... SET ACCESS METHOD는 테이블 리라이트를 수행합니다. 대용량 테이블은 충분한 디스크 공간점검 창 확보가 필요합니다.
BEGIN;
ALTER TABLE public."테이블명" SET ACCESS METHOD heap;
COMMIT;

-- 전환 후 필수 점검
VACUUM (FULL, ANALYZE) public."테이블명";
REINDEX TABLE public."테이블명";

4-2. PostgreSQL 12~14: CTAS 리빌드(대체 절차)

  1. 새 테이블(HEAP) 생성 후 데이터 이관
    CREATE TABLE public."테이블명_heap" (LIKE public."테이블명" INCLUDING ALL);
    -- 필요 시 제약조건/인덱스는 이후 재생성
    INSERT INTO public."테이블명_heap" SELECT * FROM public."테이블명";
  2. 인덱스·제약조건 재생성
    -- 예시
    CREATE INDEX ON public."테이블명_heap"(pk_col);
    ALTER TABLE public."테이블명_heap" ADD PRIMARY KEY (pk_col);
  3. 스왑(다운타임 구간)
    BEGIN;
    ALTER TABLE public."테이블명" RENAME TO "테이블명_bak";
    ALTER TABLE public."테이블명_heap" RENAME TO "테이블명";
    COMMIT;
  4. 검증 후 백업 테이블 삭제
    DROP TABLE public."테이블명_bak";

4-3. 인덱스/TOAST 손상 가능성 대응

-- 인덱스 재구성
REINDEX TABLE CONCURRENTLY public."테이블명";  -- 가능 시
-- 통계 갱신
ANALYZE public."테이블명";

4-4. 트랜잭션/복제 환경 주의

  • 논리복제: 리라이트 시 리플리카 지연/중단 유의. 점검 창에 맞춰 스왑.
  • 물리복제: 대용량 리라이트는 WAL 폭증 → 아카이브 공간 확보.

5️⃣ 결과 확인

-- 오류 재현 쿼리 재실행
EXPLAIN (ANALYZE, BUFFERS) SELECT ... FROM public."테이블명" ...;

-- 테이블·TOAST·인덱스 무결성 점검(샘플)
VACUUM VERBOSE public."테이블명";

에러가 재발하지 않고, 실행 계획이 정상화(Seq/Index Scan)되며, VACUUM에서 추가 경고가 없다면 복구 성공으로 판단합니다.

6️⃣ 예방 가이드

  • 표준 AM(HEAP) 우선: 실험적/서드파티 AM 도입 시는 성능·무결성 검증롤백 플랜 포함.
  • 리라이트 작업의 중단 방지: VACUUM FULL, CLUSTER, 대규모 ALTER TABLE 시 충분한 공간·시간 확보.
  • 주기적 REINDEX: 대형/핫 테이블은 REINDEX (CONCURRENTLY) 주기 운영으로 인덱스 일탈 방지.
  • 백업·리커버리 훈련: pg_dump + PITR 조합으로 신속 복구 가능한 절차 점검.
현장 Tip
운영 중 즉시성 요구가 크면: CTAS 스왑 방식이 가장 예측 가능하고 안전합니다. PostgreSQL 15+ 환경은 ALTER TABLE ... SET ACCESS METHOD heap가 단순하지만, 동일하게 리라이트이므로 WAL·디스크 여유를 반드시 확인하세요.
반응형
LIST