모집·커뮤니티 연동 DB 설계 (design-db)

Background

두 BE TDD의 도메인 모델을 DB 스키마로 확정하는 설계 문서입니다. DDL 전문·마이그레이션 파일 작성은 후속 private-mysql-implementer·private-mongodb-implementer가 이 문서를 입력으로 담당합니다.

  • 입력 A: 스포츠앱/모임·커뮤니티/20260707-post-community-연동-tdd.md (PRD A — post↔community 연동)
  • 입력 B: 스포츠앱/모임·커뮤니티/20260707-모집-시설상품-소모임예약연동-tdd.md (PRD B — recruitment·facility program·community↔booking)
  • 규약(SSOT): private-db-schema-convention(Flyway/MySQL), private-mongodb-convention(MongoDB)

Overview

저장소스키마 변경
A. post↔community 연동MySQLposts 컬럼 3개 추가 (community_id·sport_category·global_listed)
B1. recruitmentMySQL신규 테이블 recruitments·applications
B2. facility program·슬롯MySQL + Mongo신규 테이블 programs, slots 컬럼 2개 추가(status·program_id), facilities validator 신설 + operating_hours/holidays 임베드 필드
B3. community↔bookingMySQL신규 테이블 community_bookings

AS-IS (실제 스키마 재구성 — 기존 마이그레이션 근거)

현재 스키마는 backend/src/main/resources/db/migration/ V1~V55와 mongo/migration/을 전수 재구성했습니다. 레포 HEAD == origin/dev (5ff07c7c, 신선). recruitment/programs/community_booking 참조는 마이그레이션 0건, Mongo $jsonSchema validator 0건입니다.

테이블/컬렉션근거현재 컬럼(요약)현재 인덱스
postsV9__create_posts_comments.sql, V10__add_post_type.sqlid, user_id, title VARCHAR(200), content VARCHAR(10000), type VARCHAR(32) DEFAULT ‘FREE’, audit 6idx_posts_user_id_deleted_at(user_id,deleted_at), idx_posts_deleted_at(deleted_at), idx_posts_type_deleted_at(type,deleted_at)
commentsV9id, post_id, user_id, content, audit 6idx_comments_post_id_deleted_at, idx_comments_deleted_at
slotsV3__create_bookings.sql, V14__add_slot_owner_unique.sqlid, facility_id VARCHAR(255), date DATETIME(6), time_range VARCHAR(11), capacity INT, owner_id BIGINT, audit 6UNIQUE uq_slots_facility_date_time_range(facility_id,date,time_range,deleted_at), idx_slots_facility_id_date_deleted_at(facility_id,date,deleted_at), idx_slots_deleted_at
bookingsV3, V34__add_bookings_slot_capacity_index.sqlid, user_id, slot_id, status VARCHAR(20), payment_id, audit 6idx_bookings_slot_id_status(slot_id,status)(정원 카운트 락 경로), idx_bookings_user_id_status, idx_bookings_created_at, idx_bookings_deleted_at
communitiesV50__create_communities.sqlid, name, visibility VARCHAR(20), sport_category VARCHAR(30), host_user_id, audit 6idx_communities_visibility_deleted_at
community_membersV51__create_community_members.sqlid, community_id, user_id, role, status, joined_at, audit 6UNIQUE uq_community_members_community_user(community_id,user_id,deleted_at), idx_community_members_community_status(community_id,status,deleted_at)
facilities (Mongo)Facility.kt#entity/Facility.kt, V202607041930__add_facility_region_index.jscode, name, gu, type, address, location(Point), parking, tel, home_page, edu_yn, meta(Map), owner_user_id, sido_code/name, sigungu_code/name. operating_hours·holidays 없음, validator 없음idx_gu_type, idx_sido_sigungu_type, code(단일), gu(단일), location(2dsphere)
  • 최종 마이그레이션 버전 = V55. 신규 MySQL 마이그레이션은 V56 이후를 사용합니다. 단 동시 dev 머지 레이스로 Flyway 버전이 충돌한 이력이 있어(MEMORY: migration-number-collision), implementer는 머지 직전 버전 중복을 재검사합니다.
  • slots.facility_idVARCHAR(255) (Mongo facilities._id를 논리 참조). 신규 programs.facility_id도 동일 타입으로 맞춥니다.

저장소 선택 판단

기본 MySQL. Mongo는 private-mongodb-convention의 채택 근거표에 해당할 때만 채택합니다.

데이터 단위저장소채택 사유
recruitments (모집글)MySQL정원·상태머신·마감 시각의 트랜잭션·정합이 본질. 신청 정원(오버부킹 0)은 비관락+분산락 강일관성 필요 → 관계형
applications (신청)MySQLrecruitment와 강한 관계(정원 카운트)·결제 참조·상태전이. 트랜잭션 정합 필수
programs (시설상품)MySQL예약·정원·가격의 정합. 예약 회차(slots)와 관계. facility_id로 Mongo 시설을 논리 참조
community_bookings (소모임 예약 링크)MySQLcommunity·slot 간 관계 링크. slotId ID 참조, 멱등 UNIQUE 제약 필요
posts 확장 컬럼MySQL기존 posts가 MySQL. additive 컬럼
operating_hours·holidaysMongoDB (facilities 문서 임베드)facility가 이미 Mongo 문서(공공데이터 카탈로그). 운영시간(요일 ≤7)·휴무(유한)는 facility 소유·항상 함께 로드·상한 있는 크기 → 임베딩 기준 충족 (아래 §임베딩 판단)
  • 두 저장소 병용: facility만 Mongo, 나머지 신규 도메인은 MySQL. SSOT는 facility 문서 자체(operating_hours/holidays는 facility의 일부, 중복 보관 없음). booking slots는 MySQL이 SSOT이고 facility 운영시간은 슬롯 생성의 입력일 뿐 중복 저장하지 않습니다(FacilityScheduleGateway가 매 배치 조회).
  • Mongo 미채택 근거: recruitment/application/program은 “Mongo가 편해서”가 아니라 정원·결제 정합이 핵심이라 MySQL. 샤딩·파티셔닝은 개인 프로젝트 규모(RPS<10)에 과함 → 미채택.

MySQL 테이블 정의

FK 컬럼 금지·ENUM→VARCHAR·BOOLEAN→TINYINT(1)·DATETIME(6)·PK=id·참조={entity}_id·audit 6컬럼·COMMENT 필수를 전 테이블에 적용합니다. (DDL 전문은 후속 마이그레이션 티켓 — 여기선 컬럼·타입·NULL·근거만.)

1. recruitments (신규)

컬럼타입NULL근거/COMMENT
idBIGINT AINNPK
titleVARCHAR(200)NN모집 제목
descriptionVARCHAR(2000)NULL설명(선택)
capacityINTNN정원 (≥1, 도메인 검증)
fee_amountDECIMAL(12,2)NN참가비. 0원 허용(무료 모집)
activity_atDATETIME(6)NN활동 일시
application_deadlineDATETIME(6)NN신청 마감. CancellationPolicy.feeRateFor 기준 시각
community_idBIGINTNULL소속 community 논리 참조(FK 없음). NULL=전역 모집
recruiter_user_idBIGINTNN개설자 user 논리 참조
statusVARCHAR(20)NNOPEN/CLOSED/CANCELLED. ENUM 금지→VARCHAR
audit 6created_at/by, updated_at/by, deleted_at/by
  • 정원 경합 방어선: findForUpdateById(PK 비관락) + 분산락 recruitment:{id} — PK 락이므로 별도 인덱스 불요.

2. applications (신규)

컬럼타입NULL근거/COMMENT
idBIGINT AINNPK
recruitment_idBIGINTNNrecruitment 논리 참조(FK 없음)
applicant_user_idBIGINTNN신청자 user 논리 참조
statusVARCHAR(20)NNPENDING/CONFIRMED/CANCELLED/REFUNDED
payment_idBIGINTNULL결제 논리 참조. 무료 모집은 NULL
audit 6

3. programs (신규)

컬럼타입NULL근거/COMMENT
idBIGINT AINNPK
facility_idVARCHAR(255)NNMongo facilities._id 논리 참조. slots.facility_id와 동일 타입
owner_user_idBIGINTNN시설 소유자(=program 소유자)
nameVARCHAR(200)NN상품명
descriptionVARCHAR(2000)NULL설명(선택)
priceDECIMAL(12,2)NN가격(≥0)
capacityINTNN회차 정원(≥1)
duration_minutesINTNN소요 시간(분, >0)
audit 6

4. community_bookings (신규)

컬럼타입NULL근거/COMMENT
idBIGINT AINNPK
community_idBIGINTNNcommunity 논리 참조
slot_idBIGINTNNbooking slots.id 논리 참조
linked_by_user_idBIGINTNN연결한 방장 user
audit 6
  • 중복 링크 멱등 가드는 UNIQUE 제약으로 (아래 인덱스). 표시용 시설·일시·정원은 SlotInfoGateway가 booking 조회 → community가 소유하지 않음.

5. posts 컬럼 추가 (기존 테이블 — 하위호환)

컬럼타입NULL하위호환 판단
community_idBIGINTNULL컬럼 추가(nullable) → 단일 마이그레이션. NULL=전역 게시글(FR-4 기존 동작 불변)
sport_categoryVARCHAR(30)NULL컬럼 추가(nullable) → 단일 마이그레이션. communities.sport_category와 동일 타입. NULL=미지정
global_listedTINYINT(1)NN DEFAULT 1BOOLEAN 금지→TINYINT(1). DEFAULT 1이 기존 전역 게시글 100%에 적용(전역=노출=정상값) → 단일 마이그레이션 안전 (아래 무중단 §근거)
  • global_listed는 not-null이지만 3단계 분리 불요: DEFAULT 1이 모든 기존 행에 유효한 정상값이므로(private-db-schema-convention의 “채우기” 단계가 DEFAULT로 원자 충족). 3단계는 “유효 기본값이 없을 때” 규칙 — 여기선 해당 없음.

6. slots 컬럼 추가 (기존 테이블 — 하위호환)

컬럼타입NULL하위호환 판단
program_idBIGINTNULL컬럼 추가(nullable) → 단일 마이그레이션. NULL=일반 슬롯, non-null=program 회차
statusVARCHAR(20)NULL DEFAULT ‘OPEN’TDD/과제 SSOT 요구대로 nullable DEFAULT ‘OPEN’. DEFAULT가 기존 행 100%를 ‘OPEN’으로 채움 → NULL은 실제로 발생 안 함. 코드는 NULL을 OPEN으로 방어적 해석. (implementer가 NOT NULL 승격 원하면 별도 판단 — 데이터상 안전)

slots UNIQUE 호환 확인 (과제 필수 항목)

  • 기존 UNIQUE uq_slots_facility_date_time_range(facility_id, date, time_range, deleted_at)변경하지 않습니다.
  • 자동 슬롯 멱등 재생성 호환: 자동 생성 슬롯은 program_id IS NULL. 멱등 재생성은 윈도우 내 기존 (facility_id, date, time_range) Set 조회 후 미존재분만 INSERT → 기존 UNIQUE가 중복을 차단(충돌 0). program_id/status 추가는 UNIQUE 키 구성에 없으므로 재생성 로직에 영향 없음. SlotGenerationDomainService.generate의 diff 계산 정합.
  • program 회차와의 충돌(문서화된 제약): program 세션 슬롯도 같은 UNIQUE 네임스페이스를 씁니다. 따라서 program 세션은 기존 일반 슬롯과 정확히 동일한 (facility_id, date, time_range)를 공유할 수 없습니다(다중 룸/코트 개념 부재). 충돌 시 INSERT가 UNIQUE 위반으로 실패 → 소유자가 다른 시간대 선택. program_id를 UNIQUE에 추가하는 대안은 불가: nullable program_id는 MySQL UNIQUE에서 NULL≠NULL로 취급되어 일반 슬롯 중복 멱등이 깨집니다. → 스키마 변경 없이 제약을 문서화하고 유지. (Open Question)

쿼리 패턴 → 인덱스 매핑

인덱스는 대상 쿼리가 근거입니다. 아래 표에 없는 인덱스는 만들지 않습니다(쓰기 비용). 인덱스 추가는 전부 ALGORITHM=INPLACE, LOCK=NONE 명시.

PRD A — posts

쿼리 ID쿼리 패턴 (WHERE / ORDER BY)인덱스 결정컬럼 순서 근거
A-Q1 전역 피드 기본deleted_at IS NULL AND global_listed=1 ORDER BY created_at DESC (paged, 최빈 hot path)신규 idx_posts_global_listed_deleted_at_created_at(global_listed, deleted_at, created_at)global_listed=1 equality → deleted_at IS NULL equality → created_at 정렬. 저카디널리티 선두지만 쿼리가 항상 =1 고정이라 filesort 제거가 목적
A-Q2 모임 게시글 목록community_id=? AND deleted_at IS NULL [AND sport_category=?] ORDER BY created_at DESC (GET /communities/{id}/posts)신규 idx_posts_community_id_deleted_at_created_at(community_id, deleted_at, created_at)community_id 고카디널리티 equality 선두 → deleted_at → created_at 정렬
A-Q3 종목별 전역 피드deleted_at IS NULL AND global_listed=1 AND sport_category=? ORDER BY created_at DESC신규 idx_posts_sport_category_global_listed_deleted_at_created_at(sport_category, global_listed, deleted_at, created_at)sport_category(12+NULL) equality 선두(선택도↑) → global_listed equality → deleted_at → created_at 정렬
A-Q4 종목 무관 type 필터type=? AND deleted_at IS NULL기존 idx_posts_type_deleted_at 재사용신규 불요
— keyword LIKEcontent LIKE '%kw%'인덱스 불가(선행 와일드카드) — 기존 한계 유지, 신규 인덱스 안 만듦
  • 3개 신규 인덱스는 posts 쓰기량이 낮은 개인 프로젝트 규모라 수용. 각 인덱스가 A-Q1/A-Q2/A-Q3에 1:1 대응.

PRD B

쿼리 ID대상쿼리 패턴인덱스 결정근거
B-Q1applicationsrecruitment_id=? AND status IN('PENDING','CONFIRMED') AND deleted_at IS NULL (countActiveByRecruitmentId, 정원 경합 hot)신규 idx_applications_recruitment_id_status_deleted_at(recruitment_id, status, deleted_at)recruitment_id equality 선두 → status(IN) → deleted_at. 오버부킹 0 카운트가 인덱스 타야 함
B-Q2applicationsrecruitment_id=? [AND status='CONFIRMED'] AND deleted_at IS NULL (findByRecruitmentId/findConfirmedByRecruitmentId)B-Q1 인덱스 접두로 커버별도 인덱스 불요
B-Q3recruitmentscommunity_id=? AND deleted_at IS NULL ORDER BY created_at DESC (GET /recruitments?communityId=)신규 idx_recruitments_community_id_deleted_at_created_at(community_id, deleted_at, created_at)community_id equality 선두 → deleted_at → created_at 정렬
B-Q4programsfacility_id=? AND deleted_at IS NULL (GET /facilities/{id}/programs)신규 idx_programs_facility_id_deleted_at(facility_id, deleted_at)facility_id equality 선두 → deleted_at
B-Q5community_bookingscommunity_id=? AND deleted_at IS NULL (GET /communities/{id}/bookings) + 중복 링크 멱등 가드 (community_id, slot_id)신규 UNIQUE uq_community_bookings_community_slot(community_id, slot_id, deleted_at)UNIQUE가 멱등 가드 + community_id 접두로 B-Q5 목록 조회까지 커버 → 별도 인덱스 불요
B-Q6slotsprogram_id=? AND deleted_at IS NULL ORDER BY date (program 회차 조회)신규 idx_slots_program_id_date_deleted_at(program_id, date, deleted_at)program_id equality 선두 → date 정렬 → deleted_at. program 세션 목록용
B-Q7slots자동생성 멱등: facility_id=? AND date BETWEEN ? ORDER BY date기존 idx_slots_facility_id_date_deleted_at 재사용신규 불요
B-Q8slots예약 시 slot.requireBookable() 상태 체크PK 로드(id) → status 컬럼 read. 인덱스 불요status 단독 조회 쿼리 없음 → status 인덱스 안 만듦
B-Q9bookingsprogram 회차 정원 카운트 slot_id=? AND status IN(...)기존 idx_bookings_slot_id_status(V34) 재사용program 예약이 기존 booking 경로 재사용 → 락 경로 스키마 그대로 지원 (아래 §동시성)

동시성 락 경로 스키마 지원 확인 (과제 필수)

경로락 전략스키마 지원
recruitment 신청 정원(오버부킹 0)분산락 recruitment:{id} + findForUpdateById(recruitments PK 비관락) + B-Q1 카운트recruitments PK + idx_applications_recruitment_id_status_deleted_at 로 지원 ✓
program/소모임 회차 예약(오버셀 0)기존 booking:slot:{slotId} 분산락 + findForUpdateById(slots PK 비관락) + idx_bookings_slot_id_status 카운트기존 경로 재사용, 스키마 변경 없음. slots에 status/program_id 추가는 락 경로에 무영향(status는 requireBookable 게이트로 PK 로드 시 확인) ✓

MongoDB — facilities validator 설계

임베딩 vs 참조 판단

필드결정근거
operating_hours (요일별 운영시간)임베딩facility 소유 속성, 항상 시설과 함께 로드, 상한 있는 크기(요일 ≤7 엔트리) → private-mongodb-convention 임베딩 기준 3종 충족. 무한 성장 없음
holidays (휴무일)임베딩facility 소유, 유한(연 수십 건 상한), 함께 조회. 무한 성장 아님. 다년 누적이 우려되면 과거 날짜 정리(아래 보존정책)
  • 16MB 문서 한계: 운영시간 7 + 휴무 수십 건은 KB 단위 → 무관.

문서 구조 예시 (임베드 후)

{
  "_id": "...", "code": "...", "name": "...", "type": "...", "location": { ... },
  "owner_user_id": 42,
  "operating_hours": [
    { "day_of_week": "MON", "open": "09:00", "close": "22:00",
      "breaks": [ { "start": "12:00", "end": "13:00" } ],
      "slot_duration_minutes": 60, "capacity": 1 }
  ],
  "holidays": [ "2026-08-15", "2026-09-28" ]
}

$jsonSchema validator 초안 (신설 — 기존 validator 없음)

중요: 현재 facilities에는 validator가 전혀 없습니다($jsonSchema 0건). 이번 과제는 validator 갱신이 아니라 신설입니다. 전국 시설 문서가 다수 존재하므로:

  • validationLevel: "moderate" — 신규/수정 문서만 검증, 기존 문서 소급 검증 없음 (컨벤션 기본). 운영시간 미등록 기존 시설이 소유자 owner 배정 등으로 update될 때 필수 필드가 이미 있으므로 통과.
  • operating_hours·holidaysoptional — 소유자가 나중에 등록하는 필드. required로 걸면 파괴적(기존 문서 update 차단) → 걸지 않음.
  • required는 모든 문서에 항상 존재하는 코어 필드만(code·name·type·location). region/meta/owner_user_id는 유입 경로 다양성 고려해 optional로 둬 수집 파이프라인을 깨지 않음.

validator 초안(구현자 참조 — 실 스크립트는 mongo/migration/ 담당):

{
  $jsonSchema: {
    bsonType: "object",
    required: ["code", "name", "type", "location"],
    properties: {
      code: { bsonType: "string" },
      name: { bsonType: "string" },
      type: { bsonType: "string" },
      location: { bsonType: "object" },
      owner_user_id: { bsonType: ["long", "int", "null"] },
      operating_hours: {
        bsonType: "array",
        items: {
          bsonType: "object",
          required: ["day_of_week", "open", "close", "slot_duration_minutes", "capacity"],
          properties: {
            day_of_week: { enum: ["MON","TUE","WED","THU","FRI","SAT","SUN"] },
            open:  { bsonType: "string" },
            close: { bsonType: "string" },
            breaks: { bsonType: "array" },
            slot_duration_minutes: { bsonType: "int", minimum: 1 },
            capacity: { bsonType: "int", minimum: 1 }
          }
        }
      },
      holidays: { bsonType: "array", items: { bsonType: "string" } }
    }
  }
}
  • 인덱스: operating_hours/holidays 대상 쿼리 없음(항상 facility 문서와 함께 로드) → 신규 인덱스 안 만듦. 기존 idx_gu_type·idx_sido_sigungu_type·2dsphere 유지.
  • 파괴적 변경 주의: validator를 strict로 올리거나 operating_hours를 required로 강화하는 것은 파괴적 → 하지 않음(사용자 확인 대상).

용량 추정·보존 정책

개인 프로젝트 상시 RPS<10 전제(진단 §2). 1년 후 추정:

테이블행 폭(대략)증가율 가정1년 후 행수1년 후 크기(대략)보존/아카이빙
recruitments~0.3KB월 50건~600<1MB무한 성장 아님. 보존 정책 불요
applications~0.1KB모집당 ~20 × 600~12,000~2MB소량. 불요
programs~0.3KB시설당 수개~수백<1MB불요
community_bookings~0.1KB소량~수천<1MB불요
posts (증분)컬럼 3개 추가기존 대비 미미+수 byte/행불요
slots~0.2KB자동생성: 시설 N × 슬롯/일 × 14일 롤링시설 100 × 10슬롯/일 × 365 ≈ 365,000/년~70MB/년무한 성장 — 정책 필요

slots 보존 정책 (무한 성장 대상)

  • 자동 생성이 매일 향후 14일치를 롤링 생성 → 과거 날짜 슬롯이 무한 누적됩니다.
  • MySQL은 TTL 인덱스가 없으므로: 주기 배치 소프트 삭제/아카이빙 — 활동 완료 후 일정 기간(예: 90일) 지난 date < now-90d 슬롯을 soft-delete(deleted_at 세팅). bookings 참조 무결성상 하드 삭제 금지. 필요 시 별도 slots_archive 테이블로 이관은 규모 도달 시(YAGNI, 현재 미채택).
  • 이 정책은 자동생성 스케줄러와 짝을 이루는 별도 정리 배치로 후속 설계(현 과제 범위는 스키마까지 — Open Question).

Mongo holidays 보존

  • holidays 배열에 과거 날짜가 누적될 수 있음 → 임베드 배열 상한 관리: 소유자 UI/배치에서 지난 연도 휴무 정리 권장(무한 성장 방지). 상한이 낮아 긴급도 낮음.

무중단 마이그레이션 (expand-contract)

전 변경이 additive(신규 테이블·nullable 컬럼·유효 DEFAULT·validator moderate)라 expand-contract 무중단. 배포 순서: 스키마 먼저 → 코드(플래그 OFF) → 플래그 점진 ON — 두 senior-be Release Scenario와 정합. 락 영향은 변경마다 명시.

단계작업락/온라인 DDL롤백 지점(역방향)
S1. MySQL 신규 테이블 (V56~)recruitments·applications·programs·community_bookings CREATE (audit 6 + 위 인덱스·UNIQUE 포함)신규 테이블 CREATE → 기존 테이블 무락DROP TABLE ... (코드 미배포라 안전)
S2. posts 컬럼 추가community_id(NULL)·sport_category(NULL)·global_listed(TINYINT NN DEFAULT 1)MySQL 8 INSTANT ADD COLUMN(DEFAULT 포함) → 무락. DEFAULT 1이 기존 행 원자 채움3컬럼 DROP COLUMN
S3. posts 인덱스 추가A-Q1/A-Q2/A-Q3 인덱스 3종ALGORITHM=INPLACE, LOCK=NONE 명시DROP INDEX
S4. slots 컬럼 추가program_id(NULL)·status(NULL DEFAULT ‘OPEN’)INSTANT ADD COLUMN → 무락. DEFAULT ‘OPEN’ 기존 행 채움. UNIQUE 무변경(멱등 호환 유지)2컬럼 DROP COLUMN
S5. slots 인덱스 추가B-Q6 idx_slots_program_id_date_deleted_atALGORITHM=INPLACE, LOCK=NONEDROP INDEX
S6. Mongo validator 신설facilities에 $jsonSchema + validationLevel:"moderate" collMod. operating_hours/holidays optional컬렉션 메타 변경(문서 재작성 없음, moderate) → 사실상 무영향validator 제거 collMod {validator:{}, validationLevel:"off"}
S7. 코드 배포(플래그 OFF)신규 도메인·컨트롤러 배포하되 recruitment.enabled/facility.autoslot.enabled/facility.program.enabled/community.booking.enabled=false이전 이미지 태그 compose 재기동
S8. 플래그 점진 ONcommunity.booking → facility program/autoslot → recruitment 순 (senior-be B)플래그 OFF 즉시 비활성(경로 404/스케줄러 정지)
  • autoslot 초기 OFF 필수: facility.autoslot.enabled=true 전까지 배치 미실행 → 대량 슬롯 생성 사고 방지(senior-be B 정합).
  • S1~S6은 코드 미배포 상태(플래그 OFF 배포 전)라 전부 코드 롤백만으로 안전. DB 마이그레이션 롤백 시 역방향 DDL은 위 표 우측 열.
  • 마이그레이션 파일 명명: V{YYYYMMddHHmm}__{snake_case}.sql (Mongo는 V{ts}__{desc}.js). 각 파일 상단에 롤백 역방향 DDL 주석 명시(implementer 담당).

자가 점검 (TDD 도메인 필드 ↔ 컬럼 대응)

TDD 도메인 필드대응 컬럼/필드확인
Post.communityId/sportCategory/globalListedposts.community_id/sport_category/global_listed
Recruitment: capacity/feeAmount/activityAt/applicationDeadline/communityId/recruiterUserId/statusrecruitments 동명 컬럼
Application: recruitmentId/applicantUserId/status/paymentIdapplications 동명 컬럼
Program: facilityId/ownerUserId/price/capacity/durationMinutesprograms 동명 컬럼
Slot 확장: programId/statusslots.program_id/status
CommunityBooking: communityId/slotId/linkedBycommunity_bookings.community_id/slot_id/linked_by_user_id
OperatingHours/Holidayfacilities.operating_hours/holidays 임베드

컨벤션 위반 점검: FK 0건(전부 논리 참조), ENUM 0건(status/visibility VARCHAR), BOOLEAN 0건(global_listed TINYINT(1)), JSON 컬럼 0건, 시간 전부 DATETIME(6), audit 6컬럼 전 신규 테이블 포함, PK=id — 위반 없음.

Open Questions

  • slots 과거 슬롯 정리 배치: 무한 성장 방지 소프트삭제 배치는 후속 설계(스키마는 준비됨).
  • program 세션 ↔ 일반 슬롯 UNIQUE 충돌: 동일 (facility,date,time_range) 공유 불가(다중 룸 개념 부재). 룸/코트 개념 도입 시 UNIQUE 재설계 필요.
  • slots.status NOT NULL 승격: 현재 nullable DEFAULT ‘OPEN’(과제 SSOT). DEFAULT로 NULL 미발생 — implementer가 NOT NULL 강제 원하면 데이터상 안전하게 승격 가능.

Document History

날짜변경 내용
2026-07-07최초 작성 — 저장소 선택(facility만 Mongo), posts 3컬럼·slots 2컬럼 추가, 신규 4테이블, 쿼리→인덱스 매핑(posts 3·applications 1·recruitments 1·programs 1·community_bookings UNIQUE·slots 1), facilities validator 신설(moderate·operating_hours optional), slots 보존정책, expand-contract 무중단 순서