Skip to content

Feat/#61 slot generator - #65

Merged
jaebeom79 merged 11 commits into
mainfrom
feat/#61-slot-generator
May 15, 2026
Merged

Feat/#61 slot generator#65
jaebeom79 merged 11 commits into
mainfrom
feat/#61-slot-generator

Conversation

@johe00123

Copy link
Copy Markdown
Contributor

📢 기능 설명

필요시 실행결과 스크린샷 첨부

연결된 issue

연결된 issue를 자동을 닫기 위해 아래 {이슈넘버}를 입력해주세요.

close #{61}

✅ 체크리스트

  • PR 제목 규칙 잘 지켰는가?
  • 추가/수정사항을 설명하였는가?
  • 이슈넘버를 적었는가?

johe00123 added 10 commits May 13, 2026 11:52
- 충돌 해결 (병합)
- 매일 새벽 4시 "오늘 + 30일" 날짜의 슬롯을 활성 매장에 생성
- 슬라이딩 윈도우 방식으로 항상 30일치 미래 슬롯 유지
- 이미 슬롯이 있는 매장/날짜는 스킵
- 기존: \"이미 그 날짜 슬롯이 1건이라도 있으면 스킵\" -> 부분 생성 시 나머지 누락
- 변경: 매장별 기대 슬롯과 실제 슬롯을 차집합으로 비교, 부족한 슬롯만 보충
- Repository에 findRemainTimesByStoreIdAndRemainDate 쿼리 추가
- buildSlotTimes 헬퍼 메서드로 슬롯 시간 생성 로직 분리
- 기존: 오늘+30일 단 하루치만 생성 -> 첫 실행 시 사이 날짜 비어있음
- 변경: 오늘 ~ 오늘+29일(총 30일) 범위를 매일 순회하며 보충
- 차집합 중복 방지 로직 덕분에 매일 돌아도 부담 없음
- 기존: \"이미 그 날짜 슬롯이 1건이라도 있으면 스킵\" -> 부분 생성 시 나머지 누락
- 변경: 매장별 기대 슬롯과 실제 슬롯을 차집합으로 비교, 부족한 슬롯만 보충
- Repository에 findRemainTimesByStoreIdAndRemainDate 쿼리 추가
- buildSlotTimes 헬퍼 메서드로 슬롯 시간 생성 로직 분리
- 파일 이름: StoreRemainService.java
- POST /api/v1/remains: Swagger 테스트용 수동 슬롯 생성 엔드포인트
- 스케줄러로 자동화되어 사용처가 없어졌으나 시딩/복구 시 부활 가능하도록 주석만 처리
- 매장 조회를 스케줄러 1회로 통합 (30일 루프 중복 제거)
- 슬롯 차집합 N+1 쿼리 제거 (날짜별 일괄 조회 1회로 변경)
- 매장 청크(500개) 단위 REQUIRES_NEW 트랜잭션 분리 (StoreRemainSlotWriter)
- DateTimeFormatter 클래스 상수화
- generateMonthlyRemain에서 buildSlotTimes 재사용
- 미사용 메서드/변수 제거 (existsByStoreIdAndRemainDate, ReservationService.create 변수)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an automated scheduler to generate reservation slots for a 30-day window, replacing the previous manual endpoint. The implementation includes a chunking strategy and a dedicated transaction writer to handle large datasets. Review feedback highlights several areas for improvement: addressing potential memory issues when fetching existing slots for all stores at once, improving type safety by replacing generic Object[] results with DTOs or projections, optimizing performance by reducing redundant time parsing within loops, and removing dead code instead of leaving it commented out.

* @return 슬롯이 보충된 매장 수
*/
public int generateDailySlotsForStores(List<Store> stores, LocalDate targetDate) {
Map<Long, Set<LocalTime>> existingByStore = fetchExistingTimesByStore(targetDate);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

fetchExistingTimesByStore 메서드는 특정 날짜의 모든 매장 슬롯 데이터를 한 번에 메모리로 로드합니다. 매장 수가 수만 개 이상으로 늘어날 경우 Map<Long, Set<LocalTime>>이 차지하는 메모리 양이 급격히 증가하여 OutOfMemoryError가 발생할 위험이 있습니다. 현재 매장 목록을 청크 단위로 나누어 처리하고 있으므로, 해당 청크에 포함된 매장 ID들만 필터링하여 기존 슬롯을 조회하도록 쿼리를 개선하는 것이 확장성 측면에서 안전합니다.

Comment on lines +24 to +33
// 스케줄러 도입 후 사용 안 함. 기존 swagger 에서 수동으로 생성하던 POST /api/v1/remains 주석 처리.
// @PostMapping
// public ResponseEntity<ApiResponse<Void>> generateMonthlyRemain(
// @Valid @RequestBody StoreRemainCreateRequestDto request
// ) {
// storeRemainService.generateMonthlyRemain(request);
// return ResponseEntity
// .status(SuccessCode.REMAIN_CREATE_SUCCESS.getHttpStatus())
// .body(ApiResponse.success(SuccessCode.REMAIN_CREATE_SUCCESS));
// }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

주석 처리된 코드는 가독성을 저해하고 유지보수 비용을 높입니다. 스케줄러 도입으로 더 이상 사용하지 않는 기능이라면 코드를 주석으로 남겨두기보다 삭제하는 것을 권장합니다. 필요한 경우 Git 히스토리를 통해 언제든지 확인하거나 복구할 수 있습니다.

);

@Query("SELECT sr.store.id, sr.remainTime FROM StoreRemain sr WHERE sr.remainDate = :date")
List<Object[]> findStoreIdAndTimesByDate(@Param("date") LocalDate date);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

List<Object[]>를 반환하는 방식은 데이터의 순서나 타입에 의존적이어서 런타임 에러가 발생할 가능성이 있으며 유지보수가 어렵습니다. Interface Projection이나 별도의 DTO(Record 등)를 정의하여 타입 안정성을 확보하는 것을 권장합니다.

Comment on lines +169 to +172
LocalTime openTime = LocalTime.parse(store.getOpenTime(), TIME_FORMATTER);
LocalTime closeTime = LocalTime.parse(store.getCloseTime(), TIME_FORMATTER);

List<LocalTime> expectedTimes = buildSlotTimes(openTime, closeTime);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

buildMissingSlotsForStore 메서드는 루프 내부에서 호출되며, 매번 영업시간 문자열을 파싱하고 슬롯 목록을 생성합니다. 스케줄러를 통해 30일치 데이터를 생성할 때 동일한 매장에 대해 이 연산이 30번 반복되므로 성능 저하의 원인이 될 수 있습니다. 매장별 슬롯 구성을 미리 계산하여 재사용하거나, Store 엔티티에서 파싱된 LocalTime을 반환하도록 개선하는 것을 고려해 보세요.

- 청크 단위로 기존 슬롯 조회하도록 변경하여 OOM 위험 제거 (메모리 ~95MB -> ~0.4MB)
- Object[] 반환 대신 StoreRemainTimeView Projection으로 타입 안전성 확보
- StoreSlotPlan record로 매장 영업시간 파싱/슬롯 시간 계산을 1회로 단축
  (파싱 호출 354만회 -> 11.8만회)
- store_remain 테이블에 (remain_date, store_id), (store_id, remain_date) 복합 인덱스 추가
@jaebeom79
jaebeom79 merged commit 4beacd6 into main May 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants