2026년 07월 01일 | DBMS Error 가이드
이 글에서 다루는 내용
39P02 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
39P02 srf protocol violated 는?
39P02: srf protocol violated 에러는 PostgreSQL에서 Set-Returning Function(SRF), 즉 집합 반환 함수가 내부 프로토콜 규약을 위반했을 때 발생하는 에러입니다. SRF는 RETURNS SETOF 또는 RETURNS TABLE로 선언된 함수로, 여러 행을 반환할 수 있는 특수한 함수 유형입니다. 이 에러는 주로 PL/pgSQL 또는 C 언어로 작성된 함수가 SRF의 내부 상태 머신(state machine) 규칙을 준수하지 않거나, 반환 시퀀스가 예상된 순서를 벗어날 때 트리거됩니다.
주요 발생 원인
- PL/pgSQL 함수에서 RETURN NEXT / RETURN QUERY의 잘못된 사용
RETURNS SETOF 또는 RETURNS TABLE로 선언된 함수 내부에서 RETURN NEXT나 RETURN QUERY를 올바르지 않은 위치에서 호출하거나, 반환 타입과 실제 반환 데이터 구조가 일치하지 않을 때 이 에러가 발생합니다. 예를 들어 단일 값 반환 함수 안에서 RETURN NEXT를 사용하거나, SRF 컨텍스트 외부에서 SRF 전용 지시어를 호출하는 경우가 대표적입니다. 이는 PostgreSQL의 SRF 내부 프로토콜 상태가 초기화되지 않은 상태에서 진행 신호를 보내는 것과 같아 프로토콜 위반으로 간주됩니다.
- C 언어 확장 함수에서 SRF API의 부적절한 구현
PostgreSQL C API를 사용하여 SRF를 구현할 때, SRF_IS_FIRSTCALL(), SRF_FIRSTCALL_INIT(), SRF_PERCALL_SETUP(), SRF_RETURN_NEXT(), SRF_RETURN_DONE() 매크로를 올바른 순서와 조건에 맞게 사용하지 않으면 이 에러가 발생합니다. 특히 첫 번째 호출 여부를 확인하지 않고 SRF_PERCALL_SETUP()을 바로 호출하거나, 함수 컨텍스트(FuncCallContext)를 잘못 관리하는 경우 프로토콜 위반이 발생합니다. 이는 주로 서드파티 PostgreSQL 확장 또는 오래된 플러그인에서 자주 나타나는 문제입니다.
- 함수 중첩 호출 또는 트리거 컨텍스트에서의 SRF 사용
SRF를 트리거 함수 내부에서 직접 호출하거나, SRF가 지원되지 않는 특정 컨텍스트(예: 집계 함수 내부, 특정 WHERE 절의 서브쿼리)에서 사용하면 프로토콜 위반 에러가 발생할 수 있습니다. PostgreSQL은 SRF를 호출할 수 있는 위치에 엄격한 제한을 두고 있으며, 잘못된 컨텍스트에서의 호출은 내부 상태 관리 오류로 이어집니다. 특히 PostgreSQL 10 이전 버전에서는 SELECT 목록 이외의 위치에서 SRF를 사용할 때 이러한 문제가 더 빈번하게 발생했습니다.
해결 방법
원인 1 해결: RETURN NEXT / RETURN QUERY 올바르게 사용하기
잘못된 예시:
-- 잘못된 예: 단일 값 반환 함수에서 RETURN NEXT 사용
CREATE OR REPLACE FUNCTION bad_srf_example()
RETURNS INTEGER AS $$
BEGIN
RETURN NEXT 1; -- 오류! 단일 값 반환 함수에서 RETURN NEXT 사용 불가
RETURN NEXT 2;
END;
$$ LANGUAGE plpgsql;
올바른 예시:
-- 올바른 예: RETURNS SETOF 선언 후 RETURN NEXT 사용
CREATE OR REPLACE FUNCTION correct_srf_example()
RETURNS SETOF INTEGER AS $$
BEGIN
RETURN NEXT 1;
RETURN NEXT 2;
RETURN NEXT 3;
RETURN; -- 함수 종료
END;
$$ LANGUAGE plpgsql;
-- 호출 방법
SELECT * FROM correct_srf_example();
RETURN QUERY를 활용한 올바른 SRF 구현:
-- RETURNS TABLE을 활용한 SRF 구현
CREATE OR REPLACE FUNCTION get_active_users(min_age INT)
RETURNS TABLE(
user_id INT,
username TEXT,
age INT
) AS $$
BEGIN
RETURN QUERY
SELECT u.id, u.name, u.age
FROM users u
WHERE u.age >= min_age
AND u.is_active = TRUE
ORDER BY u.age DESC;
END;
$$ LANGUAGE plpgsql;
-- 호출 방법
SELECT * FROM get_active_users(25);
원인 2 해결: C 확장 함수의 SRF API 올바르게 구현하기
C 언어 확장에서 SRF를 구현할 때는 아래 패턴을 반드시 준수해야 합니다:
-- C 확장 함수가 올바르게 구현된 경우 SQL에서 이렇게 선언합니다
CREATE OR REPLACE FUNCTION my_c_srf_function()
RETURNS SETOF TEXT
AS 'my_extension', 'my_c_srf_function'
LANGUAGE C STRICT;
-- C 코드 내부에서는 반드시 아래 순서를 따릅니다:
-- 1. SRF_IS_FIRSTCALL() 확인
-- 2. SRF_FIRSTCALL_INIT() 로 컨텍스트 초기화
-- 3. SRF_PERCALL_SETUP() 으로 각 호출마다 컨텍스트 설정
-- 4. 데이터가 있으면 SRF_RETURN_NEXT(), 없으면 SRF_RETURN_DONE()
원인 3 해결: 올바른 컨텍스트에서 SRF 호출하기
-- 잘못된 예: WHERE 절에서 직접 SRF 호출 (구버전에서 문제 발생)
-- SELECT * FROM orders WHERE generate_series(1,5) = order_id;
-- 올바른 예: FROM 절 또는 LATERAL 조인 사용
SELECT o.*
FROM orders o
JOIN LATERAL generate_series(1, 5) AS gs(n)
ON o.order_id = gs.n;
-- LATERAL을 활용한 SRF 안전 호출
SELECT u.username, logs.log_entry
FROM users u
CROSS JOIN LATERAL get_user_logs(u.user_id) AS logs(log_entry);
-- 집계 함수와 SRF를 함께 사용할 때는 서브쿼리로 분리
SELECT COUNT(*), AVG(val)
FROM (
SELECT * FROM generate_series(1, 100) AS gs(val)
) subq;
예방 방법
- 함수 선언 타입과 반환 지시어의 일관성 유지
SRF 함수를 작성할 때는 함수 선언부(RETURNS SETOF, RETURNS TABLE)와 함수 본문의 반환 지시어(RETURN NEXT, RETURN QUERY)가 반드시 일치하도록 코드 리뷰 체크리스트를 운영하세요. CI/CD 파이프라인에 pg_dump 기반의 함수 유효성 검사 단계를 추가하거나, pgTAP을 사용한 단위 테스트를 작성하여 배포 전에 함수 동작을 검증하는 것을 강력히 권장합니다.
“`sql
— pgTAP을 활용한 SRF 함수 테스트 예시
SELECT results_eq(
‘SELECT * FROM correct_srf_example()’,
ARRAY[1, 2, 3]::INTEGER[],
‘SRF 함수가 올바른 값을 반환해야 합니다’
);
“`
- PostgreSQL 버전 호환성 확인 및 정기적인 함수 감사(Audit)
PostgreSQL 버전이 업그레이드될 때 SRF 관련 동작이 변경될 수 있으므로, 주요 버전 업그레이드 전에 반드시 테스트 환경에서 모든 SRF 함수를 검증하세요. 아래 쿼리로 데이터베이스 내 모든 SRF 함수를 추출하여 주기적으로 점검하는 습관을 가지세요.
“`sql
— 데이터베이스 내 모든 SRF 함수 조회
SELECT
n.nspname AS schema_name,
p.proname AS function_name,
pg_get_function_result(p.oid) AS return_type,
p.prolang::REGPROCEDURE AS language
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE p.proretset = TRUE — SRF 함수만 필터링
AND n.nspname NOT IN (‘pg_catalog’, ‘information_schema’)
ORDER BY n.nspname, p.proname;
“`
관련 에러
0A000(feature_not_supported): SRF가 지원되지 않는 컨텍스트에서 호출될 때 발생하며,39P02와 함께 연쇄적으로 나타날 수 있습니다.42P13(invalid_function_definition): 함수 정의 자체가 잘못되었을 때 발생하며, SRF 선언 오류의 선행 에러로 나타납니다.54001(statement_too_complex): 복잡한 SRF 중첩 구조에서 플래너가 처리 한계를 초과할 때 발생할 수 있습니다.XX000(internal_error): C 확장 SRF의 심각한 구현 오류 시39P02대신 이 에러가 발생하는 경우도 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.