Skip to content

Refactor: 프론트엔드 구현 및 여러 기능 수정#117

Merged
CheatIsKey merged 43 commits into
developfrom
refactor/Index-Cache-N+1#115
Apr 27, 2026
Merged

Refactor: 프론트엔드 구현 및 여러 기능 수정#117
CheatIsKey merged 43 commits into
developfrom
refactor/Index-Cache-N+1#115

Conversation

@CheatIsKey
Copy link
Copy Markdown
Member

💡 개요

리팩토링을 통해 코드 개선 및 테스트 코드 작성

🛠️ 작업 내용

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 4위 (리뷰 97건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

안녕하세요! 이번 PR은 단순한 기능 구현을 넘어, 성능 최적화와 아키텍처 개선에 대한 고민이 많이 느껴지는 아주 훌륭한 결과물입니다. 특히 N+1 문제 해결과 트랜잭션 전파 속성을 활용한 동시성 처리 방식은 실무에서도 자주 사용되는 패턴인데, 이를 잘 적용해 주셨네요.

주니어 단계에서 놓치기 쉬운 세밀한 부분들을 중심으로 리뷰를 작성했습니다.


📋 과제 요구사항 준수 여부

  • 동시성 제어: ChatInactivityScheduler에서 RedisLockRepository를 사용하는 모습이 보이나, 필수 요구사항인 Lettuce 기반의 SETNX 구현체의 상세 로직은 이번 diff에 포함되지 않았습니다. 기존 구현을 유지하신 것으로 보이며, 락 획득 실패 시의 전략(Retry/Fail Fast)이 요구사항에 부합하는지 점검이 필요합니다.
  • 캐싱 성능 개선: 검색 API v1(LIKE 조건)은 QueryDSL로 잘 구현되었습니다. 다만, Search API v2 (Caffeine Cache) 적용 부분이 이번 diff에서는 확인되지 않습니다. 필수 기능이므로 누락되었다면 추가 구현이 필요합니다.
  • 도전 기능 (감지됨):
    • 실시간 채팅: STOMP Interceptor를 통한 JWT 인증 및 Redis Pub/Sub 브로드캐스팅이 잘 구현되었습니다. 🔴 필수 수정 사항 참조.
    • 인덱스 최적화: Order, OrderProduct 엔티티에 인덱스를 직접 설정하신 점이 매우 좋습니다.
    • 더미 데이터: 5만 건의 데이터를 적재하는 로직이 포함되었습니다.

🟢 잘된 점

  1. N+1 문제의 완벽한 해결: OrderQueryServiceImpl에서 주문 목록 조회 시 발생하는 N+1 문제를 IN 쿼리와 메모리 그룹핑(Collectors.groupingBy)을 통해 1+1 쿼리로 최적화하신 점은 백엔드 개발자로서 매우 훌륭한 자세입니다.
  2. 트랜잭션 전파 속성(REQUIRES_NEW) 활용: ChatRoomCreator를 별도 빈으로 분리하여 REQUIRES_NEW를 적용한 점이 인상적입니다. 동시성 이슈로 인한 예외 발생 시 부모 트랜잭션의 영속성 컨텍스트(엔티티를 관리하는 메모리 공간)를 오염시키지 않고 재시도할 수 있는 구조를 잘 설계하셨습니다.
  3. 배치 삽입(Batch Insert) 적용: OrderCommandServiceImpl에서 루프 내 save 대신 saveAll을 사용하도록 변경하여 DB 네트워크 IO를 줄이려 노력하신 점을 칭찬합니다.

🔴 필수 수정

1. WebSocket 인증 보안 취약점

JwtHandshakeInterceptor에서 쿠키의 JWT를 추출하여 attributes에 저장하고 있으나, beforeHandshake에서 토큰 유효성 검증(validateToken) 로직이 누락되어 있습니다. 유효하지 않은 토큰을 가진 연결도 세션을 생성하게 되어 자원 낭비 및 보안 문제가 발생할 수 있습니다.

// JwtHandshakeInterceptor.java
- if (AuthConstants.ACCESS_TOKEN.equals(cookie.getName())) {
-     attributes.put("jwt_token", cookie.getValue());
-     break;
- }
+ if (AuthConstants.ACCESS_TOKEN.equals(cookie.getName())) {
+     String token = cookie.getValue();
+     if (jwtTokenProvider.validateToken(token)) { // 유효성 검사 필수!
+         attributes.put("jwt_token", token);
+     }
+     break;
+ }

🟡 권장 개선

1. LIKE 검색 성능 최적화 (Full Table Scan 방지)

ProductRepositoryCustomImpl에서 containsIgnoreCase를 사용하셨는데, 이는 SQL에서 %keyword% 형태의 LIKE 문을 생성합니다. 이 방식은 인덱스를 탈 수 없어 데이터가 많아지면 매우 느려집니다.

  • 개선 제안: 검색어 앞에만 와일드카드를 붙이는 startsWith(keyword%)를 사용하거나, 대량 데이터 검색이 중요하다면 Full Text Index 또는 Elasticsearch 도입을 고려해 보세요.

2. 관리자 인터셉터 응답 방식 개선

AdminInterceptor에서 권한이 없을 경우 sendRedirect를 사용하고 있습니다. 현재 프로젝트가 API 서버와 프론트엔드(Thymeleaf)가 공존하는 구조이지만, /api/로 시작하는 요청에 대해서는 302 리다이렉트보다 403 Forbidden 에러 코드를 내려주는 것이 RESTful한 설계입니다.

// AdminInterceptor.java
if (!"ADMIN".equals(userInfo.role())) {
+   if (request.getRequestURI().startsWith("/api/")) {
+       response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
+       return false;
+   }
    response.sendRedirect("/");
    return false;
}

💡 학습 포인트

  1. 트랜잭션 전파 속성 (Transaction Propagation): REQUIRES_NEW는 부모 트랜잭션과 독립된 트랜잭션을 만듭니다. 이번에 사용하신 "예외 발생 시 독립적 롤백" 외에도 "로그 기록" 등 부모의 성공 여부와 상관없이 실행되어야 하는 로직에 자주 쓰입니다.
  2. 복합 인덱스의 순서 (Composite Index): Order 엔티티에 user_id, id DESC 순서로 인덱스를 거셨는데, 이는 Leftmost Prefix 규칙 때문입니다. 조건절에 user_id가 항상 포함되어야 이 인덱스를 효율적으로 탈 수 있습니다.

🤔 생각해보기

"현재 OrderQueryServiceImpl에서 5만 건의 주문을 조회한다고 가정할 때, productsByOrderId라는 Map을 메모리에 통째로 올리는 방식은 어떤 잠재적 위험이 있을까요? 데이터가 더 늘어난다면 어떻게 개선할 수 있을까요?"

💬 이 질문에 대해 궁금한 점이 있으면 코멘트에 @sparta 를 남겨보세요!
예: @sparta 이 부분에서 메모리 부하를 줄이려면 페이징 처리를 어떻게 연동해야 하나요?


AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

Copy link
Copy Markdown
Contributor

@DGAZA-max DGAZA-max left a comment

Choose a reason for hiding this comment

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

대박; 고생 많으셨습니다!

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 4위 (리뷰 98건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

📋 과제 요구사항 준수 여부

  • 검색 API v1 (LIKE 조건 검색): QueryDSL의 BooleanExpression을 활용하여 categoryEq, nameContains 메서드로 동적 쿼리와 LIKE 검색을 아주 잘 구현했습니다. 과제의 핵심 요구사항을 정확하게 충족했습니다.
  • QueryDSL 동적 쿼리: Projections.constructor()를 통한 DTO 직접 조회는 아니지만, 동적 조건(BooleanExpression) 생성은 잘 해내셨습니다.
  • 사용 금지 기술 (Redisson): 이번 diff에서 감지되지 않았습니다. (Lettuce 기반 유지)

🟢 잘된 점

  1. 동적 정렬(Sorting) 처리: ProductRepositoryCustomImpl에서 Pageable의 정렬 조건에 따라 OrderSpecifier를 동적으로 생성하는 로직은 실무에서도 자주 쓰이는 정석적인 패턴입니다.
  2. 인덱스 전략: Order 엔티티에 커서 페이징용(user_id, id DESC)과 상태 조회용(order_status) 인덱스를 명확하게 분리하여 설계한 점은 매우 훌륭합니다. 운영 환경에서 DB 부하를 줄이는 데 직결됩니다.
  3. 조회 성능 최적화 (N+1 해결 및 IN 쿼리): findByOrderIdIn을 도입하여 메모리 상에서 그룹핑(Collectors.groupingBy)하도록 개선한 점은 완벽합니다. 1+N 문제를 1+1로 해결하여 네트워크 I/O와 DB 부하를 동시에 줄였습니다.

🟡 개선 제안

  1. nameContains의 대소문자 무시 방식: product.name.containsIgnoreCase(keyword)는 결국 SQL에서 LOWER(product.name) LIKE LOWER('%keyword%')로 변환됩니다. DB 컬럼에 함수(LOWER)를 씌우면 인덱스를 타지 못하고 Full Table Scan을 유발합니다. 데이터가 5만 건 이상 쌓이면 검색 API가 느려질 것입니다.

    • 해결책: DB Collation을 utf8mb4_0900_ai_ci(MySQL 8.0 기본) 등 대소문자 무시(Case-Insensitive) 설정으로 변경하고, product.name.contains(keyword)를 사용하여 인덱스를 탈 수 있게 유도하는 것이 좋습니다.
  2. 카테고리 예외 삼키기 (Silent Catch): categoryEq 메서드에서 IllegalArgumentException을 catch하고 null을 반환하고 있습니다. 프론트엔드에서 오타가 난 카테고리 값을 보냈을 때 아무런 에러 없이 빈 데이터가 내려가므로 디버깅이 매우 어렵습니다.

    • 해결책: 잘못된 카테고리 요청은 null을 반환하지 말고 명시적으로 400 Bad Request 예외를 던져 클라이언트에게 알려주어야 합니다.
  3. 중복된 어노테이션 및 import: AuthController.java 상단에 @RequestMapping@RestController가 중복 선언되어 있습니다. 컴파일 에러는 나지 않겠지만, 코드는 깔끔하게 관리해야 합니다.

🔴 보안 / 성능 주의

  1. WebSocket 인증 검증 누락 (미해결): 이전 리뷰에서 지적한 JwtHandshakeInterceptor토큰 검증(validateToken) 누락 문제가 여전히 반영되지 않았습니다. attributes.put("jwt_token", cookie.getValue()) 부분에서 만료되었거나 위조된 토큰인지조차 검사하지 않고 무조건 세션을 열어주고 있습니다. 실시간 채팅 API는 악의적인 사용자가 무한히 연결을 시도할 수 있는 큰 공격 벡터가 되므로 반드시 beforeHandshake 단계에서 토큰 유효성을 검증하고 실패 시 return false;를 던져 연결을 차단해야 합니다.
  2. 관리자 인터셉터 (AdminInterceptor) API 스펙 파괴: 이전 리뷰에서 지적한 내용이 반영되지 않았습니다. preHandle에서 권한 없는 API 요청이 들어올 경우 response.sendRedirect("/")를 내리고 있습니다. REST API 클라이언트(React, Vue 등)는 302 Redirect를 처리하지 않고 에러로 취급합니다. /api/** 경로에 대해서는 response.sendError(HttpServletResponse.SC_FORBIDDEN)로 403 상태 코드를 반환하도록 분기 처리가 필수입니다.

💡 학습 포인트

  • JPA 하이버네이트 배치(Batch) Insert: orderProductRepository.saveAll(orderProducts)를 사용하셨는데, JPA의 saveAll이 실제 DB에 "Batch Insert"로 날아가려면 application.properties 또는 yaml 파일에 spring.jpa.properties.hibernate.jdbc.batch_size=50 (또는 그 이상) 설정이 반드시 있어야 합니다. 이 설정이 없다면 saveAll은 결국 건당 1개의 INSERT 쿼리를 DB에 전송합니다.

🤔 생각해보기

"현재 ProductRepositoryCustomImpl에서 pageable.getSort()를 순회하며 정렬 조건을 적용하고 있습니다. 만약 프론트엔드 개발자가 실수로 정렬 기준으로 id, name, price가 아닌 unknown_field라는 파라미터를 보내면, 백엔드 서버와 데이터베이스에서는 어떤 일이 발생하며 이를 운영 환경에서 어떻게 방어해야 할까요?"

💬 이 질문에 대해 궁금한 점이 있으면 코멘트에 @sparta 를 남겨보세요!
예: @sparta QueryDSL에서 지원하지 않는 정렬 필드가 들어왔을 때 가장 우아하게 400 에러를 내는 방법은 무엇인가요?

🔄 이전 피드백 반영 여부

  • WebSocket 인증 검증 로직 추가 (validateToken): 미해결 (🔴 보안 주의 항목 참고)
  • 관리자 인터셉터 API 응답 방식 개선 (403 반환): 미해결 (🔴 보안 주의 항목 참고)
  • 인기 검색어 길이 제한 (length = 100): 해결 (PopularKeyword 엔티티에 반영 확인)
  • 주문 생성 Batch Insert (saveAll): 해결 (OrderCommandServiceImpl에 반영 확인)

AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 4위 (리뷰 99건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

안녕하세요! 10년 차 Java/Spring Boot 개발자이자 여러분의 코드 리뷰 멘토입니다.

이번 PR은 단순한 기능 수정을 넘어, 이전 피드백을 아주 성실하게 반영해주셨고, 특히 트랜잭션 격리(REQUIRES_NEW)와 N+1 문제 해결을 위해 고민하신 흔적이 역력하여 매우 인상적입니다. 실무에서도 이 정도 수준의 고민을 하는 주니어는 팀 내에서 굉장히 환영받습니다.

다만, 과제 필수 요구사항 중 일부 누락된 부분과 코드의 완결성을 위해 몇 가지 짚어드릴 포인트가 있습니다.


📋 과제 요구사항 준수 여부

  • 동시성 제어 (Lettuce 기반): ChatInactivityScheduler에서 RedisLockRepository를 호출하는 등 구조는 갖춰져 있습니다. 다만, 실제 락의 상세 구현(SETNX, Lua Script)은 이번 diff에 포함되지 않아 내부 로직이 요구사항대로 UUID 기반 원자적 해제를 수행하는지 점검이 필요합니다.
  • 검색 API v1 (LIKE 검색): QueryDSL을 이용해 아주 깔끔하게 구현되었습니다.
  • 검색 API v2 (Caffeine Cache): 🔴 필수 수정. 지난 리뷰에 이어 이번에도 Caffeine Cache 설정 및 @Cacheable 적용 코드가 보이지 않습니다. 필수 기능이므로 반드시 구현이 필요합니다.
  • 도전 기능 (채팅/인덱스/더미): WebSocket 인증 보안 강화, DB 인덱스 설계, 5만 건 데이터 적재 등 도전 기능을 훌륭하게 수행 중입니다.

🔄 이전 피드백 반영 여부

지난 리뷰에서 지적된 보안 및 아키텍처 이슈가 완벽하게 반영되었습니다!

  • WebSocket 인증 검증: JwtHandshakeInterceptorjwtTokenProvider.validateToken(token)이 추가되어 보안 허점이 메워졌습니다.
  • AdminInterceptor API 응답: 권한 부족 시 /api/ 요청에 대해 403 Forbidden을 반환하도록 분기 처리가 완료되었습니다.
  • DB 제약 사항: PopularKeyword의 길이 제한(100) 및 saveAll을 통한 배치 삽입이 적용되었습니다.

🟢 잘된 점

  1. 트랜잭션 전파 속성(REQUIRES_NEW)의 정석적 활용: ChatRoomCreator를 별도 빈(Bean)으로 분리하여 부모 트랜잭션과 분리한 점은 매우 훌륭합니다. 이는 동시성 이슈로 인한 DB 충돌 시 세션 오염 없이 재조회를 가능케 하는 고급 패턴입니다.
  2. 성능 중심의 인덱스 설계: OrderOrderProduct 엔티티에 커서 페이징과 상태 조회를 고려한 복합 인덱스를 명시적으로 선언한 점은 운영 환경을 고려한 시니어급 접근 방식입니다.
  3. N+1 문제 해결 (1+1 전략): OrderQueryServiceImpl에서 주문 목록 조회 시 상품 정보를 IN 쿼리로 한 번에 가져와 메모리에서 매칭(groupingBy)한 로직은 백엔드 성능 최적화의 핵심입니다.

🔴 필수 수정

1. Caffeine Local Cache (v2) 구현 누락

과제 필수 요구사항인 v2: Caffeine Cache 적용이 보이지 않습니다. v1(LIKE 검색)과 성능을 비교하기 위한 핵심 장치이므로 추가가 필요합니다.

2. QueryDSL 조건문 오타 및 미완성 코드

ProductRepositoryCustomImpl의 최하단 nameContains 메서드 로직이 잘려 있거나 오타가 있을 가능성이 높습니다. (diff 상에서 .i로 끝남)

// ProductRepositoryCustomImpl.java
private BooleanExpression nameContains(String keyword) {
-    if (keyword == null || keyword.trim().i
+    if (!StringUtils.hasText(keyword)) {
+        return null;
+    }
+    return product.name.containsIgnoreCase(keyword);
}

🟡 권장 개선

1. AdminInterceptor의 인증 정보 추출 방식 개선

현재 SecurityContextHolder에서 직접 꺼내고 있는데, 이미 Spring Security 필터를 거친 상태이므로 인터셉터보다는 **Security Config의 authorizeHttpRequests**에서 경로별 권한을 설정하는 것이 더 표준적입니다. 현재 방식은 유지보수 시 설정이 분산되어 찾기 어려울 수 있습니다.

2. 채팅방 자동 종료 배치 성능

batchAutoCloseRooms에서 saveAll을 사용하고 있는데, 만약 종료할 방이 수천 개라면 영속성 컨텍스트에 수천 개의 메시지 객체가 올라갑니다. 이 경우 JDBC Batch Insert를 사용하거나 일정 단위(예: 100개)로 끊어서 처리하는 것이 안전합니다.


💡 학습 포인트

  1. 트랜잭션과 프록시(Proxy): 왜 REQUIRES_NEW를 사용할 때 ChatRoomCreator처럼 별도 클래스로 분리해야 했을까요? 같은 클래스 내부 호출 시에는 Spring의 AOP 프록시가 작동하지 않아 @Transactional이 무시되는 Self-invocation 문제를 이해하는 것이 중요합니다.
  2. 커서 페이징(Cursor Paging): Order 엔티티에 적용한 idx_orders_user_id_id 인덱스가 왜 id DESC를 포함해야 하는지, 그리고 대용량 데이터에서 OFFSET 방식보다 왜 유리한지 정리해 보세요.

🤔 생각해보기

"현재 ChatRoomCreator에서 REQUIRES_NEW를 사용하여 별도 트랜잭션으로 방을 생성하고 있습니다. 만약 방 생성은 성공했는데, 직후에 부모 트랜잭션에서 다른 로직 때문에 예외가 발생한다면, 생성된 채팅방은 롤백될까요, 그대로 남을까요? 이 현상이 비즈니스적으로 어떤 영향을 줄지 생각해 봅시다."

💬 이 질문에 대해 궁금한 점이 있으면 코멘트에 @sparta 를 남겨보세요!
예: @sparta 부모 트랜잭션이 실패해도 방이 남는다면, 이를 해결하기 위한 대안은 무엇인가요?


AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

@github-actions
Copy link
Copy Markdown

🤖 AI 코드리뷰

리더보드 — 현재 4위 (리뷰 100건) | 전체 8개 팀 참여 중
업데이트 확인 — 리뷰가 질문을 던지다 — 생각해보기

코드는 결국 운영 환경에서 돌아갑니다. 리뷰에 들어가기 전에, 프론트엔드 연동을 명분으로 여기저기 뇌관을 심어놓은 건 아닌지 꼼꼼히 따져보겠습니다.

📋 과제 요구사항 준수 여부

  • 검색 API v1 (LIKE 조건 검색): ProductRepositoryCustomImpl에서 nameContains%keyword% 형태의 LIKE 검색을 구현하여 준수.
  • 검색 API v2 (Caffeine Local Cache): 🔴 누락. 3회 연속 지적이지만 여전히 @Cacheable 및 Caffeine 설정이 보이지 않습니다. 필수 요구사항입니다.
  • 동시성 제어: Lettuce 기반 락 유지. (Redisson 미사용)

🔄 이전 피드백 반영 여부

  • WebSocket 인증 검증 로직 추가 (validateToken): 해결
  • 관리자 인터셉터 API 응답 방식 개선 (403 반환): 해결
  • 인기 검색어 길이 제한 (length = 100): 해결
  • 주문 생성 Batch Insert (saveAll): 해결
  • 검색 API v2 (Caffeine Cache) 구현: 미해결 (과제 필수 요구사항)

🟢 잘된 점

  1. 트랜잭션 원자성 보장: ChatInactivityScheduler에서 분리되어 있던 Bulk Update와 메시지 저장 로직을 ChatRoomCommandServiceImpl.batchAutoCloseRooms로 묶어 하나의 트랜잭션으로 처리한 점은 훌륭합니다. 데이터 정합성 측면에서 반드시 필요한 수정이었습니다.
  2. 상태 변경 시 실시간 알림 연동: 채팅방 종료 및 상담사 입장 시 ChatRedisPublisher를 통해 이벤트를 발행하도록 보완한 점이 좋습니다. 이벤트 유실 없이 클라이언트와 상태를 동기화하는 정석적인 접근입니다.

🟡 개선 제안

  1. API 중복 코드 및 Import 에러 (컴파일 불가): AuthController에 중복된 애노테이션과 import가 그대로 방치되어 있습니다. 빌드가 안 되거나 의도치 않게 매핑이 꼬일 수 있습니다.

    // AuthController.java
    // 아래 두 줄이 중복되어 있습니다. 즉시 삭제하세요.
    + import org.springframework.web.bind.annotation.RequestMapping;
    + import org.springframework.web.bind.annotation.RestController;
  2. /check-duplicate API의 잘못된 HTTP Method: 이메일 중복 확인 API를 @GetMapping으로 만들었습니다. 검색어에 이메일을 그대로 노출하는 것은 보안상 좋지 않으며, URL 인코딩 이슈도 발생할 수 있습니다. @PostMapping이나 @PostMapping("/check-duplicate")으로 변경하여 Request Body로 이메일을 받는 것이 마땅합니다.

  3. 채팅방 유저 이메일 조회 시 Left Join의 함정: ChatRoomRepositoryCustomImpl에서 leftJoin(user).on(user.id.eq(chatRoom.userId))를 수행합니다. 만약 탈퇴한 유저여서 User 엔티티가 없다면 user.emailnull로 조회될 것입니다. ChatRoomResponse.from 팩토리 메서드가 userEmail을 non-null로 기대하고 있다면 응답 생성 시 NullPointerException이 발생할 수 있습니다.

🔴 보안 / 성능 주의

  1. Security Config의 잘못된 permitAll 경로: SecurityConfig/cart, /mypage, /orders/**, /checkout 경로가 permitAll()로 등록되어 있습니다. 장바구니, 마이페이지, 주문 내역 조회 등은 인증된 사용자만 접근해야 하는 민감한 리소스입니다. (Thymeleaf SSR 렌더링을 위해 열어둔 것으로 보이나, API 엔드포인트까지 전부 인증 없이 뚫려 있습니다.)
    해당 경로를 permitAll()에서 제거하고, 인증이 필요한 페이지는 Spring Security 필터를 거쳐 로그인 페이지로 리다이렉트 되도록 원복해야 합니다.

💡 학습 포인트

  • Bulk Update와 영속성 컨텍스트: chatRoomRepository.bulkCompleteRooms(roomIds)와 같은 JPQL/QueryDSL의 update 쿼리는 DB에 직접 쿼리를 날리기 때문에, 영속성 컨텍스트(1차 캐시)를 거치지 않습니다. 만약 이 트랜잭션 내에서 bulkCompleteRooms 호출 직후 chatRoomRepository.findById(roomId)를 호출하면 DB가 아닌 오래된 영속성 컨텍스트의 데이터를 읽어오게 됩니다. 이런 현상을 방지하기 위해 Bulk 연산 후에는 영속성 컨텍스트를 초기화(em.flush(); em.clear();)하는 것이 안전합니다.

🤔 생각해보기

"현재 batchAutoCloseRooms에서 수백~수천 개의 채팅방을 한 번에 종료시킨다고 가정해 봅시다. chatRoomRepository.bulkCompleteRooms(roomIds)IN 절에 수천 개의 ID를 넣습니다. MySQL은 이런 쿼리를 어떻게 처리하며, 이로 인해 발생할 수 있는 DB 부하나 잠금(Lock) 문제를 어떻게 방어해야 할까요?"

💬 이 질문에 대해 궁금한 점이 있으면 코멘트에 @sparta 를 남겨보세요!
예: @sparta IN 절의 파라미터가 너무 많을 때 발생하는 문제를 List를 나누어 처리하는 방법이 있을까요?


AI 리뷰는 참고용입니다. 최종 판단은 팀원이 직접 합니다.

@CheatIsKey CheatIsKey merged commit 7c61d8e into develop Apr 27, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Allday-Project-Board Apr 27, 2026
@CheatIsKey CheatIsKey deleted the refactor/Index-Cache-N+1#115 branch April 27, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Refactor] 인덱싱, 캐싱, N+1 문제 해결, 코드 개선

4 participants