2026년 07월 17일 | DBMS Error 가이드
이 글에서 다루는 내용
55000 에러의 원인 분석, 해결 SQL, 예방 방법을 실무 관점에서 정리합니다.
55000 object not in prerequisite state 는?
PostgreSQL 에러 코드 55000은 object not in prerequisite state로, 특정 작업을 수행하기 위해 필요한 선행 조건이 충족되지 않은 상태에서 명령을 실행할 때 발생합니다. 즉, 데이터베이스 객체(테이블, 인덱스, 시퀀스, WAL 등)가 현재 요청된 작업을 처리할 수 있는 적절한 상태에 있지 않다는 의미입니다. 이 에러는 주로 Hot Standby 환경, 논리적 복제(Logical Replication), WAL 관련 설정 미비, 또는 잘못된 트랜잭션 격리 수준 설정 등 다양한 상황에서 광범위하게 나타납니다.
주요 발생 원인
1. Hot Standby(읽기 전용 복제본)에서 쓰기 작업 시도
PostgreSQL의 스탠바이(Standby) 서버는 기본적으로 읽기 전용(read-only) 모드로 동작합니다. 스탠바이 서버에서 INSERT, UPDATE, DELETE, CREATE, DROP 등의 쓰기 작업을 시도하면 이 에러가 발생합니다. 특히 애플리케이션이 프라이머리(Primary)와 스탠바이(Standby)를 구분하지 못하고 쓰기 작업을 스탠바이로 보내는 경우에 자주 발생합니다.
ERROR: 55000: cannot execute INSERT in a read-only transaction
DETAIL: This standby server is not accepting commands that are not read-only.
2. 논리적 복제(Logical Replication)를 위한 WAL 레벨 미설정
논리적 복제를 사용하기 위해서는 wal_level이 반드시 logical로 설정되어 있어야 합니다. wal_level이 replica 또는 minimal로 설정된 상태에서 논리적 복제 슬롯을 생성하거나 퍼블리케이션(Publication)을 구성하려 하면 55000 에러가 발생합니다. 이 설정은 PostgreSQL 서버 재시작이 필요하기 때문에 운영 환경에서 간과하기 쉬운 원인 중 하나입니다.
ERROR: 55000: logical replication not enabled
HINT: Enable logical replication by setting wal_level = 'logical'.
3. 트랜잭션 내에서 허용되지 않는 DDL 또는 시스템 명령 실행
특정 DDL 명령(예: VACUUM, CLUSTER, CREATE DATABASE, DROP DATABASE 등)은 명시적 트랜잭션 블록 내부에서 실행할 수 없습니다. 트랜잭션 블록 안에서 이러한 명령을 실행하거나, 특정 잠금(Lock) 상태에서 충돌하는 작업을 수행하려 할 때 55000 에러가 발생할 수 있습니다. 또한, pg_wal_replay_pause() 또는 pg_wal_replay_resume() 등의 함수를 잘못된 서버 역할에서 호출할 때도 이 에러가 발생합니다.
해결 방법
원인 1: Hot Standby 쓰기 작업 차단 해결
우선 현재 접속한 서버가 프라이머리인지 스탠바이인지 확인합니다.
-- 현재 서버 역할 확인
SELECT pg_is_in_recovery();
-- true 이면 Standby(읽기 전용), false 이면 Primary
-- Standby에서 허용 가능한 읽기 작업 예시
SELECT * FROM orders WHERE created_at >= NOW() - INTERVAL '1 day';
쓰기 작업은 반드시 프라이머리 서버로 라우팅되어야 합니다. 애플리케이션 레벨에서 커넥션 풀러(PgBouncer, HAProxy 등)를 사용하여 읽기/쓰기를 분리하세요.
-- 프라이머리 서버에서 스탠바이 목록 확인
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication;
원인 2: WAL 레벨 설정 변경
현재 WAL 레벨을 확인하고 논리적 복제를 위해 변경합니다.
-- 현재 WAL 레벨 확인
SHOW wal_level;
-- postgresql.conf에서 변경 (서버 재시작 필요)
-- wal_level = logical
-- max_replication_slots = 10
-- max_wal_senders = 10
서버 재시작 후 논리적 복제 슬롯과 퍼블리케이션을 생성합니다.
-- 퍼블리케이션 생성 (Primary에서)
CREATE PUBLICATION my_publication FOR TABLE orders, customers;
-- 복제 슬롯 확인
SELECT slot_name, plugin, slot_type, active
FROM pg_replication_slots;
-- Subscriber 서버에서 구독 생성
CREATE SUBSCRIPTION my_subscription
CONNECTION 'host=primary_host port=5432 dbname=mydb user=replicator password=secret'
PUBLICATION my_publication;
원인 3: 트랜잭션 블록 외부에서 DDL 실행
VACUUM, CLUSTER 등의 명령은 트랜잭션 블록 밖에서 단독으로 실행해야 합니다.
-- 잘못된 예시 (트랜잭션 블록 내 VACUUM 실행 - 에러 발생)
BEGIN;
VACUUM ANALYZE orders; -- ERROR: 55000
COMMIT;
-- 올바른 예시 (트랜잭션 블록 외부에서 실행)
VACUUM ANALYZE orders;
-- WAL 재생 관련 함수는 Standby에서만 호출 가능
-- Primary에서 호출 시 에러 발생
-- 올바른 Standby 서버에서의 WAL 재생 제어
SELECT pg_wal_replay_pause(); -- Standby에서만 유효
SELECT pg_wal_replay_resume(); -- Standby에서만 유효
-- 서버 역할 확인 후 조건부 실행 예시
DO $$
BEGIN
IF pg_is_in_recovery() THEN
RAISE NOTICE 'This is a Standby server. WAL replay control available.';
ELSE
RAISE NOTICE 'This is a Primary server. Skipping WAL replay control.';
END IF;
END;
$$;
예방 방법
1. 애플리케이션 레벨에서 서버 역할 체크 및 라우팅 자동화
데이터베이스 연결 시 항상 pg_is_in_recovery()를 통해 서버 역할을 확인하는 헬스체크 로직을 구현하고, PgBouncer나 HAProxy와 같은 커넥션 풀러를 활용하여 쓰기 트래픽은 프라이머리로, 읽기 트래픽은 스탠바이로 자동 라우팅하는 구조를 갖추세요. 또한 CI/CD 파이프라인에서 마이그레이션 스크립트 실행 전 반드시 프라이머리 서버 여부를 검증하는 단계를 추가하여 실수로 스탠바이에 DDL이 실행되는 것을 방지해야 합니다.
-- 헬스체크 쿼리 예시 (애플리케이션 시작 시 실행)
SELECT
pg_is_in_recovery() AS is_standby,
current_setting('wal_level') AS wal_level,
pg_postmaster_start_time() AS start_time;
2. PostgreSQL 설정 표준화 및 변경 관리 프로세스 적용
wal_level, max_replication_slots, max_wal_senders 등 복제 관련 핵심 파라미터를 IaC(Infrastructure as Code) 도구(Ansible, Terraform 등)로 관리하여 환경별 설정 불일치를 방지하세요. 설정 변경 시에는 반드시 변경 관리 프로세스(Change Management Process)를 거쳐 검토와 승인 후 적용하고, 변경 전후 pg_settings 뷰를 통해 설정값을 비교 검증하는 습관을 갖추어야 합니다.
-- 복제 관련 핵심 설정값 일괄 확인
SELECT name, setting, unit, context, short_desc
FROM pg_settings
WHERE name IN (
'wal_level',
'max_replication_slots',
'max_wal_senders',
'hot_standby',
'hot_standby_feedback'
)
ORDER BY name;
관련 에러
- 25006 (read_only_sql_transaction): 읽기 전용 트랜잭션에서 쓰기 작업 시도 시 발생하며, 55000과 유사한 맥락에서 자주 함께 나타납니다.
- 55006 (object_in_use): 다른 세션에서 사용 중인 객체에 접근하려 할 때 발생하는 에러로, 선행 조건 위반 에러군에 속합니다.
- 55P02 (cant_change_runtime_param): 현재 세션에서 변경할 수 없는 런타임 파라미터를 변경하려 할 때 발생하며, 설정 관련 선행 조건이 맞지 않을 때 나타납니다.
- 57P01 (admin_shutdown): 관리자 종료로 인한 에러로, 서버 상태 전환 중 발생할 수 있어 55000과 비슷한 운영 맥락에서 접하게 됩니다.
주요 DBMS error code를 정리하는 시리즈입니다.
블로그 홈에서 다른 에러도 확인하세요.
본 포스트는 AI가 생성한 기술 가이드입니다. 운영 환경 적용 전 충분한 검토를 권장합니다.