배경
같은 엔진오일 교체도 부품을 직접 사서 공임만 맡기느냐, 정비소에 전부 맡기느냐에 따라 비용이 크게 달라집니다. 그런데 정비소의 가격과 가능한 작업은 전화로 물어보기 전까지 알기 어렵습니다.
반대로 정비소에는 평일 낮처럼 비어 있는 리프트와 시간이 있습니다. 직접 정비하고 싶은 사람과 비어 있는 작업 공간을 이어 주는 서비스가 PitNow입니다.
맡은 일
- 고객 앱: 지도 기반 정비소 탐색, 예약, 결제, QR 체크인, 이용 시간 확인, 체크아웃, 사후 정산, 리뷰
- 정비소 운영 화면: 예약, 베이, 예약 가능 시간과 차단, 패키지, 시설 사진, 체크인, 고객 관리
- 내부 관리자 화면: 전체 예약, 결제와 환불, 정산, 패키지, 파트너 감사 기록
- Supabase 데이터베이스 설계와 마이그레이션, 권한(RLS), 결제 연동, 테스트 자동화
핵심 결정
결정 1
예약 겹침은 데이터베이스가 막게 했습니다
- 상황
- 같은 베이의 같은 시간을 두 사람이 동시에 예약하면, 화면에서 확인하거나 저장 전에 한 번 조회하는 방식으로는 경쟁 상태를 막을 수 없습니다.
- 선택
- PostgreSQL exclusion constraint로 같은 베이에서 시간 범위가 겹치는 예약은 저장 자체가 거부되도록 했습니다.
- 이유와 결과
- 요청이 동시에 몰려도 정합성이 데이터 계층에서 보장되고, 애플리케이션은 제약 위반을 사용자 안내로 바꾸기만 하면 됩니다.
결정 2
이용 시간 계산은 서버 시각으로만 했습니다
- 상황
- 셀프 정비는 이용 시간에 따라 추가 요금이 붙습니다. 사용자 기기의 시간은 틀리거나 바뀔 수 있습니다.
- 선택
- 체크인과 체크아웃 시각을 서버 기준으로 기록해 30분 단위로 올림 계산하고, 예약 상태를 상태 머신으로 정의해 중복 체크아웃 같은 잘못된 전환을 막았습니다.
- 이유와 결과
- 요금의 기준이 하나여야 분쟁이 생겼을 때 근거를 설명할 수 있습니다.
결정 3
헬퍼 기능을 빼고 패키지 예약을 따로 두었습니다
- 상황
- 초기 기획에는 셀프 정비 중 도움을 요청하는 헬퍼 기능이 있었습니다.
- 선택
- 헬퍼 기능을 빼고, 처음부터 정비소에 맡기는 패키지 예약을 별도 방식으로 두었습니다.
- 이유와 결과
- 헬퍼가 끼면 사고가 났을 때 책임이 사용자와 헬퍼, 정비소 사이에서 흐려지고, 사용자는 대여료와 인건비를 함께 내게 됩니다.
코드로 보기
alter table reservations
add constraint no_overlap
exclude using gist (
bay_id with =,
tstzrange(start_time, end_time) with &&
);bay_id가 같고(=) 예약 시간 범위가 겹치는(&&) 행은 저장되지 않습니다.
결과
- 탐색부터 예약과 결제, 체크인, 체크아웃, 사후 정산, 리뷰까지 한 흐름으로 동작하는 서비스를 배포했습니다.
- Toss Payments 테스트 환경에서 결제와 취소, 환불, 사후 정산을 확인하고 Playwright E2E 테스트와 실기기 QA를 진행했습니다.
- Supabase RLS와 Storage 정책으로 고객과 정비소, 관리자의 데이터 접근을 분리했습니다.
- 사업자 등록, 결제 대행 계약, 약관, 분쟁 처리 기준처럼 유료 출시 전에 필요한 운영 기준을 20여 개 문서로 정리했습니다.
개발 방식
Codex와 Claude Code를 페어 개발자로 사용했습니다. 작업 요청은 Context, Goal, Constraints, Output 템플릿으로 표준화하고, 설계 결정과 검증은 직접 맡았습니다.
예약 시작 시간 제한 때문에 막히던 체크인 테스트는 특정 예약의 시간을 조정하는 QA 스크립트를 만들어 반복 검증했습니다.
관련 글
데이터 이상은 규칙으로 찾고, AI에게는 정리만 맡겼습니다
결제는 승인됐는데 예약이 없거나, 환불 기록 없이 상태만 환불로 바뀐 데이터를 매일 찾는 운영 점검을 만들었습니다. 탐지는 SQL 규칙에 두고, AI에게는 우선순위와 조치 정리만 맡겼습니다. AI 응답을 어떻게 검증했는지도 적었습니다.
예약 겹침을 코드가 아니라 데이터베이스에서 막은 이유
빈 시간을 확인한 뒤 예약을 넣는 코드만으로는 동시에 들어온 요청을 막을 수 없습니다. PostgreSQL 배제 제약 조건으로 같은 리프트의 시간 겹침을 막고, 에러를 사용자 안내로 바꾼 과정입니다.



