2026년 10월 22일 | DBMS Error 가이드
이 글에서 다루는 내용
22P03 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
22P03 invalid binary representation 는?
PostgreSQL 에러 코드 22P03 invalid binary representation은 바이너리 형식으로 전달된 데이터가 해당 데이터 타입의 유효한 내부 표현 형식과 일치하지 않을 때 발생합니다. 주로 클라이언트 드라이버가 COPY BINARY 명령, prepared statement의 바이너리 프로토콜, 또는 외부 시스템에서 데이터를 가져올 때 잘못된 바이트 시퀀스나 손상된 데이터를 전달하는 경우에 나타납니다. 이 에러는 텍스트 형식 전송이 아닌 바이너리 형식 전송에서만 발생하므로, 드라이버 수준의 직렬화 문제 혹은 버전 불일치에서 비롯되는 경우가 많습니다.
주요 발생 원인
1. COPY BINARY 명령으로 잘못된 바이너리 데이터 로드
COPY ... WITH (FORMAT BINARY) 명령을 사용하여 외부 파일이나 스트림에서 데이터를 가져올 때, 파일이 PostgreSQL 바이너리 포맷 스펙을 따르지 않으면 이 에러가 발생합니다. 예를 들어 파일 헤더가 손상되었거나, 엔디안(Endian) 처리가 잘못되어 있거나, 해당 데이터 타입의 내부 바이너리 구조가 서버 버전과 맞지 않을 때 주로 발생합니다. PostgreSQL 바이너리 COPY 포맷은 버전 간에 완벽히 호환되지 않는 경우가 있으므로 특히 주의가 필요합니다.
2. 클라이언트 드라이버의 바이너리 프로토콜 직렬화 오류
JDBC, libpq, psycopg2 등의 드라이버가 Extended Query Protocol(바이너리 모드)로 데이터를 전송할 때, 클라이언트 측에서 잘못된 바이트 순서나 잘못된 타입 OID로 데이터를 인코딩하면 이 에러가 발생합니다. 특히 UUID, numeric, timestamp with time zone, 배열(Array), 복합 타입(Composite Type) 등의 복잡한 데이터 타입에서 이 문제가 빈번하게 나타납니다. 드라이버의 버전과 PostgreSQL 서버 버전 간의 불일치가 주요 원인이 되기도 합니다.
3. pg_restore 또는 논리적 복제 시 타입 불일치
pg_dump/pg_restore로 서로 다른 메이저 버전 간 데이터를 이전하거나, 논리적 복제(Logical Replication)를 통해 데이터를 전달할 때, 특정 데이터 타입의 내부 바이너리 표현 방식이 버전마다 달라서 에러가 발생할 수 있습니다. 이 경우 바이너리 포맷 대신 텍스트 포맷으로 덤프/복원하면 대부분 해결됩니다. 특히 geometry, hstore, jsonb 와 같은 확장 타입에서 이 문제가 두드러집니다.
해결 방법
원인 1: COPY BINARY 문제 해결
바이너리 COPY 대신 텍스트 또는 CSV 포맷을 사용하여 데이터를 가져오는 것이 가장 안전한 방법입니다.
-- 잘못된 방법: 바이너리 포맷 사용 (에러 발생 가능)
COPY orders FROM '/data/orders.bin' WITH (FORMAT BINARY);
-- 권장 방법: 텍스트 또는 CSV 포맷 사용
COPY orders FROM '/data/orders.csv' WITH (FORMAT CSV, HEADER true);
-- 또는 텍스트 포맷
COPY orders FROM '/data/orders.txt' WITH (FORMAT TEXT, DELIMITER E'\t');
바이너리 파일의 헤더를 확인하려면 아래처럼 psql에서 직접 검증할 수 있습니다:
-- 임시 테이블에 먼저 텍스트로 가져온 뒤 타입 캐스팅
CREATE TEMP TABLE orders_staging (
order_id TEXT,
amount TEXT,
created_at TEXT
);
COPY orders_staging FROM '/data/orders.csv' WITH (FORMAT CSV, HEADER true);
-- 정제 후 실제 테이블로 INSERT
INSERT INTO orders (order_id, amount, created_at)
SELECT
order_id::INTEGER,
amount::NUMERIC(15,2),
created_at::TIMESTAMPTZ
FROM orders_staging
WHERE order_id ~ '^\d+$'; -- 유효한 데이터만 선택
원인 2: 드라이버 바이너리 프로토콜 문제 해결
드라이버에서 바이너리 모드를 비활성화하거나, 문제가 되는 타입을 텍스트로 캐스팅하여 전송합니다.
-- UUID 타입을 텍스트로 명시적 캐스팅 후 조회 (드라이버 바이너리 변환 우회)
SELECT
id::TEXT AS id,
user_uuid::TEXT AS user_uuid,
payload::TEXT AS payload
FROM events
WHERE created_at > NOW() - INTERVAL '1 day';
-- Numeric 타입의 바이너리 표현 문제 우회
SELECT
price::TEXT::NUMERIC AS price
FROM products;
-- 배열 타입의 바이너리 직렬화 문제 우회
SELECT
ARRAY_TO_STRING(tags, ',') AS tags_text
FROM articles;
Java JDBC를 사용하는 경우, 연결 문자열에 binaryTransfer=false 옵션을 추가합니다:
-- PostgreSQL 서버에서 현재 연결의 파라미터 확인
SELECT pid, application_name, client_addr, state
FROM pg_stat_activity
WHERE datname = current_database();
-- 문제가 있는 타입에 대한 형변환 함수 확인
SELECT typname, typcategory, typinput, typoutput, typreceive, typsend
FROM pg_type
WHERE typname IN ('uuid', 'numeric', 'jsonb', 'timestamptz');
원인 3: pg_dump/pg_restore 버전 불일치 해결
반드시 텍스트(plain) 또는 custom 포맷으로 덤프하고, 타입을 명시적으로 검증합니다.
-- 복원 후 데이터 유효성 검사
-- geometry 타입 유효성 확인 (PostGIS 사용 시)
SELECT id, ST_IsValid(geom), ST_IsValidReason(geom)
FROM spatial_table
WHERE NOT ST_IsValid(geom);
-- jsonb 데이터 유효성 검사
SELECT id, data
FROM json_table
WHERE data IS NOT NULL
AND data::TEXT = 'null'; -- 빈 JSON 또는 null 감지
-- 손상된 바이너리 데이터를 null로 교체하는 예시
UPDATE spatial_table
SET geom = NULL
WHERE id IN (
SELECT id FROM spatial_table
WHERE NOT ST_IsValid(geom)
);
-- numeric 타입 범위 초과 검사
SELECT column_name, data_type, numeric_precision, numeric_scale
FROM information_schema.columns
WHERE table_name = 'financial_data'
AND data_type = 'numeric';
예방 방법
1. 바이너리 COPY보다 텍스트/CSV 포맷을 기본값으로 사용하고, 드라이버 버전을 서버 버전과 일치시키기
운영 환경에서 데이터를 대량으로 이적할 때는 바이너리 포맷의 성능 이점보다 안정성을 우선시하여 텍스트 또는 CSV 포맷을 기본으로 사용하세요. 또한 JDBC, psycopg2, libpq 등 클라이언트 드라이버를 PostgreSQL 서버 메이저 버전 업그레이드 시 함께 업그레이드하고, binaryTransfer 관련 옵션을 명시적으로 설정하여 예상치 못한 바이너리 직렬화 오류를 방지합니다.
-- 데이터 로드 전 항상 텍스트 포맷으로 검증 후 진행
COPY staging_table FROM STDIN WITH (FORMAT TEXT);
-- 이후 타입 변환 및 유효성 검사 후 실제 테이블로 이동
2. 데이터 파이프라인에 타입 유효성 검사 레이어 추가
ETL 파이프라인이나 데이터 마이그레이션 작업에서는 반드시 중간 스테이징 테이블(모든 컬럼을 TEXT로 선언)을 활용하여 원본 데이터를 먼저 적재하고, 타입 캐스팅 및 유효성 검증을 거친 후 최종 테이블에 INSERT하는 방식을 채택하세요. 이렇게 하면 바이너리 표현 오류를 사전에 차단하고, 문제 데이터를 격리하여 처리할 수 있습니다.
-- 스테이징 테이블 패턴 예시
CREATE TABLE stg_orders (
raw_id TEXT,
raw_amount TEXT,
raw_created_at TEXT,
load_ts TIMESTAMPTZ DEFAULT NOW()
);
-- 유효성 검사 후 본 테이블 적재
INSERT INTO orders (id, amount, created_at)
SELECT
raw_id::BIGINT,
raw_amount::NUMERIC(15,2),
raw_created_at::TIMESTAMPTZ
FROM stg_orders
WHERE raw_id ~ '^\d+$'
AND raw_amount ~ '^\d+(\.\d+)?$'
AND raw_created_at ~ '^\d{4}-\d{2}-\d{2}';
관련 에러
22000data_exception: 데이터 예외의 상위 클래스로,22P03은 이 카테고리에 속합니다. 비슷한 맥락에서 발생하는 데이터 형식 관련 에러들을 포함합니다.22P02invalid_text_representation: 텍스트 형식의 입력값이 대상 데이터 타입으로 변환될 수 없을 때 발생합니다.22P03의 텍스트 버전에 해당하며 실무에서 매우 자주 만나는 에러입니다.22003numeric_value_out_of_range: NUMERIC, INTEGER 등의 타입에서 값의 범위를 초과했을 때 발생하며, 바이너리 표현 오류와 함께 나타나는 경우가 있습니다.42804datatype_mismatch: 컬럼의 데이터 타입과 제공된 값의 타입이 일치하지 않을 때 발생하며, 드라이버 수준에서 바이너리 타입 불일치로 이어지는 경우가 있습니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.