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

38000
2026년 06월 29일 | DBMS Error 가이드

이 글에서 다루는 내용

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

38000 external routine exception 는?

PostgreSQL 에러 코드 38000 (external routine exception) 은 데이터베이스 외부에서 실행되는 루틴, 즉 PL/Python, PL/Perl, PL/Java, PL/R 등과 같은 외부 언어로 작성된 함수나 프로시저가 실행 중 예외를 발생시킬 때 나타나는 에러입니다. 이 에러는 외부 언어 런타임 환경과 PostgreSQL 엔진 사이의 경계에서 처리되지 않은 예외가 발생했음을 의미합니다. 주로 외부 루틴 내부에서 발생하는 런타임 오류, 잘못된 데이터 처리, 또는 외부 라이브러리 호출 실패 등의 상황에서 발생합니다.


주요 발생 원인

  • 외부 언어 함수 내부의 처리되지 않은 예외 (PL/Python, PL/Perl 등)

외부 언어로 작성된 함수 내에서 예외가 발생했지만 해당 언어의 예외 처리 구문(try/except, eval 등)으로 잡히지 않고 PostgreSQL 레이어까지 전파될 때 이 에러가 발생합니다. 예를 들어, PL/Python 함수 내에서 0으로 나누기, None 타입에 대한 잘못된 연산, 또는 외부 모듈 임포트 실패 등이 이에 해당합니다. 이 경우 PostgreSQL은 외부 런타임에서 전달받은 예외 정보를 38000 코드로 감싸 클라이언트에게 반환합니다.

  • 잘못된 입력 데이터 또는 NULL 처리 미흡

외부 루틴이 기대하는 데이터 타입이나 값의 범위를 벗어난 입력이 전달될 때 발생합니다. 특히 NULL 값에 대한 처리가 누락된 외부 함수는 런타임에서 타입 오류나 참조 오류를 발생시켜 38000 에러로 이어질 수 있습니다. 실무에서는 대용량 데이터를 처리하는 배치 작업 중 예상치 못한 NULL 컬럼 값이 외부 함수에 전달되어 장애를 일으키는 사례가 빈번합니다.

  • 외부 라이브러리 또는 시스템 리소스 접근 실패

PL/Python이나 PL/Java 같은 외부 언어 함수가 내부적으로 파일 시스템, 네트워크 소켓, 또는 서드파티 라이브러리를 호출할 때 해당 리소스에 접근이 실패하면 38000 에러가 발생합니다. 예를 들어, 외부 API 호출 타임아웃, 파일 권한 문제, 또는 필요한 Python 패키지가 서버에 설치되지 않은 경우가 대표적입니다. 이런 환경 의존성 문제는 개발 환경과 운영 환경의 차이에서 특히 많이 발생합니다.


해결 방법

원인 1: 외부 언어 함수 내 예외 처리 추가

PL/Python 함수에 try/except 블록을 추가하여 예외가 PostgreSQL 레이어로 전파되지 않도록 처리합니다.

-- 문제가 있는 PL/Python 함수 (예외 처리 없음)
CREATE OR REPLACE FUNCTION calculate_ratio(numerator FLOAT, denominator FLOAT)
RETURNS FLOAT
LANGUAGE plpython3u
AS $$
    return numerator / denominator  -- denominator가 0이면 ZeroDivisionError 발생
$$;

-- 개선된 PL/Python 함수 (예외 처리 추가)
CREATE OR REPLACE FUNCTION calculate_ratio(numerator FLOAT, denominator FLOAT)
RETURNS FLOAT
LANGUAGE plpython3u
AS $$
    try:
        if denominator == 0:
            raise ValueError("Denominator cannot be zero")
        return numerator / denominator
    except ZeroDivisionError as e:
        plpy.error(f"Division error: {str(e)}")
    except Exception as e:
        plpy.warning(f"Unexpected error: {str(e)}")
        return None
$$;

-- 테스트
SELECT calculate_ratio(10.0, 2.0);   -- 정상: 5.0
SELECT calculate_ratio(10.0, 0.0);   -- 안전하게 에러 메시지 반환

원인 2: NULL 값 처리 강화

외부 함수 호출 전 SQL 레벨에서 NULL을 필터링하거나, 함수 내부에서 NULL 처리 로직을 추가합니다.

-- NULL 처리가 없는 위험한 함수
CREATE OR REPLACE FUNCTION process_text(input_text TEXT)
RETURNS TEXT
LANGUAGE plpython3u
AS $$
    return input_text.upper()  -- input_text가 None이면 AttributeError 발생
$$;

-- NULL 안전 처리가 추가된 함수
CREATE OR REPLACE FUNCTION process_text_safe(input_text TEXT)
RETURNS TEXT
LANGUAGE plpython3u
AS $$
    if input_text is None:
        return None
    try:
        return input_text.strip().upper()
    except AttributeError as e:
        plpy.warning(f"AttributeError: {str(e)}")
        return None
    except Exception as e:
        plpy.error(f"Unexpected error in process_text_safe: {str(e)}")
$$;

-- SQL 레벨에서도 NULLIF와 COALESCE로 방어
SELECT process_text_safe(COALESCE(user_input, '')) 
FROM user_data
WHERE user_input IS NOT NULL;

-- STRICT 옵션을 활용해 NULL 입력 시 자동으로 NULL 반환
CREATE OR REPLACE FUNCTION process_text_strict(input_text TEXT)
RETURNS TEXT
LANGUAGE plpython3u
STRICT  -- NULL 입력 시 함수 본체를 실행하지 않고 NULL 반환
AS $$
    return input_text.strip().upper()
$$;

원인 3: 외부 라이브러리 의존성 및 환경 검증

함수 실행 전 필요한 라이브러리가 설치되어 있는지 확인하고, 임포트 오류를 안전하게 처리합니다.

-- 외부 라이브러리를 사용하는 함수에 안전한 임포트 처리 추가
CREATE OR REPLACE FUNCTION parse_json_data(json_string TEXT)
RETURNS TEXT
LANGUAGE plpython3u
AS $$
    try:
        import json
    except ImportError as e:
        plpy.error(f"Required module 'json' is not available: {str(e)}")
        return None

    try:
        data = json.loads(json_string)
        return str(data.get('result', 'N/A'))
    except json.JSONDecodeError as e:
        plpy.warning(f"JSON parsing error: {str(e)}")
        return None
    except Exception as e:
        plpy.error(f"Unexpected error: {str(e)}")
$$;

-- 외부 함수 에러 발생 시 PostgreSQL DO 블록으로 디버깅
DO $$
BEGIN
    PERFORM parse_json_data('{"result": "test"}');
EXCEPTION
    WHEN external_routine_exception THEN
        RAISE NOTICE 'External routine error caught: %', SQLERRM;
    WHEN OTHERS THEN
        RAISE NOTICE 'Other error: % (SQLSTATE: %)', SQLERRM, SQLSTATE;
END;
$$;

-- 현재 설치된 언어 확인
SELECT lanname, lanpltrusted 
FROM pg_language 
WHERE lanname IN ('plpython3u', 'plperlu', 'pljava');

-- 외부 함수 목록 조회 및 점검
SELECT 
    n.nspname AS schema_name,
    p.proname AS function_name,
    l.lanname AS language,
    p.prosrc AS source_code
FROM pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
JOIN pg_language l ON p.prolang = l.oid
WHERE l.lanname IN ('plpython3u', 'plperlu', 'pljava', 'plr')
ORDER BY schema_name, function_name;

예방 방법

  • 외부 함수의 방어적 코딩 표준화 및 코드 리뷰 강화

모든 외부 언어 함수에 대해 반드시 예외 처리 블록을 포함하도록 개발 표준을 수립하고, 코드 리뷰 체크리스트에 예외 처리 여부를 필수 항목으로 추가해야 합니다. 특히 NULL 입력, 빈 문자열, 경계값 등 엣지 케이스에 대한 단위 테스트를 작성하고 배포 전에 반드시 실행해야 합니다. 가능하다면 STRICT 옵션을 활용하여 NULL 입력에 대한 기본 방어선을 구축하는 것도 좋은 방법입니다.

  • 운영 환경과 개발 환경의 라이브러리 버전 동기화 및 모니터링

외부 언어 함수가 의존하는 라이브러리 목록을 문서화하고, 개발/스테이징/운영 환경 간의 버전 일치 여부를 주기적으로 확인해야 합니다. PostgreSQL 로그에서 38000 에러를 모니터링하는 알림 시스템을 구축하고, pg_stat_activitypg_log를 통해 외부 루틴 실행 현황을 추적하는 것을 권장합니다. 또한 외부 함수의 실행 시간과 성공/실패 통계를 별도 테이블에 기록하는 래퍼 함수를 사용하면 장애 분석에 큰 도움이 됩니다.


관련 에러

  • 39000 (external routine invocation exception): 외부 루틴 호출 자체의 문제로 발생하며, 38000과 유사하지만 호출 계층에서의 예외를 나타냅니다.
  • 39001 (invalid sqlstate returned): 외부 루틴이 유효하지 않은 SQLSTATE를 반환할 때 발생합니다.
  • 2F000 (sql routine exception): SQL로 작성된 함수에서 발생하는 예외로, 외부 언어가 아닌 SQL 함수의 에러에 해당합니다.
  • 58000 (system error): 외부 루틴 실행 중 시스템 레벨 오류가 발생할 때 함께 나타날 수 있습니다.
  • XX000 (internal error): 외부 언어 런타임과의 인터페이스에서 예상치 못한 내부 오류가 발생할 때 관련될 수 있습니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기