템플릿 둘러보기/에어비앤비 (Airbnb)

에어비앤비 (Airbnb)

호스트 및 게스트 프로필, 편의시설과 사진이 포함된 숙소 리스팅, 가용성 캘린더, 동적 요금 설정, 예약 수명 주기 관리, 양방향 리뷰, 메시징, 위시리스트 및 호스트 대금 지급 추적 기능을 갖춘 에어비앤비 스타일의 단기 임대 마켓플레이스를 위한 전체 데이터베이스 스키마입니다.

PostgreSQL15개 테이블웹 앱
rentalmarketplacebookinghospitality
ERD Studio로 제작

이 스키마에 대하여

이 스키마는 양쪽이 같은 사람일 수 있는 시장을 모델링합니다. 이번 주에 숙소를 내놓은 사람이 다음 주에는 남의 숙소를 예약합니다. 그래서 호스트 테이블을 따로 두지 않았습니다. host_profiles 는 users 에 붙어서, 호스트로 활동할 때만 성립하는 것 — 슈퍼호스트 여부, 응답률 — 만 담습니다.

예약 쪽은 "가격은 숙소가 아니라 날짜의 속성"이라는 전제 위에 있습니다. listings 에 base_price 가 있지만 availability_calendar 가 하루 단위로 덮어씁니다. 주말과 성수기를 다르게 받으면서도 숙소를 복제하지 않는 이유가 이것입니다.

돈은 일부러 두 군데에 적습니다. bookings 에는 게스트가 낸 금액을 base_total·cleaning_fee·service_fee 로 나눠 담고, payouts 에는 호스트가 받은 금액을 담습니다. 플랫폼이 수수료를 떼므로 두 숫자는 다르고, 둘 다 있어야 정산을 예약 건과 맞춰 볼 수 있습니다.

눈여겨볼 관계

users → host_profiles
호스트인 사용자마다 한 행. 별도 계정이 아닙니다. 게스트가 호스트를 시작해도 신원은 그대로라, 그 전에 받은 리뷰가 여전히 같은 사용자를 가리킵니다.
listings → availability_calendar
숙소 하나의 하루마다 한 행. status·price·min_nights 를 함께 들고 있어서, 날짜를 막는 일과 값을 바꾸는 일이 같은 작업입니다.
bookings → reviews
리뷰가 숙소가 아니라 예약에 붙습니다. 실제로 묵은 사람만 쓸 수 있다는 규칙이 구조로 들어간 것입니다. type 컬럼이 게스트 리뷰와 호스트 리뷰를 가릅니다.
reviews → users (두 번)
reviewer_id 와 reviewee_id 가 모두 users 를 가리킵니다. 예약 한 건이 서로 반대 방향의 두 행을 만들지, 평점 컬럼 두 개짜리 한 행을 만들지 않습니다.
listings ↔ amenities
listing_amenities 를 거치는 다대다. 편의시설을 공용 마스터로 둬서 "와이파이 있는 곳" 필터가 문자열 검색이 아니라 인덱스 조회가 됩니다.

설계 판단

리뷰는 한 행이 아니라 두 행

한 번의 숙박이 게스트 리뷰와 호스트 리뷰를 만듭니다. 둘은 쓰는 시점이 다르고, 공개되는 규칙이 다르고, 따로 신고될 수 있습니다. 한 행에 담으면 모든 질의가 자기가 어느 쪽을 읽는지 알아야 하고, 한 행을 두 번 공개할 수도 없습니다. type 컬럼을 둔 두 행은 조인 한 번이 더 들지만 "이 사용자가 쓴 글 전부" 가 단순 필터가 됩니다.

가능 여부와 가격이 같은 행에

막힌 날은 가격이 없고, 비싼 날은 대개 수요가 많은 날입니다. 둘을 나누면 하루를 바꾸는 데 쓰기가 두 번 일어나고 읽을 때 조인이 붙습니다. 숙소×날짜 한 행이면 달력이 한 번의 스캔으로 끝나고, 기본값과 다른 날만 행을 만들므로 테이블도 크지 않습니다.

사진 순서는 입력 순서가 아니라 컬럼으로

listing_photos.position 이 있는 이유는 대표 사진이 시각이 아니라 결정이기 때문입니다. created_at 에 기대면 순서를 바꿀 때마다 행을 다시 써야 하고, 업로드 방식을 손대는 순간 갤러리가 조용히 뒤섞입니다.

대화가 숙소와 예약을 둘 다 가리킨다

문의는 대개 예약보다 먼저 시작합니다 — 반려동물이 되는지 묻는 것처럼. listing_id 는 처음부터 채워지고, booking_id 는 문의가 숙박으로 이어질 때 채워집니다. 예약을 먼저 요구하면 예약 전 문의를 막거나 가짜 예약을 만들어야 합니다.

이 템플릿의 테이블

users사용자
10 cols
host_profiles호스트 프로필
9 cols
listings숙소 리스팅
19 cols
listing_photos숙소 사진
6 cols
amenities편의시설
5 cols
listing_amenities숙소별 편의시설
4 cols
availability_calendar예약 가능 캘린더
8 cols
bookings예약
14 cols
reviews리뷰
11 cols
conversations대화
5 cols
conversation_participants대화 참여자
5 cols
messages메시지
6 cols
wish_lists위시리스트
6 cols
wish_list_items위시리스트 항목
5 cols
payouts대금 지급
8 cols

자주 묻는 것

호스트 테이블을 왜 따로 두지 않았나요?
호스팅은 신원이 아니라 역할이기 때문입니다. 같은 계정이 예약도 하고 등록도 합니다. 나누면 이메일·전화·인증이 중복되고, 이름 하나를 찾는 데도 모든 메시지와 리뷰가 두 테이블을 뒤져야 합니다. host_profiles 는 호스트로 활동할 때만 존재하는 값만 담습니다.
1박 가격은 어디서 오나요?
listings.base_price 가 기본값이고, availability_calendar.price 가 있으면 그 날짜에 한해 덮어씁니다. 예약은 자기 base_total 을 따로 저장하므로, 나중에 값을 바꿔도 지난 예약이 다시 쓰이지 않습니다.
availability_calendar 를 빼도 되나요?
모든 날의 값이 같고 개별 날짜를 막을 일이 없다면 됩니다. 둘 중 하나라도 아니게 되는 순간 날짜 구간을 bookings 에 넣게 되고, "이 날 비어 있나" 가 조회가 아니라 구간 겹침 질의가 됩니다.
nights 와 total 은 계산되는 값인데 왜 저장하나요?
계산의 재료가 바뀌기 때문입니다. nights 는 check_in·check_out 에서 나오므로 어느 쪽이든 괜찮지만, total 은 수수료와 세금 규칙에 달려 있고 그 규칙은 고쳐집니다. 실제로 청구한 금액을 적어 두어야 규칙이 바뀐 뒤에도 지난 예약을 읽을 수 있습니다.
즉시 예약과 승인 후 예약을 넣으려면?
숙소의 속성이므로 bookings 의 상태가 아니라 listings 에 컬럼을 답니다. bookings.status 가 이미 승인 절차를 담고 있어서, 즉시 예약은 대기 상태를 건너뛰는 것으로 끝납니다.