PostgreSQL 39P02 오류 원인과 해결 방법 완벽 가이드

39P02
2026년 09월 04일 | DBMS Error 가이드

이 글에서 다루는 내용

39P02 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.

39P02 srf protocol violated 는?

PostgreSQL 에러 코드 39P02: srf protocol violated는 Set-Returning Function(SRF), 즉 집합 반환 함수가 내부 프로토콜을 올바르게 따르지 않았을 때 발생하는 에러입니다. SRF는 단일 값이 아닌 여러 행(row)을 반환하도록 설계된 함수인데, 이 함수의 호출 및 반환 과정에서 PostgreSQL 내부 프로토콜 규약이 위반될 경우 이 에러가 트리거됩니다. 주로 C 언어로 작성된 커스텀 확장 함수, PL/pgSQL 내부의 잘못된 SRF 구현, 또는 PostgreSQL 버전 간 호환성 문제가 있는 확장 모듈에서 자주 발생합니다.


주요 발생 원인

1. C 언어 확장 함수에서의 잘못된 SRF 구현

PostgreSQL에서 C 언어로 Set-Returning Function을 구현할 때는 SRF_IS_FIRSTCALL(), SRF_FIRSTCALL_INIT(), SRF_PERCALL_SETUP(), SRF_RETURN_NEXT(), SRF_RETURN_DONE() 매크로를 정해진 순서와 규칙에 따라 반드시 사용해야 합니다. 이 매크로 중 하나라도 누락되거나 잘못된 순서로 호출되면 srf protocol violated 에러가 발생합니다. 특히 SRF_RETURN_DONE()을 호출하지 않고 함수를 종료하거나, FuncCallContext를 올바르게 초기화하지 않은 경우가 대표적인 원인입니다.

2. PostgreSQL 버전 업그레이드 후 호환되지 않는 확장 모듈

메이저 버전 업그레이드(예: PostgreSQL 13 → 14 또는 14 → 15) 이후에 기존에 설치된 C 확장 모듈을 재컴파일하지 않고 그대로 사용하는 경우 이 에러가 발생할 수 있습니다. PostgreSQL의 내부 ABI(Application Binary Interface)가 버전마다 달라질 수 있으며, 구버전 바이너리로 컴파일된 SRF 함수는 신버전 서버에서 프로토콜 위반으로 인식됩니다. 서드파티 확장(PostGIS, TimescaleDB 등)도 반드시 현재 PostgreSQL 버전에 맞는 버전으로 재설치해야 합니다.

3. PL/pgSQL 또는 PL/Python 등 절차적 언어에서의 잘못된 RETURNS SETOF 구현

RETURNS SETOF 또는 RETURNS TABLE을 사용하는 함수에서 RETURN NEXT 또는 RETURN QUERY를 올바르게 사용하지 않았을 때 SRF 프로토콜 위반이 발생할 수 있습니다. 예를 들어 PL/Python이나 PL/Perl로 작성된 함수에서 제너레이터(generator) 패턴을 잘못 구현하거나, 결과 집합을 반환하는 도중 예외가 발생하여 정상적인 종료 시퀀스가 실행되지 않은 경우에도 이 에러가 트리거됩니다. 또한 함수 내에서 트랜잭션 제어를 잘못 사용하는 경우도 원인이 될 수 있습니다.


해결 방법

원인 1: C 확장 함수의 SRF 매크로 올바르게 사용하기

C로 SRF를 구현할 때는 아래 패턴을 정확하게 따라야 합니다.

-- 먼저 C 확장 함수가 올바르게 등록되었는지 확인
SELECT proname, prosrc, prolang
FROM pg_proc
WHERE proname = 'your_srf_function_name';

-- 문제가 있는 함수를 제거하고 재등록
DROP FUNCTION IF EXISTS your_srf_function_name();

-- 올바르게 재컴파일된 SO 파일로 함수 재생성
CREATE OR REPLACE FUNCTION your_srf_function_name()
RETURNS SETOF TEXT
AS '/path/to/your_extension.so', 'your_srf_function_name'
LANGUAGE C STRICT;

C 코드 내에서의 올바른 SRF 패턴은 다음과 같습니다 (참고용 의사 코드):

-- SRF 함수를 대체하는 PL/pgSQL 버전으로 임시 교체 (C 수정 전 임시 방편)
CREATE OR REPLACE FUNCTION your_srf_function_name()
RETURNS SETOF TEXT
LANGUAGE plpgsql
AS $$
DECLARE
    v_row TEXT;
BEGIN
    FOR v_row IN SELECT unnest(ARRAY['value1', 'value2', 'value3'])
    LOOP
        RETURN NEXT v_row;
    END LOOP;
    RETURN;
END;
$$;

원인 2: 버전 업그레이드 후 확장 모듈 재설치

-- 현재 설치된 확장 목록 및 버전 확인
SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE installed_version IS NOT NULL
ORDER BY name;

-- 특정 확장의 상세 정보 확인
SELECT extname, extversion, extrelocatable
FROM pg_extension
WHERE extname = 'your_extension_name';

-- 확장 업데이트 시도
ALTER EXTENSION your_extension_name UPDATE;

-- 만약 업데이트가 불가능하다면 재설치
DROP EXTENSION IF EXISTS your_extension_name CASCADE;
-- (OS 레벨에서 새 버전 패키지 설치 후)
CREATE EXTENSION your_extension_name;

-- PostgreSQL 서버 버전과 확장 컴파일 버전 불일치 확인
SELECT version();
-- 위 결과와 확장 모듈의 컴파일 버전을 대조

원인 3: PL/pgSQL RETURNS SETOF 함수 수정

-- 문제가 있는 패턴 (잘못된 예)
CREATE OR REPLACE FUNCTION bad_srf_example()
RETURNS SETOF INTEGER
LANGUAGE plpgsql
AS $$
BEGIN
    -- RETURN NEXT 없이 그냥 반환하려는 잘못된 시도
    RETURN 1; -- 이렇게 하면 안 됨
END;
$$;

-- 올바른 패턴 1: RETURN NEXT 사용
CREATE OR REPLACE FUNCTION good_srf_example_v1()
RETURNS SETOF INTEGER
LANGUAGE plpgsql
AS $$
DECLARE
    i INTEGER;
BEGIN
    FOR i IN 1..10 LOOP
        RETURN NEXT i;  -- 각 행을 하나씩 반환
    END LOOP;
    RETURN;  -- 명시적 종료
END;
$$;

-- 올바른 패턴 2: RETURN QUERY 사용
CREATE OR REPLACE FUNCTION good_srf_example_v2(p_limit INT DEFAULT 10)
RETURNS SETOF pg_stat_activity
LANGUAGE plpgsql
AS $$
BEGIN
    RETURN QUERY
        SELECT *
        FROM pg_stat_activity
        LIMIT p_limit;
END;
$$;

-- 올바른 패턴 3: RETURNS TABLE 사용
CREATE OR REPLACE FUNCTION good_srf_example_v3(p_schema TEXT)
RETURNS TABLE(table_name TEXT, row_count BIGINT)
LANGUAGE plpgsql
AS $$
DECLARE
    v_table RECORD;
    v_count BIGINT;
BEGIN
    FOR v_table IN
        SELECT tablename
        FROM pg_tables
        WHERE schemaname = p_schema
    LOOP
        EXECUTE format('SELECT COUNT(*) FROM %I.%I',
                       p_schema, v_table.tablename)
        INTO v_count;

        table_name := v_table.tablename;
        row_count  := v_count;
        RETURN NEXT;
    END LOOP;
END;
$$;

-- 함수 테스트
SELECT * FROM good_srf_example_v1();
SELECT * FROM good_srf_example_v3('public');

에러 발생 함수 진단 쿼리

-- SRF 관련 함수 목록 조회 (proretset = true인 함수)
SELECT
    n.nspname   AS schema_name,
    p.proname   AS function_name,
    l.lanname   AS language,
    p.proretset AS is_set_returning,
    p.prosrc    AS source_preview
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
JOIN pg_language l  ON p.prolang = l.oid
WHERE p.proretset = true
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY n.nspname, p.proname;

-- C 언어로 작성된 SRF 함수 확인 (위험도 높음)
SELECT
    n.nspname   AS schema_name,
    p.proname   AS function_name,
    p.probin    AS shared_library,
    p.prosrc    AS c_function_name
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
JOIN pg_language l  ON p.prolang = l.oid
WHERE p.proretset = true
  AND l.lanname = 'c'
  AND n.nspname NOT IN ('pg_catalog', 'information_schema');

예방 방법

1. PostgreSQL 버전 업그레이드 시 확장 모듈 호환성 사전 검증

메이저 버전 업그레이드를 수행하기 전에 반드시 테스트 환경에서 모든 C 확장 및 서드파티 확장을 새 버전으로 재컴파일하고 동작을 검증해야 합니다. pg_upgrade 도구를 사용할 경우에도 --check 옵션으로 사전 검사를 수행하고, 업그레이드 후에는 ALTER EXTENSION ... UPDATE 명령으로 모든 확장을 최신 상태로 유지하십시오. CI/CD 파이프라인에 SRF 함수들의 회귀 테스트(regression test)를 포함시켜 배포 전에 자동으로 검증되도록 구성하는 것이 좋습니다.

2. SRF 함수 작성 시 코드 리뷰 및 표준 패턴 준수

팀 내에서 SRF 함수를 작성할 때는 반드시 RETURN NEXT / RETURN QUERY / RETURN(종료)의 올바른 사용 패턴을 문서화하고, 코드 리뷰 단계에서 이를 확인하는 체크리스트를 운영하십시오. 특히 예외 처리(EXCEPTION 블록) 내에서 SRF의 상태가 올바르게 종료되는지 반드시 확인하고, 가능하다면 C 언어 대신 PL/pgSQL 또는 SQL 함수로 대체하여 프로토콜 위반 가능성 자체를 줄이는 것이 장기적으로 안전합니다.


관련 에러

  • 39P01: invalid transaction termination: SRF 함수 내에서 트랜잭션을 부적절하게 종료하려 할 때 발생하며, 39P02와 유사한 맥락에서 나타납니다.
  • 42P13: invalid_function_definition: 함수 정의 자체가 잘못된 경우 발생하며, SRF 프로토콜 위반의 근본 원인이 함수 정의 오류에서 비롯된 경우 함께 확인해야 합니다.
  • XX000: internal error: C 확장의 심각한 내부 오류로, 39P02 이전 또는 이후에 함께 발생할 수 있습니다. PostgreSQL 서버 로그에서 함께 확인하는 것이 중요합니다.
  • 0A000: feature_not_supported: 특정 PL 언어에서 SRF를 지원하지 않는 방식으로 사용하려 할 때 발생할 수 있습니다.
DBMS 에러 코드 시리즈

주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.

본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.

댓글 남기기