2026년 06월 01일 | DBMS Error 가이드
이 글에서 다루는 내용
0F001 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
0F001 invalid locator specification 는?
PostgreSQL 에러 코드 0F001 invalid locator specification은 LOB(Large Object Binary) 로케이터(locator)가 유효하지 않거나 잘못된 방식으로 사용될 때 발생하는 에러입니다. 이 에러는 주로 Large Object(대용량 객체)를 다루는 함수(lo_read, lo_write, lo_lseek, lo_close 등)에서 잘못된 파일 디스크립터(fd) 또는 이미 닫힌 LOB 핸들을 참조할 때 트리거됩니다. 실무에서는 트랜잭션 외부에서 Large Object 작업을 수행하거나, 세션 간에 LOB 핸들을 공유하려 할 때 자주 목격됩니다.
주요 발생 원인
1. 이미 닫혔거나 유효하지 않은 Large Object 디스크립터 사용
lo_open()으로 열었던 Large Object 디스크립터가 lo_close()로 닫힌 이후에 해당 디스크립터를 다시 참조하면 이 에러가 발생합니다. 특히 복잡한 로직에서 조건 분기로 인해 디스크립터가 중간에 닫혔음에도 불구하고 이후 코드에서 계속 사용하는 실수가 빈번합니다. 한 번 닫힌 디스크립터는 재사용할 수 없으며, 반드시 새로 lo_open()을 호출해야 합니다.
2. 트랜잭션 외부에서 Large Object 함수 호출
PostgreSQL의 Large Object 함수들은 반드시 활성 트랜잭션 내에서 호출되어야 합니다. BEGIN 없이 autocommit 모드에서 lo_open() → lo_read() 순서로 호출하면, 각 명령이 별도 트랜잭션으로 처리되어 두 번째 호출 시 로케이터가 이미 무효화된 상태가 됩니다. 이 원인은 특히 psycopg2, JDBC 등의 드라이버를 통해 프로그래밍 방식으로 접근할 때 자주 발생합니다.
3. 잘못된 OID 또는 존재하지 않는 Large Object 참조
lo_open() 호출 시 pg_largeobject 카탈로그에 존재하지 않는 OID를 전달하거나, 이미 lo_unlink()로 삭제된 Large Object의 OID를 사용하면 유효하지 않은 로케이터가 생성됩니다. 이후 해당 로케이터를 통해 읽기/쓰기를 시도하면 0F001 에러로 이어집니다. 데이터 마이그레이션이나 정리 작업 이후에 OID 참조 테이블이 최신화되지 않은 경우 특히 주의해야 합니다.
해결 방법
원인 1 해결: 디스크립터 상태 관리
닫힌 디스크립터를 재사용하지 않도록 작업 단위마다 새로 open/close 사이클을 구성하세요.
-- 잘못된 예: lo_close 이후 같은 fd 재사용
DO $$
DECLARE
loid OID;
fd INTEGER;
buf BYTEA;
BEGIN
loid := lo_create(0);
fd := lo_open(loid, 131072); -- INV_WRITE
PERFORM lo_write(fd, 'Hello Large Object');
PERFORM lo_close(fd);
-- 에러 발생: 이미 닫힌 fd를 재사용
-- buf := loread(fd, 100); <-- 0F001 발생!
-- 올바른 방법: 다시 open
fd := lo_open(loid, 262144); -- INV_READ
buf := loread(fd, 100);
RAISE NOTICE 'Read: %', buf;
PERFORM lo_close(fd);
END;
$$;
원인 2 해결: 명시적 트랜잭션 사용
Large Object 작업 전체를 반드시 하나의 트랜잭션으로 감싸야 합니다.
-- 올바른 트랜잭션 구조
BEGIN;
DO $$
DECLARE
loid OID;
fd INTEGER;
data BYTEA;
BEGIN
-- Large Object 생성
loid := lo_create(0);
RAISE NOTICE 'Created LO with OID: %', loid;
-- 쓰기 모드로 열기 (INV_WRITE = 131072)
fd := lo_open(loid, 131072);
PERFORM lo_write(fd, convert_to('PostgreSQL LO Test Data', 'UTF8'));
PERFORM lo_close(fd);
-- 읽기 모드로 열기 (INV_READ = 262144)
fd := lo_open(loid, 262144);
data := loread(fd, 1024);
RAISE NOTICE 'Data: %', convert_from(data, 'UTF8');
PERFORM lo_close(fd);
-- 정리
PERFORM lo_unlink(loid);
END;
$$;
COMMIT;
원인 3 해결: OID 유효성 검사 후 사용
Large Object를 열기 전에 반드시 해당 OID가 pg_largeobject_metadata에 존재하는지 확인하세요.
-- OID 유효성 검사 함수
CREATE OR REPLACE FUNCTION safe_lo_read(p_loid OID, p_length INTEGER)
RETURNS BYTEA AS $$
DECLARE
v_fd INTEGER;
v_data BYTEA;
v_exists BOOLEAN;
BEGIN
-- OID 존재 여부 확인
SELECT EXISTS (
SELECT 1
FROM pg_largeobject_metadata
WHERE oid = p_loid
) INTO v_exists;
IF NOT v_exists THEN
RAISE EXCEPTION 'Large Object OID % does not exist. Check if it was deleted.', p_loid
USING ERRCODE = '0F001';
END IF;
-- 안전하게 열기
v_fd := lo_open(p_loid, 262144); -- INV_READ
v_data := loread(v_fd, p_length);
PERFORM lo_close(v_fd);
RETURN v_data;
EXCEPTION
WHEN OTHERS THEN
-- fd가 열려있을 경우 정리 시도
BEGIN
PERFORM lo_close(v_fd);
EXCEPTION WHEN OTHERS THEN
NULL; -- 이미 닫혔으면 무시
END;
RAISE;
END;
$$ LANGUAGE plpgsql;
-- 사용 예시
BEGIN;
SELECT safe_lo_read(12345::OID, 4096);
COMMIT;
고아(Orphan) Large Object 정리
-- pg_largeobject에는 있지만 참조 테이블에 없는 고아 LO 탐지
SELECT lo.oid
FROM pg_largeobject_metadata lo
LEFT JOIN your_table t ON t.lo_oid_column = lo.oid
WHERE t.lo_oid_column IS NULL;
-- vacuumlo 명령으로 고아 LO 일괄 정리 (psql 외부 도구)
-- $ vacuumlo -v your_database
예방 방법
1. Large Object 작업은 반드시 트랜잭션으로 래핑하고, 래퍼 함수를 표준화하세요
팀 내에서 Large Object를 다루는 공통 PL/pgSQL 함수를 만들어 두고, 모든 애플리케이션에서 해당 함수만 호출하도록 규칙을 정하세요. 직접 lo_open / lo_close를 여기저기서 호출하면 디스크립터 관리 실수가 생기기 쉽습니다. 아래처럼 BEGIN ... EXCEPTION ... END 블록을 활용하여 예외 상황에서도 반드시 lo_close가 호출되도록 보장하세요.
-- 안전한 래퍼 패턴
CREATE OR REPLACE FUNCTION lo_safe_operation(p_loid OID)
RETURNS VOID AS $$
DECLARE
v_fd INTEGER := -1;
BEGIN
v_fd := lo_open(p_loid, 262144);
-- ... 작업 수행 ...
PERFORM lo_close(v_fd);
v_fd := -1;
EXCEPTION
WHEN OTHERS THEN
IF v_fd != -1 THEN
PERFORM lo_close(v_fd);
END IF;
RAISE;
END;
$$ LANGUAGE plpgsql;
2. vacuumlo를 정기적으로 실행하고 LO OID 참조 무결성을 관리하세요
애플리케이션 테이블에서 Large Object OID를 컬럼으로 관리할 때는 해당 컬럼에 외래 키 대신 트리거를 통한 참조 무결성 검사를 구현하고, 배치 작업이나 데이터 삭제 시 lo_unlink()를 반드시 호출하도록 프로세스를 수립하세요. pg_cron 등을 활용해 주기적으로 vacuumlo를 실행하면 고아 Large Object 누적으로 인한 스토리지 낭비와 잠재적 에러를 예방할 수 있습니다.
관련 에러
58030(io_error): 디스크 I/O 문제로 Large Object 읽기/쓰기 자체가 실패할 때 발생하며,0F001과 혼동될 수 있습니다.42704(undefined_object): 존재하지 않는 객체를 참조할 때 발생하며, 잘못된 OID로 Large Object 접근 시0F001이전에 나타날 수 있습니다.55000(object_not_in_prerequisite_state): 트랜잭션 상태 문제로 Large Object 연산이 불가능할 때 발생하며,0F001과 유사한 문맥에서 등장합니다.0F000(locator_exception):0F001의 상위 에러 클래스로, 로케이터 관련 에러의 일반적인 분류입니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.