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

42P04
2026년 09월 13일 | DBMS Error 가이드

이 글에서 다루는 내용

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

42P04 duplicate database 는?

PostgreSQL 에러 코드 42P04duplicate database, 즉 이미 존재하는 데이터베이스를 다시 생성하려 할 때 발생하는 에러입니다. 예를 들어 CREATE DATABASE mydb; 명령을 실행했는데 mydb라는 이름의 데이터베이스가 이미 클러스터 내에 존재할 경우 PostgreSQL은 이 에러를 반환하며 명령 실행을 거부합니다. 이 에러는 주로 자동화 스크립트, CI/CD 파이프라인, 또는 초기화 작업 중에 데이터베이스 존재 여부를 사전에 확인하지 않고 무조건 생성 명령을 실행할 때 빈번하게 나타납니다.


주요 발생 원인

1. 자동화 스크립트에서 조건 없이 CREATE DATABASE 실행

배포 자동화나 초기 세팅 스크립트에서 데이터베이스가 이미 존재하는지 확인하지 않고 CREATE DATABASE 명령을 그대로 실행하는 경우가 가장 흔한 원인입니다. 특히 CI/CD 파이프라인에서 환경을 재구성할 때, 이전 실행에서 이미 생성된 데이터베이스가 남아 있는 상태에서 같은 스크립트를 재실행하면 이 에러가 반드시 발생합니다. 테스트 환경에서 자주 겪는 상황이며, 운영 환경에서도 충분히 발생할 수 있는 케이스입니다.

2. 여러 프로세스 또는 세션의 동시 실행 (Race Condition)

멀티 스레드 애플리케이션이나 병렬 배포 환경에서 두 개 이상의 프로세스가 거의 동시에 같은 데이터베이스를 생성하려 시도할 때 발생할 수 있습니다. 첫 번째 프로세스가 데이터베이스를 성공적으로 생성한 직후, 두 번째 프로세스가 동일한 이름으로 CREATE DATABASE를 실행하면 42P04 에러가 발생합니다. 이 케이스는 디버깅이 어렵고, 타이밍에 따라 간헐적으로 발생하기 때문에 특히 주의가 필요합니다.

3. 개발/운영 환경 혼용 또는 데이터베이스 이름 충돌

여러 프로젝트가 동일한 PostgreSQL 클러스터를 공유하는 환경에서, 서로 다른 팀이나 서비스가 동일한 데이터베이스 이름을 사용하려 할 때 충돌이 발생합니다. 예를 들어 app_db, testdb, main 같은 일반적인 이름은 여러 프로젝트에서 중복으로 사용될 가능성이 높습니다. 네이밍 컨벤션이 없는 조직에서 특히 자주 발생하며, 의도치 않은 데이터 손실이나 혼란으로 이어질 수 있습니다.


해결 방법

원인 1 해결: IF NOT EXISTS 절 사용 (PostgreSQL 9.3+)

PostgreSQL 9.3 버전부터는 CREATE DATABASE 명령에 IF NOT EXISTS 옵션을 직접 지원하지 않습니다. 하지만 psql 메타커맨드나 PL/pgSQL을 활용하여 조건부로 처리할 수 있습니다.

-- 방법 1: DO 블록을 사용한 조건부 생성
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT FROM pg_database WHERE datname = 'mydb'
    ) THEN
        PERFORM dblink_exec('dbname=postgres', 'CREATE DATABASE mydb');
    END IF;
END
$$;
-- 방법 2: pg_database 카탈로그를 조회하여 존재 여부 확인 후 생성
-- (psql 스크립트 내에서 shell 조합 사용 예)
-- 먼저 데이터베이스 존재 여부 확인
SELECT 1 FROM pg_database WHERE datname = 'mydb';

-- 결과가 없을 때만 아래 명령 실행
CREATE DATABASE mydb
    OWNER = myuser
    ENCODING = 'UTF8'
    LC_COLLATE = 'ko_KR.UTF-8'
    LC_CTYPE = 'ko_KR.UTF-8'
    TEMPLATE = template0;
# 방법 3: Shell 스크립트에서 사전 체크 후 실행
DB_NAME="mydb"
DB_EXISTS=$(psql -U postgres -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'")

if [ "$DB_EXISTS" != "1" ]; then
    psql -U postgres -c "CREATE DATABASE $DB_NAME;"
    echo "데이터베이스 '$DB_NAME' 생성 완료"
else
    echo "데이터베이스 '$DB_NAME' 이미 존재함. 건너뜀."
fi

원인 2 해결: Race Condition 방지를 위한 Advisory Lock 활용

-- 세션 수준 Advisory Lock으로 동시 실행 방지
DO $$
DECLARE
    lock_acquired BOOLEAN;
BEGIN
    -- 고유한 lock ID (예: 12345)로 잠금 시도
    SELECT pg_try_advisory_lock(12345) INTO lock_acquired;

    IF lock_acquired THEN
        IF NOT EXISTS (
            SELECT FROM pg_database WHERE datname = 'mydb'
        ) THEN
            RAISE NOTICE '데이터베이스 생성을 진행합니다.';
            -- 실제 CREATE DATABASE는 트랜잭션 밖에서 실행 필요
        ELSE
            RAISE NOTICE '데이터베이스가 이미 존재합니다.';
        END IF;
        PERFORM pg_advisory_unlock(12345);
    ELSE
        RAISE NOTICE '다른 프로세스가 작업 중입니다. 잠시 후 재시도하세요.';
    END IF;
END
$$;

원인 3 해결: 기존 데이터베이스 목록 조회 및 이름 정리

-- 현재 클러스터에 존재하는 모든 데이터베이스 목록 확인
SELECT
    datname AS database_name,
    pg_get_userbyid(datdba) AS owner,
    pg_encoding_to_char(encoding) AS encoding,
    datcollate AS collation,
    pg_size_pretty(pg_database_size(datname)) AS size,
    datcreated  -- PostgreSQL 15+ 에서 지원
FROM pg_database
WHERE datistemplate = false
ORDER BY datname;

-- 특정 이름의 데이터베이스가 존재하는지 빠르게 확인
SELECT EXISTS (
    SELECT 1 FROM pg_database WHERE datname = 'mydb'
) AS database_exists;
-- 충돌하는 데이터베이스가 불필요한 경우 삭제 후 재생성
-- ※ 주의: 실제 운영 데이터가 있는지 반드시 확인 후 실행!
-- 1단계: 연결된 세션 강제 종료
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'mydb'
  AND pid <> pg_backend_pid();

-- 2단계: 데이터베이스 삭제
DROP DATABASE IF EXISTS mydb;

-- 3단계: 재생성
CREATE DATABASE mydb
    OWNER = myuser
    ENCODING = 'UTF8'
    TEMPLATE = template0;

예방 방법

1. 데이터베이스 생성 스크립트에 멱등성(Idempotency) 보장

모든 데이터베이스 초기화 스크립트는 몇 번을 실행해도 동일한 결과를 보장하는 멱등성을 갖춰야 합니다. 스크립트 최상단에 항상 pg_database 카탈로그를 조회하는 사전 검사 로직을 추가하고, 존재하는 경우 RAISE NOTICE로 알림만 출력하고 건너뛰도록 설계하세요. CI/CD 파이프라인에서는 Ansible의 postgresql_db 모듈이나 Terraform의 postgresql_database 리소스처럼 멱등성을 기본으로 지원하는 도구를 활용하는 것이 가장 안전합니다.

-- 멱등성 보장 스크립트 예시 (psql에서 실행)
\set db_name 'mydb'

DO $$
BEGIN
    IF EXISTS (SELECT FROM pg_database WHERE datname = :'db_name') THEN
        RAISE NOTICE 'Database "%" already exists. Skipping creation.', :'db_name';
    ELSE
        RAISE NOTICE 'Database "%" does not exist. Please create it manually outside a transaction.', :'db_name';
    END IF;
END
$$;

2. 표준화된 네이밍 컨벤션 적용 및 중앙 관리

데이터베이스 이름은 조직 전체에서 공유하는 네이밍 컨벤션을 반드시 수립하고 문서화해야 합니다. 예를 들어 {팀명}_{프로젝트명}_{환경} 형식(예: platform_auth_prod, data_etl_staging)을 표준으로 정하면 이름 충돌을 원천 차단할 수 있습니다. 더불어 데이터베이스 생성 권한을 CREATEDB 권한을 가진 특정 관리자 계정에만 부여하고, 일반 개발자는 직접 데이터베이스를 생성할 수 없도록 권한을 제한하는 것이 좋습니다.

-- CREATEDB 권한을 특정 관리자에게만 부여
ALTER ROLE deploy_admin CREATEDB;

-- 일반 개발자 계정에서 CREATEDB 권한 제거
ALTER ROLE developer NOCREATEDB;

-- 현재 CREATEDB 권한을 가진 롤 목록 확인
SELECT rolname, rolcreatedb
FROM pg_roles
WHERE rolcreatedb = true
ORDER BY rolname;

관련 에러

  • 42P06 (duplicate_schema): 42P04와 유사하게 이미 존재하는 스키마를 CREATE SCHEMA로 다시 생성할 때 발생합니다. CREATE SCHEMA IF NOT EXISTS 구문으로 방지 가능합니다.
  • 42P07 (duplicate_table): 동일 스키마 내에서 이미 존재하는 테이블을 CREATE TABLE로 재생성 시 발생하며, CREATE TABLE IF NOT EXISTS로 처리할 수 있습니다.
  • 23505 (unique_violation): 유니크 제약 조건 위반으로, 42P04와 직접적 연관은 없지만 중복 데이터 처리라는 맥락에서 함께 이해하면 좋습니다.
  • 55006 (object_in_use): 삭제하려는 데이터베이스에 활성 연결이 있을 때 DROP DATABASE 실행 시 발생하며, 42P04 해결 과정에서 재생성을 위해 기존 DB를 삭제할 때 맞닥뜨릴 수 있는 에러입니다.

DBMS 에러 코드 시리즈

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

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

댓글 남기기