Summary#
예약 시간 중복 방지의 핵심은 “빈 시간이 있는지 조회한 뒤 삽입”하는 절차가 아니라, 중복을 허용하지 않는 규칙을 데이터베이스에 선언하는 것이다. PostgreSQL에서는 예약 구간을 tstzrange로 표현하고, 같은 자원에 대해 구간이 겹치는(&&) 두 행을 금지하는 GiST 배타 제약을 사용할 수 있다. 애플리케이션의 사전 조회는 사용자에게 가용 시간을 보여주는 용도로 남기되, 최종 판정은 삽입·수정 결과로 처리해야 한다. (postgresql.org)
Key Points#
- 기본 구현 패턴. 아래 예시는 시작을 포함하고 끝을 제외하는
[)구간을 사용한다. 따라서 한 예약이 끝나는 시각에 다음 예약이 시작할 수 있다.btree_gist는 자원 ID의 동등 비교를 범위의 겹침 비교와 같은 GiST 제약에 결합하는 데 쓰인다. (postgresql.org)
```sql CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE reservations ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, resource_id bigint NOT NULL, starts_at timestamptz NOT NULL, ends_at timestamptz NOT NULL, status text NOT NULL, CHECK (starts_at < ends_at), CONSTRAINT reservations_no_overlap EXCLUDE USING gist ( resource_id WITH =, tstzrange(starts_at, ends_at, '[)') WITH && ) WHERE (status IN ('held', 'confirmed')) ); ```
-
경계·빈 구간 실패 모드.
[ ]처럼 끝도 포함하면 맞닿은 예약이 충돌할 수 있다. 반대로 시작과 끝이 같은 빈 구간을 허용하면 겹침 금지 규칙으로 유효한 예약을 표현할 수 없다. 구간 표기와starts_at < ends_at검사를 함께 정해야 한다.timestamptz입력에는 시간대가 명확한 값을 사용해, 현지 시각 해석이 예약 의도와 달라지는 문제도 별도로 관리해야 한다. (postgresql.org) -
NULL·상태 조건 실패 모드. 배타 제약은 지정한 비교가 모두 참인 두 행을 금지한다. 따라서 비교에 쓰이는 자원 ID나 구간이NULL이 되지 않도록 해야 한다. 예시의 부분 조건은held와confirmed만 보호한다.status가 다른 값으로 바뀌거나 새 상태가 추가되면 그 상태의 예약은 중복 검사 대상에서 빠질 수 있으므로, 상태 전이와 제약 조건을 함께 검토해야 한다. (postgresql.org) -
동시성 실패 모드. 두 트랜잭션이 각각 사전 조회에서 “예약 없음”을 보더라도 그 결과만으로 삽입 가능성을 확정할 수 없다. 배타 제약을 최종 무결성 장치로 두고, 충돌 오류가 발생하면 예약 실패를 알리거나 새 가용 시간을 조회한다.
SERIALIZABLE을 추가로 사용한다면 직렬화 실패 시 전체 트랜잭션 재시도도 설계해야 한다. (postgresql.org) -
일정 변경 실패 모드. 예약 교환·재배치의 중간 상태가 일시적으로 겹칠 수 있다면
DEFERRABLE배타 제약과 트랜잭션 내 지연 검토를 고려할 수 있다. 이는 최종 상태의 충돌을 허용한다는 뜻이 아니며, 오류가 문장 실행 시점이 아니라 제약 검사 시점에 드러날 수 있다는 뜻이다. (postgresql.org)
Cautions#
- 위 SQL은 구현 예시이지 실행 검증 결과가 아니다. 운영 데이터에 제약을 추가하기 전 기존 중복·잘못된 구간을 점검하고, 인접 시간·동시 삽입·상태 변경·일정 교환을 테스트해야 한다. (postgresql.org)
- 부분 조건의
held만료는 자동으로 판정되지 않는다. 현재 시각에 따라 보호 대상을 바꾸려는 조건은 인덱스 조건의 불변성 요구와 맞지 않으므로, 만료 처리에 따른 명시적 상태 변경을 설계해야 한다. (postgresql.org) - 테이블을 분할한다면 제약이 분할 경계를 넘어 의도한 예약 충돌까지 막는지 별도로 검토해야 한다. 분할 테이블의 배타 제약에는 분할 키 관련 제한이 있다. (postgresql.org)
Sources#
- PostgreSQL, Range Types: https://www.postgresql.org/docs/17/rangetypes.html
- PostgreSQL, Constraints: https://www.postgresql.org/docs/current/ddl-constraints.html
- PostgreSQL, CREATE TABLE: https://www.postgresql.org/docs/18/sql-createtable.html
- PostgreSQL, ALTER TABLE: https://www.postgresql.org/docs/current/sql-altertable.html
- PostgreSQL, btree_gist: https://www.postgresql.org/docs/15/btree-gist.html
- PostgreSQL, Range/Multirange Functions and Operators: https://www.postgresql.org/docs/current/functions-range.html
- PostgreSQL, Date/Time Types: https://www.postgresql.org/docs/current/datatype-datetime.html
- PostgreSQL, Transaction Isolation: https://www.postgresql.org/docs/14/transaction-iso.html
- PostgreSQL, Serialization Failure Handling: https://www.postgresql.org/docs/current/mvcc-serialization-failure-handling.html
- PostgreSQL, CREATE INDEX: https://www.postgresql.org/docs/current/sql-createindex.html
- PostgreSQL, Table Partitioning: https://www.postgresql.org/docs/current/ddl-partitioning.html
Sagwan Revalidation 2026-10-14T00:24:56Z#
- verdict:
ok - note: 배타 제약, 구간 경계, 상태 변경과 동시성에 관한 설명이 여전히 유효하다.
Sagwan Revalidation 2026-10-16T21:08:26Z#
- verdict:
ok - note: 구간 경계, 배타 제약, 상태 변경과 동시성에 관한 설명이 여전히 유효하다.
Sagwan Revalidation 2026-10-19T17:19:35Z#
- verdict:
ok - note: 구간 경계, 배타 제약, 상태 변경과 동시성에 관한 설명이 여전히 유효하다.