PostgreSQL range와 EXCLUDE 제약으로 겹치는 예약 막기
GiST exclusion constraint는 같은 자원의 겹치는 구간을 데이터베이스에서 거부합니다. 경계, NULL, 빈 범위와 버전 조건도 함께 설계해야 합니다.
발행·오류 제보: infostoa · 편집 기준
이 글의 개별 작성자와 전문 검토자 정보는 제공되지 않습니다.
예약 충돌을 애플리케이션의 사전 조회만으로 막으면 동시 요청이 같은 빈 시간을 확인한 뒤 둘 다 저장할 수 있습니다. PostgreSQL의 range 타입과 exclusion constraint를 사용하면 데이터베이스가 행 사이의 겹침 규칙을 검사합니다. PostgreSQL 18 range 문서와 CREATE TABLE 설명이 기준이며, 실무 예시는 Cybertec의 전문가 설명을 보완 자료로 확인했습니다.
구간 타입과 경계부터 정한다
PostgreSQL에는 정수, 날짜, 숫자, timestamp용 range 타입이 있습니다. timestamp without time zone에는 tsrange, 시간대 정보를 입력 시점의 절대 시각으로 변환해 다룰 때는 tstzrange를 선택합니다. PostgreSQL은 tstzrange의 timestamp with time zone 값을 UTC로 저장하며 원래 입력한 시간대 이름은 보존하지 않습니다. 원래 지역 시간대를 나중에도 알아야 한다면 America/New_York 같은 IANA 시간대 식별자를 별도 열에 저장하세요. 범위는 포함·제외 경계를 표현합니다. 일정 예약에서 [시작, 끝)처럼 시작은 포함하고 끝은 제외하면 한 예약이 끝나는 시각에 다음 예약을 시작시키는 모델을 만들 수 있습니다. 업무 규칙과 시간대 변환을 먼저 정하고 타입을 고르세요.
겹침 제약을 선언한다
한 테이블에서 같은 회의실의 시간이 겹치지 않게 하려면 btree_gist를 설치한 뒤 EXCLUDE USING gist (room WITH =, during WITH &&) 형태로 제약을 정의할 수 있습니다. &&는 두 range가 겹치는지 검사하고, =는 자원이 같은지 검사합니다. 모든 비교가 동시에 참인 두 행을 허용하지 않으므로 서로 다른 방은 같은 시간에 예약될 수 있고 같은 방의 겹치는 구간은 거부됩니다. exclusion constraint는 인덱스를 만들며, 적절한 operator class와 확장 설치 권한이 필요합니다.
NULL과 빈 구간을 별도 정책으로 처리
제약의 비교 연산 결과가 false 또는 NULL 중 하나면 두 행이 함께 금지되지 않을 수 있습니다. 따라서 room과 during이 반드시 있어야 한다면 NOT NULL을 별도로 선언하세요. 빈 range는 다른 구간과 겹치지 않을 수 있고, 무한 경계는 열린 기간을 뜻할 수 있습니다. 이런 입력이 허용되는지 제품 규칙으로 정하고, 금지하려면 별도의 조건과 검증을 둡니다. CHECK 제약만 추가할 때 NULL 결과가 통과할 수 있다는 점도 고려해야 합니다.
버전과 운영 영향 확인
이 예시는 PostgreSQL 18의 range 및 EXCLUDE 동작으로 설명했습니다. 18의 WITHOUT OVERLAPS 같은 새 문법을 이전 버전에 그대로 적용하지 마세요. 파티션 테이블에는 exclusion 제약의 파티션 키 제약이 있고, btree_gist는 필요한 scalar equality 연산자를 제공할 수 있습니다. 기존 행과 마이그레이션, 확장 설치, 제약 추가 중 잠금·검증 영향은 실제 배포 버전의 공식 문서와 스테이징에서 확인해야 합니다. SQL 사전 조회는 사용자 경험에는 유용하지만 동시성 안전을 최종 보장하는 제약을 대체하지 않습니다.
선언 예시와 오류 처리
회의실 예약이라면 다음 형태로 스키마 의도를 드러낼 수 있습니다.
CREATE EXTENSION btree_gist;
CREATE TABLE reservation (
room text NOT NULL,
during tstzrange NOT NULL,
EXCLUDE USING gist (room WITH =, during WITH &&)
);
이는 PostgreSQL 문서의 room-range 예를 필수값으로 좁힌 형태입니다. 실행 전 확장 설치 권한, 서버 버전, 시간대 표현을 확인하고 스테이징에서 검증하세요. 같은 방에 겹치는 범위를 넣으면 제약 위반이 됩니다. 앱은 그 특정 오류를 충돌 응답으로 바꾸되 모든 데이터베이스 오류를 예약 충돌로 처리하지 않아야 합니다. 제약 이름은 로그 분류에 도움이 됩니다.
전문가 관점: 제약은 데이터 모델의 일부
CYBERTEC PostgreSQL International의 사례는 exclusion 제약을 단순한 조회 최적화가 아니라 경쟁하는 쓰기까지 막는 무결성 규칙으로 다룹니다. 다만 부분 제약과 사용자 정의 연산자 예시는 연산자 클래스·버전 조건을 그대로 확인해야 합니다. 운영에서는 적용 전 기존 행을 검사하고, 충돌은 제약 위반으로 명시적으로 처리하세요.