Skip to content

Conversation

@DingWoonee
Copy link
Contributor

@DingWoonee DingWoonee commented Jan 1, 2026

이슈(#140)에서 봤듯이 다른 화면에 비해 티어표가 월등히 느려서 원인을 파악하고 개선해봄.

1 티어표 화면 요청/응답 지표 분석

1.1 서버 트레이스 분석

image
  • 서버 로직 실행(컨트롤러 전/후 제외)에서 DB 조회가 대부분을 차지함.
  • 티어표 화면을 요청하면 "티어표 식당 데이터 조회(1번)""지도 데이터 조회(2번)" 가 순차적으로 이루어짐.
    (근데 2번 데이터는 처음 티어표 화면을 보여줄 때 필요한 부분이 아님)
  • 컨트롤러 이후에 차지하는 시간이 생각보다 큼 (타임리프 뷰 렌더링 시간으로 추측)

1.2 브라우저 개발자 도구 분석

image

서버 응답(파란색) 이후에 css나 js 다운로드로 인해(노란색, 보라색) 사용자에게 실제로 화면이 보이기까지 시간이 너무 오래걸림.

2 작업 진행 방향

  1. DB 테이블 인덱스 추가
  2. 티어표 화면 응답에서 지도 데이터 제거 (1.1의 2번 시간을 제거)
  3. 프론트엔드 CRP 최적화

캐싱 외적인 근본적인 원인을 개선하고 싶었음.

3 상세 설명

3.1 테이블 인덱스 추가

데이터가 많지 않아서 큰 의미는 없지만, 예의상 최소한의 인덱스를 추가함

  • 테스트 시나리오

    1. 조건을 넣지 않고 티어표 화면을 100번 호출

      DB로 날아가는 대표 쿼리
      SELECT r.restaurant_id
      FROM restaurant r
      LEFT JOIN restaurant_rating rr ON rr.restaurant_id = r.restaurant_id
      WHERE r.status = 'ACTIVE'
        AND rr.is_temp = false
      ORDER BY
        CASE WHEN rr.tier > 0 THEN 1 ELSE 0 END,
        rr.tier,
        rr.final_score DESC
      LIMIT 40;
    2. 중식, 아시안, 혼밥, 2~4인 조건을 넣고 티어표 화면을 100번 호출

      DB로 날아가는 대표 쿼리
      SELECT r.restaurant_id
      FROM restaurant r LEFT JOIN restaurant_rating rr ON rr.restaurant_id = r.restaurant_id
      WHERE r.restaurant_cuisine IN ('중식', '아시안') 
          AND EXISTS ( 
              SELECT r.restaurant_id
              FROM restaurant_situation_relation rsre
              WHERE rsre.restaurant_id = r.restaurant_id
        	      AND rsre.data_count >= 3
        	      AND rsre.situation_id IN (1, 2) 
          ) 
          AND r.status = 'ACTIVE'
          AND rr.is_temp = false
      ORDER BY CASE WHEN ( rr.tier < 0 ) THEN 1 ELSE 0 END, rr.tier , rr.final_score DESC 
      LIMIT 40;
  • 인덱스 추가 전/후 비교

    • [performance_schema 기준 실행 시간 전/후]
      image
      image
      데이터가 적어서 유의미한 차이는 없는듯함.

      다만, COUNT 쿼리의 경우는 ORDER BY가 없고 PK만 SELECT하기 때문에 index-only-scan으로 데이터가 적은데도 실행 시간이 줄어든게 보이긴 함. (그래도 유의미한 차이는 없는듯)

    • [쿼리 1 EXPLAIN 전/후]
      image
      image
      filesort 제거.
      기존에 ORDER BYCASE문을 써서 인덱스를 쓸 수 없었는데, hasTier 칼럼을 추가해서 해결함.

      id만 조회하기 때문에 커버링 인덱스가 됨.

    • [쿼리 2 EXPLAIN 전/후]
      image
      image
      filesort를 제거하고 싶었으나, 성능 개선은 거의 없는데 그로 인한 변경이 너무 복잡해져서 진행 안 했고,
      EXPLAIN 기준으로 읽는 rows만 줄임

3.2 지도 데이터를 HTML 응답에서 제거

image

티어표 화면에서 처음부터 지도 데이터가 필요하지 않음
→ 지도 데이터를 js로 불러오게 함.
→ 지도 데이터를 조회하는 시간이 사라짐

3.3 프론트엔드 CRP 최적화

image

HTML 응답을 받고도 js(노란색)나 css 파일(보라색) 다운로드 때문에 렌더링이 블록킹 됨(빨간 세모)
→ 실제 화면이 사용자에게 표시되기까지 시간이 더 소비됨

  • 렌더링 지연 주요 원인

    • 렌더링 시에 필요 없는 js 파일로 인해 렌더링이 지연됨.
    • 외부 라이브러리 css에 의존하다 보니 자체 css와 외부 라이브러리 css를 로드를 각각함.
    • 외부 라이브러리 내부에 googleapis.com으로 폰트 요청을 보내는게 많음.
    • 타임리프 fragment를 이용하다 보니 css 파일이 분리되어 있고, 각각 별개의 요청으로 받아옴.
    • css를 계층적으로 참조하는 부분이 있는데 이를 별도의 요청으로 받아옴.
  • 수정한 부분
    모든 css를 하나로 합치고 외부 css를 없애고 싶었으나 득보다는 실이 많아 보여서 하지 않음.
    실제로 한 것들.

    • 불필요한 js와 css 파일을 제거함.
    • <script> 태그에 defer 속성을 추가함
    • googleapis 폰트가 필요 없어서 해당 임포트 문을 전부 주석처리함
    • css 파일 중에 합칠 수 있는 것을 합침

    아래 이미지는 개선 후
    렌더링을 블록킹 하는 것(빨간 세모)이 앞쪽에만 존재
    image

4 작업 전/후 비교

  • 테스트 진행 방식
    각각 인스턴스 및 스프링 프로세스 재시작 후 5분 뒤에 티어표 화면을 powershell로 100번 순차 호출하고,
    이후에는 20~30회 정도 직접 새로고침을 함.

    그리고 그 중 가장 평균적인 수치로 가져옴.

  • 작업 전
    평균적인 HTML 응답 시간
    image
    LCP 1~1.5초
    image
    서버 응답시간 p99, p95
    image

  • 작업 후
    평균적인 HTML 응답 시간
    image
    LCP 0.5~1초 미만
    image
    서버 응답시간 p99, p95
    image

  • 앞으로의 계획
    구현 복잡도가 올라가서 조금 타협을 한 부분이 있지만, 일단 목표로 한 것은 다 진행했다고 생각함.
    근데 서버에서 실행 시간이 길어지면서 또 느려질 수 있기 때문에 2~3주 정도 뒤에 지표를 다시 봐야할 듯함.

@DingWoonee DingWoonee self-assigned this Jan 1, 2026
@DingWoonee DingWoonee added AI Review Request coderabbitai 리뷰 요청 performance Performance problem detected labels Jan 1, 2026
@DingWoonee DingWoonee linked an issue Jan 1, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Jan 1, 2026

📝 Walkthrough

Walkthrough

애플리케이션 전반에 Micrometer Observations 기반 계측 및 Zipkin 분산 추적을 통합하고 관련 런타임·빌드·인프라 설정과 일부 초기화 흐름을 수정합니다. 주요 변경: GitHub Actions의 pull_request 트리거 활성화, Docker Compose에 Zipkin 서비스 추가, Gradle에 Brave/Zipkin 의존성 추가, 다수의 컨트롤러/서비스/리포지토리 메서드에 @Observed 주석 추가, application.yml 계측/샘플링 및 Zipkin 엔드포인트 설정 추가, DB에 has_tier 컬럼·인덱스 추가(마이그레이션), 클라이언트 측 tier 맵 초기화를 비동기 fetch로 리팩터링 및 관련 CSS/템플릿 정리.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Browser as 클라이언트(브라우저)
    participant Web as 웹서버 (Controller)
    participant Service as 서비스 계층
    participant Repo as 리포지토리 / DB
    participant Zipkin as Zipkin

    rect rgb(250,250,255)
      Note over Browser,Web: 페이지 로드 -> JS가 비동기 맵 데이터 요청
      Browser->>Web: GET /web/api/tier/map
    end

    rect rgb(245,255,240)
      Note over Web,Service: Controller(`@Observed`) 호출 -> Service(`@Observed`)
      Web->>Service: tierMapInfo(ChartCondition)
      Service->>Repo: DB 쿼리 (Observed)
      Repo-->>Service: 쿼리 결과 (DTO)
      Service-->>Web: RestaurantTierMapDTO
    end

    rect rgb(255,250,240)
      Note over Service,Zipkin: Micrometer/Brave가 스팬 생성하여 Zipkin으로 전송
      Service->>Zipkin: POST /api/v2/spans (span 데이터)
      Zipkin-->>Service: 200 OK
    end

    rect rgb(250,250,255)
      Web-->>Browser: 200 OK (mapData)
      Note over Browser: initMap(mapData) 호출로 클라이언트 맵 초기화
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

enhancement

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive PR 설명이 개요, PR 유형, 상세 설명을 포함하고 있으나 제공된 템플릿의 필수 구조를 정확히 따르지 않음. PR 설명 템플릿의 '개요'와 'PR 유형' 섹션에 체크박스 형식으로 정확하게 작성하고, 기존의 상세한 설명은 템플릿 구조 내에 포함시켜 주세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경 사항인 티어표 로딩 속도 성능 최적화를 명확하게 요약하고 있습니다.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 1, 2026

🧪 소형(단위) 테스트 결과

106 tests   106 ✅  2s ⏱️
 28 suites    0 💤
 28 files      0 ❌

Results for commit 049a130.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Jan 1, 2026

🚀 중형(통합) 테스트 결과

9 tests   9 ✅  0s ⏱️
1 suites  0 💤
1 files    0 ❌

Results for commit 049a130.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
infra/docker/kustaurant/docker-compose.yml (1)

69-74: Zipkin 서비스 설정 확인 - 버전 고정 권장

Zipkin 서비스가 올바르게 설정되었습니다. 포트 9411은 표준이며, application.yml의 엔드포인트 설정과 일치합니다.

프로덕션 환경의 안정성과 재현성을 위해 :latest 태그 대신 특정 버전을 고정하는 것을 권장합니다:

🔎 제안하는 변경사항
  zipkin:
-    image: openzipkin/zipkin:latest
+    image: openzipkin/zipkin:3.4
    container_name: kustaurant-zipkin
    ports:
      - "9411:9411"
    restart: unless-stopped
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61c0286 and b104d0a.

📒 Files selected for processing (8)
  • .github/workflows/deploy.yml
  • infra/docker/kustaurant/docker-compose.yml
  • server/kustaurant/build.gradle
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/chart/controller/RestaurantChartController.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/chart/service/RestaurantChartService.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantCoreInfoRepository.java
  • server/kustaurant/src/main/resources/application.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 🛠️ 변경된 경우 Kustaurant 빌드 및 푸쉬
  • GitHub Check: 🚀 중형 테스트
  • GitHub Check: 🧪 소형 테스트
🔇 Additional comments (7)
infra/docker/kustaurant/docker-compose.yml (1)

67-67: 재시작 정책 추가 - 좋습니다

mysqld-exporter 서비스에 재시작 정책을 추가한 것은 다른 서비스들과의 일관성을 유지하고 안정성을 향상시킵니다.

server/kustaurant/src/main/resources/application.yml (1)

2-2: 애플리케이션 이름 설정 - 좋습니다

분산 트레이싱을 위한 필수 설정입니다. Zipkin에서 서비스를 식별하는 데 사용됩니다.

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/chart/controller/RestaurantChartController.java (1)

11-11: 관찰성 애노테이션의 일관된 적용 확인

@Observed 애노테이션이 tier() 컨트롤러 메서드에 적절히 추가되었으며, 코드베이스 전반에 걸쳐 네이밍 컨벤션이 일관성 있게 적용되어 있습니다.

검증된 패턴:

  • 컨트롤러: tier.controller
  • 서비스: tier.service.findBasePage, tier.service.findByConditions, tier.service.getRestaurantTierMapDto
  • 리포지토리: tier.repository.getRestaurantIdsWithPage, tier.repository.getRestaurantTiersBase

PR 목적(티어표 로딩 속도 개선)에 부합하는 성능 모니터링 구성입니다.

server/kustaurant/build.gradle (1)

83-86: 트레이싱 의존성 추가 확인

Micrometer와 Zipkin 트레이싱을 위한 표준 의존성이 올바르게 추가되었습니다. Spring Boot 3.5.6의 의존성 관리를 통해 버전이 자동으로 관리되며, io.micrometer:micrometer-tracing-bridge-brave와 io.zipkin.reporter2:zipkin-reporter-brave는 Spring Boot 공식 문서에서 권장하는 표준 조합입니다.

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java (1)

14-14: 관찰성 계층 추가 승인

리포지토리 메서드에 @Observed 어노테이션을 추가하여 성능 모니터링을 위한 트레이싱을 활성화한 것은 적절합니다. 네이밍 컨벤션도 일관성 있게 적용되었습니다.

Also applies to: 36-36

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantCoreInfoRepository.java (1)

23-23: 리포지토리 관찰성 계층 추가 승인

복잡한 조인 쿼리를 수행하는 메서드에 @Observed 어노테이션을 추가한 것은 성능 병목 지점을 파악하는 데 도움이 됩니다. 구현이 정확합니다.

Also applies to: 39-39

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/chart/service/RestaurantChartService.java (1)

55-55: 서비스 계층 관찰성 추가 승인

서비스 메서드들에 @Observed 어노테이션을 추가하여 리포지토리 계층과 함께 전체 호출 체인을 추적할 수 있게 되었습니다. 네이밍 컨벤션이 일관되게 적용되었습니다.

단, 캐시 비활성화 이슈가 해결되어야 전체 성능 최적화 목표를 달성할 수 있습니다.

Also applies to: 85-85

Comment on lines +7 to +8
pull_request:
types: [ opened, reopened, synchronize, ready_for_review ]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/deploy.yml

Repository: kustaurant/BackEnd-integration

Length of output: 12225


🏁 Script executed:

git log --oneline --all -20 -- .github/workflows/deploy.yml

Repository: kustaurant/BackEnd-integration

Length of output: 536


PR 이벤트로 인한 의도하지 않은 배포 위험 (확인됨)

라인 7-8의 pull_request 트리거는 kustaurant-deploy(라인 128)와 crawler-deploy(라인 215)의 배포 작업을 실행합니다. 이 배포 작업들은 if: needs.detect-changes.outputs.kustaurant == 'true'와 같은 경로 기반 조건만 가지고 있으며, 이벤트 타입 검증(if: github.event_name == 'push')이 없습니다. 따라서 PR에서 관련 파일이 변경되면 프로덕션 배포가 실행됩니다.

추가적으로, detect-changes 작업(라인 24)이 github.event.before를 사용하는데, 이는 PR 이벤트에서 정의되지 않아 경로 감지 로직이 의도대로 작동하지 않을 수 있습니다.

커밋 메시지에서 "임시로"라고 명시되었으므로, 이 PR을 머지하기 전에 반드시 제거되거나 다음과 같이 수정되어야 합니다:

  • 배포 작업에 if: github.event_name == 'push' 조건 추가
  • 또는 PR 환경에서 경로 감지가 올바르게 작동하도록 detect-changes 작업 수정
🤖 Prompt for AI Agents
.github/workflows/deploy.yml lines 7-8: the pull_request trigger combined with
deployment jobs that only check path-based outputs can cause unintended
deployments on PRs and detect-changes uses github.event.before which is
undefined for PRs; update the workflow so deployment jobs include an explicit
event check (e.g., add if: github.event_name == 'push' to kustaurant-deploy and
crawler-deploy) OR change detect-changes to reliably handle PRs (use
github.event.pull_request.base.sha/ head.sha or adjust the action inputs to
compare against the correct refs) so path detection works in PRs, and remove the
temporary "임시로" behavior before merging.

Comment on lines 58 to 67
# tracing
tracing:
sampling:
probability: 1.0
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
observations:
annotations:
enabled: true
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

트레이싱 샘플링 비율 - 프로덕션 환경 고려 필요

트레이싱 설정이 올바르게 구성되었습니다. 하지만 probability: 1.0 (100% 샘플링)은 개발/디버깅 환경에는 적합하지만, 프로덕션 환경에서는 다음과 같은 문제를 야기할 수 있습니다:

  • 높은 성능 오버헤드 (모든 요청에 대한 트레이스 수집)
  • Zipkin 스토리지 및 네트워크 부하 증가
  • 트레이싱 자체가 성능 병목이 될 수 있음
🔎 프로덕션 환경을 위한 권장 설정

프로필별로 샘플링 비율을 다르게 설정하는 것을 권장합니다:

application-prod.yml 파일 생성:

management:
  tracing:
    sampling:
      probability: 0.1  # 프로덕션: 10% 샘플링

application-dev.yml 또는 기본 설정 유지:

management:
  tracing:
    sampling:
      probability: 1.0  # 개발: 100% 샘플링

또는 환경 변수로 제어:

management:
  tracing:
    sampling:
      probability: ${TRACING_SAMPLING_PROBABILITY:0.1}
🤖 Prompt for AI Agents
In server/kustaurant/src/main/resources/application.yml around lines 58 to 67,
the tracing sampling is set to probability: 1.0 which is unsafe for production;
update configuration to use profile-specific or environment-controlled sampling
(e.g., move sampling under management.tracing.sampling and create an
application-prod.yml that sets a lower probability such as 0.1, keep 1.0 for dev
or default, or reference an environment variable like
${TRACING_SAMPLING_PROBABILITY:0.1}) so production traffic is sampled at a lower
rate while development can retain full sampling.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b104d0a and b780f67.

📒 Files selected for processing (3)
  • server/kustaurant/src/main/resources/application-local.yml
  • server/kustaurant/src/main/resources/application-prod.yml
  • server/kustaurant/src/main/resources/application.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/kustaurant/src/main/resources/application.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 🛠️ 변경된 경우 Kustaurant 빌드 및 푸쉬
  • GitHub Check: 🚀 중형 테스트
🔇 Additional comments (1)
server/kustaurant/src/main/resources/application-local.yml (1)

28-31: Zipkin 로컬 개발 환경 설정 확인 완료

Zipkin 트레이싱 설정이 올바르게 구성되었습니다:

  • Docker Compose에 Zipkin 서비스가 9411 포트로 정의됨
  • application.yml에 샘플링 확률 1.0으로 설정됨
  • application-local.yml에서 올바른 엔드포인트(http://localhost:9411/api/v2/spans) 지정됨

로컬 개발 시 docker-compose up으로 Zipkin이 함께 구동되므로 추가 설정 불필요합니다.

Comment on lines +34 to +37
management:
zipkin:
tracing:
endpoint: http://zipkin:9411/api/v2/spans
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Explore repository structure to understand the project layout
echo "=== Repository structure ==="
find . -type f -name "*.yml" -o -name "*.yaml" | grep -E "(docker|compose|kube|manifest|prod)" | head -20

Repository: kustaurant/BackEnd-integration

Length of output: 446


🏁 Script executed:

#!/bin/bash
# Check if the application-prod.yml file exists and view context around lines 34-37
if [ -f "server/kustaurant/src/main/resources/application-prod.yml" ]; then
  echo "=== application-prod.yml (full file) ==="
  cat -n "server/kustaurant/src/main/resources/application-prod.yml"
else
  echo "File not found at specified path"
  find . -name "application-prod.yml" -type f 2>/dev/null
fi

Repository: kustaurant/BackEnd-integration

Length of output: 1140


🏁 Script executed:

#!/bin/bash
# Search for Docker Compose files
echo "=== Docker Compose files ==="
fd -e yml -e yaml | grep -iE "(docker-compose|compose)" | head -10

# Search for Kubernetes manifests
echo -e "\n=== Kubernetes manifest files ==="
fd -e yml -e yaml | grep -iE "(deployment|service|manifest|kube)" | head -10

Repository: kustaurant/BackEnd-integration

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Search for tracing and sampling configurations across the codebase
echo "=== Sampling configurations in application files ==="
fd "application" -e yml -e yaml --exec grep -l "sampling\|sampler" {} \;

echo -e "\n=== Zipkin-related properties ==="
fd "application" -e yml -e yaml --exec grep -i "zipkin" {} \; -print

Repository: kustaurant/BackEnd-integration

Length of output: 464


🏁 Script executed:

#!/bin/bash
# View the Docker Compose production file for kustaurant to check zipkin service
echo "=== Docker Compose Production File ==="
cat -n "infra/docker/kustaurant/docker-compose.prod.yml"

Repository: kustaurant/BackEnd-integration

Length of output: 506


🏁 Script executed:

#!/bin/bash
# View application.yml to check for sampling configuration
echo "=== application.yml (default profile) ==="
cat -n "server/kustaurant/src/main/resources/application.yml"

Repository: kustaurant/BackEnd-integration

Length of output: 3086


🏁 Script executed:

#!/bin/bash
# Search for all zipkin-related configurations
echo "=== Searching for zipkin configurations ==="
rg -i "zipkin" --type yml

Repository: kustaurant/BackEnd-integration

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Search for tracing and sampling configurations
echo "=== Searching for sampling/tracing configurations ==="
rg -i "sampling|sampler|tracing" --type yml

Repository: kustaurant/BackEnd-integration

Length of output: 172


🏁 Script executed:

#!/bin/bash
# Search for zipkin configurations without type filter
echo "=== Searching for zipkin configurations ==="
rg -i "zipkin" .

echo -e "\n=== Searching for sampling configurations ==="
rg -i "sampling|sampler" .

Repository: kustaurant/BackEnd-integration

Length of output: 50387


🏁 Script executed:

#!/bin/bash
# Check if there are other docker-compose files or if zipkin is defined elsewhere
echo "=== All docker-compose files content ==="
for file in $(find . -name "docker-compose*.yml" -o -name "docker-compose*.yaml"); do
  echo "=== File: $file ==="
  cat "$file"
  echo ""
done

Repository: kustaurant/BackEnd-integration

Length of output: 3654


프로덕션 환경에서 트레이싱 샘플링 비율 조정 필수

Zipkin 엔드포인트 설정은 올바르게 구성되었으며, Docker Compose에서 zipkin 서비스가 올바르게 정의되고 9411 포트가 노출되어 있습니다. 다만 프로덕션 배포 전에 다음을 반드시 처리해야 합니다:

샘플링 설정 문제: application.ymlmanagement.tracing.sampling.probability: 1.0 (100% 샘플링)이 application-prod.yml에서 오버라이드되지 않아, 프로덕션 환경에서도 모든 트레이스를 수집하게 됩니다. 이는 성능에 심각한 영향을 미칩니다.

필요한 조치:

  • application-prod.yml에서 샘플링 비율을 프로덕션 수준으로 명시 설정 (예: 0.1~0.2)
management:
  tracing:
    sampling:
      probability: 0.1
  zipkin:
    tracing:
      endpoint: http://zipkin:9411/api/v2/spans
🤖 Prompt for AI Agents
In server/kustaurant/src/main/resources/application-prod.yml around lines 34-37,
the production config sets the Zipkin endpoint but does not override sampling,
so the global 100% sampling remains active; update the prod YAML to explicitly
set management.tracing.sampling.probability to a production-appropriate value
(e.g., 0.1–0.2) under the management.tracing.sampling path so sampling is
reduced in production while keeping the existing zipkin endpoint configuration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/kustaurant/src/main/resources/templates/restaurant/tier.html (1)

373-375: 서버 렌더링된 mapData 제거 필요

이 스크립트 블록은 서버에서 mapData를 렌더링하여 전달하고 있지만, tierMapScript.js는 이제 /web/api/tier/map 엔드포인트에서 비동기로 데이터를 가져옵니다. 이 코드는 사용되지 않으며 PR 목표("초기 HTML 응답에서 지도 데이터 제거")와 불일치합니다.

컨트롤러가 여전히 mapData를 조회하고 있다면, 의도한 DB 비용 절감 효과가 달성되지 않습니다.

🔎 제안: 사용되지 않는 스크립트 블록 제거
-<!-- 자바스크립트 데이터 전달용 태그 -->
-<script th:inline="javascript">
-  const mapData = /*[[${mapData}]]*/ {};
-</script>
-
🧹 Nitpick comments (6)
server/kustaurant/src/main/resources/static/css/library/restaurant.css (1)

1-2: LGTM! 렌더 블로킹 최적화에 적합합니다.

Google Fonts @import 주석 처리로 CRP(Critical Rendering Path)가 개선됩니다. NanumSquare 폰트가 navbar-new.html에서 CDN으로 로드되고 있으므로 대체 폰트가 정상 동작합니다.

향후 유지보수를 위해 주석 처리된 이유를 간단히 기록해두면 좋을 수 있습니다 (예: /* [Perf] Commented out for CRP optimization - #141 */).

server/kustaurant/src/main/resources/static/css/newNavbar-style.css (2)

66-76: 주석 처리된 코드 정리를 고려하세요.

여러 주석 처리된 코드 블록(Lines 66-76, 92-102, 132-145)이 남아있습니다. 이전 디자인 레퍼런스로 필요하지 않다면 제거하여 파일 크기를 줄이고 유지보수성을 높일 수 있습니다. 필요시 Git 히스토리에서 복구 가능합니다.


325-325: transparent 키워드 사용을 권장합니다.

#0000은 CSS Color Level 4의 4자리 hex 표기법입니다. 호환성과 가독성을 위해 transparent 키워드 사용을 고려하세요.

-    background-color: #0000;
+    background-color: transparent;
server/kustaurant/src/main/resources/static/js/restaurant/tierMapScript.js (2)

1-22: 비동기 지도 데이터 로딩 구현 완료

CSRF 토큰을 사용한 fetch 요청으로 지도 데이터를 비동기로 가져오는 로직이 올바르게 구현되었습니다. 이는 초기 HTML 응답에서 지도 데이터를 제거하여 서버 측 DB 쿼리 비용을 줄이는 PR 목표를 달성합니다.

🔎 제안: 사용자 피드백을 위한 에러 처리 개선

현재 에러가 발생하면 콘솔에만 로그를 남깁니다. 사용자에게 지도를 로드할 수 없다는 메시지를 표시하는 것을 고려하세요.

  } catch (e) {
    console.error(e);
+   // 사용자에게 에러 메시지 표시
+   const mapArea = document.getElementById('mapArea');
+   if (mapArea) {
+     mapArea.innerHTML = '<div style="padding: 20px; text-align: center;">지도 데이터를 불러올 수 없습니다.</div>';
+   }
  }

97-250: 변수 재선언 개선 권장

정적 분석 도구가 var를 사용한 변수 재선언을 감지했습니다 (예: marker, infoWindow, restaurantImgUrl). var는 함수 스코프를 가지므로 런타임 오류는 발생하지 않지만, 코드 가독성과 유지보수성을 위해 let 또는 const를 사용하는 것이 현대적인 JavaScript 모범 사례입니다.

🔎 제안: let/const 사용으로 변경

반복문 내에서 선언되는 변수들을 let 또는 const로 변경하세요:

  for (const restaurant of favoriteRestaurants) {
-   let marker = new naver.maps.Marker({
+   const marker = new naver.maps.Marker({
      // ...
    });
    // ...
-   var restaurantImgUrl = restaurant.restaurantImgUrl;
+   const restaurantImgUrl = restaurant.restaurantImgUrl;
    if (restaurant.mainTier !== -1) {
-     var infoWindow = new naver.maps.InfoWindow({
+     const infoWindow = new naver.maps.InfoWindow({
        // ...
      });
    } else {
-     var infoWindow = new naver.maps.InfoWindow({
+     const infoWindow = new naver.maps.InfoWindow({
        // ...
      });
    }
  }

다른 반복문에도 동일하게 적용하세요.

server/kustaurant/src/main/resources/db/migration/local/V19__add_rating_index.sql (1)

12-22: 인덱스 설계는 적절하지만 런타임 성능 검증은 코드 리뷰 단계에서 불가능합니다.

코드 분석 결과, 인덱스 설계는 쿼리 패턴과 잘 맞습니다:

  • restaurantActive() 메서드가 status = "ACTIVE" 필터링을 사용하므로 restaurant 테이블 인덱스의 status 컬럼 우선 배치가 타당합니다.
  • RestaurantSituationRelationJpaRepository.findByRestaurantIdAndSituationId() 메서드가 존재하여 (data_count, situation_id, restaurant_id) 인덱스 컬럼 순서가 일반적인 필터 → 커버링 패턴을 따릅니다.
  • restaurantPosition, restaurantCuisine 필터를 사용하는 다중 쿼리 저장소들이 확인되어 해당 인덱스들의 필요성이 입증됩니다.

다만, EXPLAIN 결과 분석(ALL→ref/range 개선), filesort 제거 확인, 실제 실행 시간 측정은 라이브 데이터베이스에 대한 런타임 테스트를 통해서만 검증 가능합니다. 프로덕션/스테이징 배포 후 성능 지표를 확인하세요.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c148025 and 5c14699.

📒 Files selected for processing (21)
  • common/jpa/src/main/java/com/kustaurant/jpa/rating/entity/RatingEntity.java
  • server/crawler/src/main/java/com/kustaurant/crawler/aianalysis/domain/model/AiAnalysisJob.java
  • server/crawler/src/main/java/com/kustaurant/crawler/aianalysis/service/AnalyzeReviewTxService.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/common/view/ViewCountService.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/common/view/ViewerKeyProvider.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/chart/controller/RestaurantChartController.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantCoreInfoRepository.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/controller/RestaurantController.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/RestaurantDetailRepositoryImpl.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/query/RestaurantDetailQuery.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/service/RestaurantService.java
  • server/kustaurant/src/main/resources/application-local.yml
  • server/kustaurant/src/main/resources/db/migration/local/V19__add_rating_index.sql
  • server/kustaurant/src/main/resources/static/css/library/restaurant.css
  • server/kustaurant/src/main/resources/static/css/library/style.min.css
  • server/kustaurant/src/main/resources/static/css/newNavbar-style.css
  • server/kustaurant/src/main/resources/static/css/topSearch-style.css
  • server/kustaurant/src/main/resources/static/js/restaurant/tierMapScript.js
  • server/kustaurant/src/main/resources/templates/common/navbar-new.html
  • server/kustaurant/src/main/resources/templates/restaurant/tier.html
💤 Files with no reviewable changes (1)
  • server/kustaurant/src/main/resources/static/css/topSearch-style.css
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantCoreInfoRepository.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/chart/controller/RestaurantChartController.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-05T17:31:46.031Z
Learnt from: DingWoonee
Repo: kustaurant/BackEnd-integration PR: 80
File: src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/query/RestaurantDetailQuery.java:57-57
Timestamp: 2025-08-05T17:31:46.031Z
Learning: restaurant_rating 테이블의 score와 tier 컬럼은 NOT NULL 제약조건이 있지만, LEFT JOIN 시 rating 레코드가 없는 식당의 경우 JOIN 결과가 NULL이 될 수 있어 coalesce 처리가 필요함

Applied to files:

  • server/kustaurant/src/main/resources/db/migration/local/V19__add_rating_index.sql
  • common/jpa/src/main/java/com/kustaurant/jpa/rating/entity/RatingEntity.java
  • server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java
📚 Learning: 2025-08-05T17:31:46.031Z
Learnt from: DingWoonee
Repo: kustaurant/BackEnd-integration PR: 80
File: src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/query/RestaurantDetailQuery.java:57-57
Timestamp: 2025-08-05T17:31:46.031Z
Learning: restaurant_rating 테이블의 score와 tier 컬럼은 NOT NULL 제약조건이 있지만, LEFT JOIN 시 rating 레코드가 없는 식당의 경우 JOIN 결과가 NULL이 될 수 있어 coalesce 처리가 필요함. DB 제약조건과 JOIN 결과는 다른 개념임.

Applied to files:

  • server/kustaurant/src/main/resources/db/migration/local/V19__add_rating_index.sql
  • common/jpa/src/main/java/com/kustaurant/jpa/rating/entity/RatingEntity.java
🪛 Biome (2.1.2)
server/kustaurant/src/main/resources/static/js/restaurant/tierMapScript.js

[error] 143-143: Shouldn't redeclare 'infoWindow'. Consider to delete it or rename it.

'infoWindow' is defined here:

(lint/suspicious/noRedeclare)


[error] 187-189: Shouldn't redeclare 'restaurantImgUrl'. Consider to delete it or rename it.

'restaurantImgUrl' is defined here:

(lint/suspicious/noRedeclare)


[error] 189-189: Shouldn't redeclare 'infoWindow'. Consider to delete it or rename it.

'infoWindow' is defined here:

(lint/suspicious/noRedeclare)


[error] 221-221: Shouldn't redeclare 'marker'. Consider to delete it or rename it.

'marker' is defined here:

(lint/suspicious/noRedeclare)


[error] 238-238: Shouldn't redeclare 'restaurantImgUrl'. Consider to delete it or rename it.

'restaurantImgUrl' is defined here:

(lint/suspicious/noRedeclare)


[error] 241-241: Shouldn't redeclare 'infoWindow'. Consider to delete it or rename it.

'infoWindow' is defined here:

(lint/suspicious/noRedeclare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 🛠️ 변경된 경우 Kustaurant 빌드 및 푸쉬
  • GitHub Check: 🛠️ 변경된 경우 Crawler 빌드 및 푸쉬
🔇 Additional comments (21)
server/kustaurant/src/main/resources/templates/common/navbar-new.html (1)

6-11: LGTM! CRP 최적화가 적절하게 적용되었습니다.

  • Google Fonts preconnect 주석 처리: @import 비활성화와 일관성 유지
  • NanumSquare CDN은 유지되어 폰트 대체 동작 보장
  • defer 속성 추가로 스크립트가 렌더 블로킹하지 않음
server/kustaurant/src/main/resources/static/css/newNavbar-style.css (1)

58-63: CSS 파일 병합이 잘 수행되었습니다.

topSearch-style.css의 내용이 이 파일로 병합되어 HTTP 요청 수가 줄어들었습니다. 섹션 주석(/* topSearch */)으로 구분된 점도 가독성에 좋습니다.

server/crawler/src/main/java/com/kustaurant/crawler/aianalysis/domain/model/AiAnalysisJob.java (1)

35-38: 메서드 시그니처 변경 승인

updateReviewCount 메서드가 LocalDateTime now 파라미터를 받도록 변경되어, 클래스 내 다른 메서드들(startJob, doneJob, failJob, complete)과 일관된 패턴을 따르게 되었습니다. 이는 타임스탬프 관리 책임을 도메인 엔티티에서 분리하여 테스트 가능성과 일관성을 향상시킵니다.

변경 사항이 깔끔하게 구현되었습니다.

server/crawler/src/main/java/com/kustaurant/crawler/aianalysis/service/AnalyzeReviewTxService.java (1)

65-73: 타임스탐프 일관성 - 현재 구현은 정상입니다

검토 결과, 공유된 now 타임스탐프를 사용하는 현재 구현은 올바릅니다. @retryable 데코레이터는 낙관적 잠금 예외 발생 시 메서드 전체를 재실행하므로, 각 재시도마다 line 65에서 새로운 타임스탐프가 생성됩니다. 따라서 최종 성공한 업데이트는 해당 시도의 실제 시간을 정확히 반영합니다.

server/kustaurant/src/main/resources/templates/restaurant/tier.html (3)

28-30: CSRF 토큰 설정이 올바르게 추가되었습니다.

비동기 fetch 요청에 필요한 CSRF 토큰과 헤더를 메타 태그로 노출하여 tierMapScript.js에서 사용할 수 있도록 했습니다. 보안 모범 사례를 따르고 있습니다.


46-49: 스크립트 defer 속성 추가로 렌더링 성능 개선

jQuery, vendors, main.js, Naver Maps 스크립트에 defer 속성을 추가하여 렌더링 차단을 방지했습니다. HTML 파싱이 완료된 후 순서대로 실행되므로 초기 페이지 로딩 속도가 개선됩니다.


33-33: Font Awesome 스크립트 제거 완료

Font Awesome 스크립트가 올바르게 주석 처리되었으며, 템플릿에서 Font Awesome 아이콘(fa-* 클래스)을 사용하는 부분이 없음을 확인했습니다. Bootstrap Icons CSS가 대체 아이콘 라이브러리로 로드되어 있습니다.

server/kustaurant/src/main/resources/static/js/restaurant/tierMapScript.js (1)

24-382: 지도 초기화 로직이 올바르게 리팩터링되었습니다.

기존의 전역 변수 기반 초기화에서 initMap(mapData) 함수 기반으로 변경하여, 비동기로 받은 데이터를 사용하도록 구조를 개선했습니다. 마커 생성, 이벤트 핸들러, 가시성 제어 로직이 모두 적절하게 캡슐화되었습니다.

server/kustaurant/src/main/java/com/kustaurant/kustaurant/common/view/ViewerKeyProvider.java (1)

4-4: LGTM: 관찰성 계측이 적절하게 추가되었습니다.

@Observed 애너테이션 추가로 뷰어 키 해석 과정의 성능을 추적할 수 있게 되었습니다. 메서드 시그니처나 로직 변경 없이 순수하게 계측만 추가되었으며, PR의 성능 분석 목표와 잘 부합합니다.

Also applies to: 20-20

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java (1)

14-14: LGTM: 리포지토리 레벨 관찰성 추가가 적절합니다.

티어 테이블 조회 성능을 측정할 수 있도록 @Observed 애너테이션이 추가되었습니다. 명시적인 관찰 이름(tier.repository.getRestaurantIdsWithPage)을 지정하여 메트릭 추적이 용이합니다.

Also applies to: 36-36

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/query/RestaurantDetailQuery.java (1)

24-24: LGTM: 레스토랑 상세 조회 성능 추적을 위한 관찰성 추가.

복잡한 쿼리를 수행하는 getRestaurantDetails 메서드에 @Observed 애너테이션이 추가되어 상세 페이지 조회 성능을 모니터링할 수 있게 되었습니다. 로직 변경 없이 순수하게 계측만 추가되었습니다.

참고: Line 61에서 tier.coalesce(0) 처리가 이미 적용되어 있어, LEFT JOIN으로 인한 NULL 값이 적절히 처리되고 있습니다.

Based on learnings about LEFT JOIN and NULL handling in rating table.

Also applies to: 39-39

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/controller/RestaurantController.java (1)

11-11: LGTM: 엔드포인트 레벨 관찰성 추가.

레스토랑 상세 페이지 엔드포인트에 @Observed 애너테이션이 추가되어 전체 요청 처리 시간을 추적할 수 있습니다. 서비스 및 리포지토리 레이어의 @Observed와 결합하여 전체 트레이스를 확인할 수 있어 성능 병목 지점 파악에 유용합니다.

Also applies to: 34-34

server/kustaurant/src/main/resources/application-local.yml (1)

30-33: Zipkin 로컬 설정이 올바르게 구성되었습니다.

docker-compose.yml에 Zipkin 서비스(포트 9411)가 정의되어 있으며, application-local.yml의 엔드포인트 설정이 일치합니다. 필요한 의존성도 build.gradle에 포함되어 있습니다.

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/RestaurantDetailRepositoryImpl.java (1)

10-10: LGTM! 관찰성 계측이 올바르게 적용되었습니다.

Spring 관리 빈의 인스턴스 메서드에 @Observed 어노테이션이 적절하게 추가되어 AOP를 통한 추적이 정상적으로 작동합니다.

Also applies to: 22-23

server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/service/RestaurantService.java (1)

7-7: LGTM! 서비스 계층 관찰성 계측이 적절합니다.

서비스 계층과 리포지토리 계층 모두에 @Observed가 적용되어 상세한 트레이스 스팬을 제공할 수 있습니다.

Also applies to: 22-24

server/kustaurant/src/main/java/com/kustaurant/kustaurant/common/view/ViewCountService.java (1)

26-36: LGTM! 인스턴스 메서드 관찰성 계측이 올바릅니다.

countOncePerHour 메서드에 @Observed가 적절하게 적용되어 조회수 카운팅 작업을 추적할 수 있습니다.

common/jpa/src/main/java/com/kustaurant/jpa/rating/entity/RatingEntity.java (3)

48-50: 헬퍼 메서드 설계는 적절합니다.

tier >= 0 로직을 별도 메서드로 분리하여 일관성을 유지하고 가독성을 높였습니다. 생성자와 업데이트 메서드에서 재사용되고 있어 DRY 원칙을 잘 따르고 있습니다.


52-62: 생성자에서 hasTier 초기화가 올바르게 구현되었습니다.

58번 라인에서 헬퍼 메서드를 통해 hasTier를 초기화하여 tier 값과 일관성을 보장합니다.


64-73: 업데이트 메서드에서 hasTier 동기화가 올바르게 구현되었습니다.

69번 라인에서 tier가 변경될 때마다 hasTier도 함께 갱신되어 일관성이 유지됩니다.

server/kustaurant/src/main/resources/db/migration/local/V19__add_rating_index.sql (2)

8-10: 복합 인덱스 설계를 재검증하세요.

idx_rr_temp_hastier_order 인덱스는 주로 티어표 페이지의 getRestaurantIdsWithPage() 쿼리를 최적화하기 위해 설계되었습니다. 해당 쿼리는 다음과 같이 실행됩니다:

  • WHERE: is_temp = false AND (tier > 0 또는 tier < 0)
  • ORDER BY: has_tier DESC, tier ASC, final_score DESC

이 패턴과 인덱스 열 순서가 부분적으로 일치하므로, 티어표 페이지 쿼리는 인덱스를 효과적으로 활용할 수 있습니다.

그러나 다른 쿼리들은 인덱스 활용도가 낮습니다:

  • RestaurantHomeRepositoryImpl, RestaurantDrawRepositoryImpl: is_temp = true 필터 사용 (인덱스 설계와 반대 방향)
  • ORDER BY final_score DESC 만 사용하는 쿼리들: 인덱스 열 구조와 불일치

또한 WHERE 절에서 tier > 0 조건으로 has_tier 를 간접적으로 필터링하고 있으므로, 인덱스가 has_tier DESC 를 두 번째 열로 가진 것이 모든 시나리오에서 최적인지 확인이 필요합니다.


1-6: 마이그레이션 코드는 기술적으로 올바르지만, 실제 사용되지 않는 컬럼 추가입니다.

has_tier 컬럼의 추가와 UPDATE 로직(tier >= 0)은 스키마 제약조건과 일치합니다. NOT NULL DEFAULT FALSE 설정도 안전하고, tier 컬럼의 CHECK 제약조건((tier >= 1 AND tier <= 5) OR tier = -1)을 고려할 때 UPDATE 문은 올바르게 동작합니다.

그러나 RestaurantDetailQuery.java를 포함한 식별된 Java 쿼리 코드에서 hasTier 필드가 실제로 사용되지 않습니다. 인덱스(idx_rr_temp_hastier_order)가 생성되었지만 WHERE 절에서 이 컬럼을 필터링하는 쿼리가 없어 인덱스 최적화 효과가 제한적입니다. 이 컬럼이 미래 기능을 위한 것이라면 용도를 명확히 하고, 실제 쿼리 통합 계획을 확인하세요.

Comment on lines +33 to +35
@Builder.Default
@Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0")
private boolean hasTier = false;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

빌더 패턴 사용 시 불일치 가능성을 고려하세요.

@Builder.DefaulthasTier = false를 설정했지만, 클래스 레벨의 @Builder 어노테이션을 통해 빌더를 사용할 경우 .tier(5).build() 형태로 객체를 생성하면 tier >= 0임에도 hasTierfalse로 남아 불일치 상태가 발생할 수 있습니다.

명시적 생성자(52-62번 라인)는 이를 올바르게 처리하지만, 빌더 패턴을 사용하는 코드에서는 일관성이 깨질 수 있습니다.

🔎 일관성 보장을 위한 제안

방안 1: hasTier를 계산된 필드로 변경 (권장)

-    @Builder.Default
-    @Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0")
-    private boolean hasTier = false;
+    @Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0")
+    private boolean hasTier;

그리고 빌더 후처리를 위해 커스텀 빌더를 구현:

public static class RatingEntityBuilder {
    public RatingEntity build() {
        RatingEntity entity = new RatingEntity(restaurantId, selfScore, tier, isTemp, ratedAt, finalScore);
        return entity;
    }
}

방안 2: hasTier를 트랜잭션 필드로 만들고 getter에서 계산

이 경우 DB 저장은 여전히 필요하므로 성능 최적화 목적과 맞지 않을 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In common/jpa/src/main/java/com/kustaurant/jpa/rating/entity/RatingEntity.java
around lines 33-35 (and note the explicit constructor at lines ~52-62), the
@Builder.Default boolean hasTier = false can become inconsistent when using the
Lombok builder (e.g. .tier(5).build() leaves hasTier false). Fix by making
hasTier a computed value or ensuring the builder sets it: preferred — remove the
mutable default and implement a custom RatingEntityBuilder.build() that
constructs the entity via the existing constructor (so hasTier is derived
consistently from tier), or alternatively make hasTier transient/removed and
compute hasTier in the getter (return tier != null && tier >= 0); update/remove
the @Builder.Default and adjust annotations accordingly so builder-created
instances have consistent hasTier semantics.

Comment on lines +21 to 24
@Observed
public static String buildDedupKey(ViewResourceType type, long id, String viewerKey) {
return FORMAT_KEY.formatted(type.key(), type.slot(id), viewerKey);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all calls to buildDedupKey to understand usage pattern
rg -n 'buildDedupKey' --type=java -B2 -A2

Repository: kustaurant/BackEnd-integration

Length of output: 1523


🌐 Web query:

Micrometer @Observed annotation static method support Spring

💡 Result:

Short answer: @observed will not be applied to static methods when you rely on Spring AOP (the default/proxy-based approach). You can get @observed on static methods only if you use full AspectJ weaving (compile‑time or load‑time) so the AspectJ aspect can advise static methods. Otherwise instrument static code manually with Observation API.

References:

  • Micrometer / Observations: annotation support (uses AspectJ aspect or Spring AOP; you normally register an ObservedAspect). [1]
  • Spring AOP is proxy‑based and cannot advise static (or private/final) methods. [2]
  • AspectJ (CTW/LTW) can weave and advise static methods; use LTW/CTW to intercept static methods. [3]

If you want, I can show (a) how to configure AspectJ LTW/CTW for Micrometer @observed, or (b) the manual Observation.create(...) pattern for static methods.


정적 메서드에서 @Observed 어노테이션이 작동하지 않습니다.

@Observed 어노테이션은 기본 설정의 Spring AOP 프록시 메커니즘에 의존하는데, Spring AOP는 정적 메서드를 인터셉트할 수 없습니다. 이 어노테이션이 작동하려면 AspectJ 위빙(컴파일 타임 또는 로드 타임)을 설정해야 하므로, 일반적인 Spring 환경에서는 효과가 없습니다.

buildDedupKey는 이미 @Observed가 적용된 countOncePerHour 인스턴스 메서드에서만 호출되므로, 해당 정적 메서드에서 @Observed를 제거하는 것을 권장합니다.

🤖 Prompt for AI Agents
In
server/kustaurant/src/main/java/com/kustaurant/kustaurant/common/view/ViewCountService.java
around lines 21–24: the @Observed annotation on the static method buildDedupKey
does nothing because Spring AOP cannot intercept static methods (AspectJ weaving
would be required); remove the @Observed annotation from this static method (or
alternatively make it an instance method only if you need it intercepted),
leaving the annotation on the instance method countOncePerHour that calls
buildDedupKey, then rebuild and run tests to ensure no behavioral change.

Comment on lines +56 to 58
ratingEntity.hasTier.desc(),
ratingEntity.tier.asc(),
ratingEntity.finalScore.desc()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# hasTier 컬럼 추가 마이그레이션 파일 검색
echo "=== Searching for hasTier column migration ==="
rg -n "hasTier|has_tier" --type=sql -C 3

# 관련 인덱스 정의 검색
echo -e "\n=== Searching for indexes on rating table ==="
rg -n "CREATE INDEX|ADD INDEX" --type=sql -C 2 | head -100

# RatingEntity에서 hasTier 필드 정의 확인
echo -e "\n=== Checking hasTier field in RatingEntity ==="
fd -e java | xargs grep -l "RatingEntity" | head -5

Repository: kustaurant/BackEnd-integration

Length of output: 5016


🏁 Script executed:

# 더 상세한 검색: 마이그레이션 파일 목록
echo "=== Migration files ==="
fd -e sql | grep -i migr | head -20

Repository: kustaurant/BackEnd-integration

Length of output: 1878


🏁 Script executed:

# RestaurantChartRepositoryImpl.java 파일 전체 내용 확인 (크기 확인)
fd -e java | grep -i "RestaurantChartRepositoryImpl"

Repository: kustaurant/BackEnd-integration

Length of output: 216


🏁 Script executed:

# Check RestaurantChartRepositoryImpl.java file size and read relevant sections
wc -l server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java

Repository: kustaurant/BackEnd-integration

Length of output: 219


🏁 Script executed:

# Read the entire RestaurantChartRepositoryImpl.java file
cat -n server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java

Repository: kustaurant/BackEnd-integration

Length of output: 5093


🏁 Script executed:

# Check RatingEntity for hasTier field definition
cat -n common/jpa/src/main/java/com/kustaurant/jpa/rating/entity/RatingEntity.java

Repository: kustaurant/BackEnd-integration

Length of output: 2617


LEFT JOIN으로 인한 NULL 값 처리가 누락되었습니다. ORDER BY 절에서 COALESCE를 추가해야 합니다.

마이그레이션과 인덱스 설정은 정상적으로 진행되었습니다:

  • ✓ V19__add_rating_index.sql에서 has_tier 컬럼 추가 완료 (NOT NULL DEFAULT FALSE)
  • ✓ idx_rr_temp_hastier_order 인덱스 생성 확인

그러나 LEFT JOIN에서 rating이 없는 레스토랑의 경우 NULL 처리가 필수입니다:

데이터베이스 컬럼은 NOT NULL이지만, LEFT JOIN 결과에서는 rating 레코드가 없는 식당의 경우 ratingEntity 전체가 NULL이 되므로 hasTier, tier, finalScore도 모두 NULL입니다. 현재 코드(56-58줄)는 이를 처리하지 않습니다.

-- 필요한 처리:
ratingEntity.hasTier.desc(),                    -- NULL 처리 필요
ratingEntity.tier.asc(),                        -- NULL 처리 필요  
ratingEntity.finalScore.desc()                  -- NULL 처리 필요

COALESCE를 사용하여 NULL값을 기본값(false, -1, 0 등)으로 변환해야 정렬이 의도대로 작동합니다.

🤖 Prompt for AI Agents
In
server/kustaurant/src/main/java/com/kustaurant/kustaurant/restaurant/query/common/infrastructure/repository/RestaurantChartRepositoryImpl.java
around lines 56-58, the ORDER BY uses ratingEntity.hasTier, ratingEntity.tier,
ratingEntity.finalScore directly which yields NULLs for restaurants without a
LEFT JOINed rating; wrap each ordering expression with COALESCE to provide
non-null defaults (e.g., coalesce(hasTier, false) for boolean, coalesce(tier,
-1) for tier, coalesce(finalScore, 0) for score) so sorting behaves as intended;
implement these COALESCE expressions using the project's QueryDSL/JPQL API
(e.g., coalesce/Expressions.coalesce or the entity path coalesce methods) and
keep the same ASC/DESC directions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
server/kustaurant/src/main/resources/db/migration/prod/V6__20260104.sql (2)

14-24: 다수의 인덱스 추가로 인한 누적 영향 검토

이 마이그레이션에서 총 5개의 인덱스가 추가되며, restaurant 테이블에만 3개의 인덱스가 생성됩니다. 특히 세 인덱스 모두 status로 시작하는데, 일부 중복이 있을 수 있습니다:

  • idx_r_status_id: (status, restaurant_id)
  • idx_r_status_cuisine_id: (status, restaurant_cuisine, restaurant_id)
  • idx_r_status_position_id: (status, restaurant_position, restaurant_id)

각 인덱스가 서로 다른 쿼리 패턴을 지원하더라도, 전체적인 쓰기 성능 저하와 스토리지 비용 증가를 고려해야 합니다. 특히 restaurant_id가 PK라면 인덱스 끝에 명시적으로 포함할 필요가 없을 수 있습니다(InnoDB는 자동으로 PK를 포함).

프로덕션 환경에서 각 인덱스의 실제 사용률을 모니터링하고, 사용되지 않는 인덱스는 제거하는 것을 권장합니다.


1-24: 프로덕션 마이그레이션 실행 전략 수립 권장

대용량 테이블의 경우 ALTER TABLECREATE INDEX 작업이 오래 걸리고 테이블 잠금을 유발할 수 있습니다. 다음 사항들을 고려해주세요:

  1. 실행 시점: 서비스 트래픽이 적은 시간대 선택
  2. 잠금 전략: MySQL 5.6+ 또는 MariaDB 10.0+에서는 ALGORITHM=INPLACE, LOCK=NONE 옵션 고려
  3. 롤백 계획: 인덱스 제거 스크립트 준비
  4. 모니터링: 마이그레이션 진행 상황 및 영향 모니터링

참고로, has_tier 컬럼은 derived 값이므로 generated column으로 정의하는 것도 고려해볼 수 있습니다:

ADD COLUMN has_tier BOOLEAN GENERATED ALWAYS AS (tier >= 0) STORED

이렇게 하면 애플리케이션에서 명시적으로 유지보수할 필요가 없어집니다.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c14699 and 049a130.

📒 Files selected for processing (2)
  • server/kustaurant/src/main/resources/db/migration/prod/V6__20260104.sql
  • server/kustaurant/src/main/resources/templates/restaurant/tier.html
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-05T17:31:46.031Z
Learnt from: DingWoonee
Repo: kustaurant/BackEnd-integration PR: 80
File: src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/query/RestaurantDetailQuery.java:57-57
Timestamp: 2025-08-05T17:31:46.031Z
Learning: restaurant_rating 테이블의 score와 tier 컬럼은 NOT NULL 제약조건이 있지만, LEFT JOIN 시 rating 레코드가 없는 식당의 경우 JOIN 결과가 NULL이 될 수 있어 coalesce 처리가 필요함

Applied to files:

  • server/kustaurant/src/main/resources/db/migration/prod/V6__20260104.sql
📚 Learning: 2025-08-05T17:31:46.031Z
Learnt from: DingWoonee
Repo: kustaurant/BackEnd-integration PR: 80
File: src/main/java/com/kustaurant/kustaurant/restaurant/restaurant/infrastructure/repository/query/RestaurantDetailQuery.java:57-57
Timestamp: 2025-08-05T17:31:46.031Z
Learning: restaurant_rating 테이블의 score와 tier 컬럼은 NOT NULL 제약조건이 있지만, LEFT JOIN 시 rating 레코드가 없는 식당의 경우 JOIN 결과가 NULL이 될 수 있어 coalesce 처리가 필요함. DB 제약조건과 JOIN 결과는 다른 개념임.

Applied to files:

  • server/kustaurant/src/main/resources/db/migration/prod/V6__20260104.sql
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 🛠️ 변경된 경우 Kustaurant 빌드 및 푸쉬
  • GitHub Check: 🛠️ 변경된 경우 Crawler 빌드 및 푸쉬
🔇 Additional comments (5)
server/kustaurant/src/main/resources/templates/restaurant/tier.html (3)

28-31: CSRF 토큰이 올바르게 구현되었습니다

tierMapScript.js에서 메타 태그로부터 CSRF 토큰을 정상적으로 받아와 /web/api/tier/map API 호출 시 요청 헤더에 포함시키고 있습니다. Spring Security CSRF 보호 패턴을 올바르게 적용하셨습니다.


33-33: Font Awesome 스크립트 안전하게 제거됨

렌더링 차단 리소스를 줄이기 위해 Font Awesome 스크립트를 제거하고 Bootstrap Icons로 완전히 마이그레이션했습니다. 티어 페이지, navbar, footer 및 관련 JavaScript 파일에서 Font Awesome 아이콘 사용 흔적이 없으므로 변경사항이 안전합니다.


46-51: 스크립트 defer 속성 추가로 렌더링 성능 개선

렌더 블로킹을 방지하기 위해 주요 스크립트에 defer 속성을 추가한 것은 훌륭한 CRP 최적화입니다. 스크립트 실행 순서가 유지되므로 의존성 문제도 없습니다.

네이버 지도 API와의 비동기 처리도 안전합니다. tierMapScript.js의 모든 코드가 DOMContentLoaded 이벤트 콜백 내에서 실행되며, 이 시점에는 네이버 지도 API가 이미 로드되어 있습니다. 추가로 지도 init 이벤트 리스너를 등록하여 비동기 초기화도 적절히 처리하고 있습니다.

server/kustaurant/src/main/resources/db/migration/prod/V6__20260104.sql (2)

4-8: tier >= 0 로직과 has_tier 유지 관리 구현이 올바르게 되어 있습니다.

검증 결과:

  • RatingEntitytier 기본값이 -1이며, hasTier() 메서드가 tier >= 0으로 정의되어 있어 로직이 정확합니다.
  • 생성자와 updateRatingData() 메서드에서 모두 this.hasTier = hasTier(tier)로 티어 값 변경 시 has_tier를 올바르게 유지하고 있습니다.
  • 마이그레이션의 DEFAULT FALSE 설정은 ALTER TABLEUPDATE 사이의 데이터 일관성을 안전하게 처리하는 표준 패턴입니다.

11-12: 5개 컬럼 복합 인덱스는 실제 쿼리 패턴과 잘 맞춰져 있습니다

이 인덱스는 코드에서 실제로 사용되는 쿼리 패턴과 정확하게 맞춰져 있습니다:

  • is_temp 컬럼은 WHERE 조건에서 사용됨 (isTemp.not())
  • has_tier DESC는 ORDER BY에서 정렬 기준으로 사용됨
  • final_score DESC도 ORDER BY에서 사용됨
  • restaurant_id가 마지막에 포함되어 커버링 인덱스 역할

마이그레이션 파일에서 has_tier 컬럼을 특별히 추가하고 이 인덱스를 함께 생성한 것을 보면 의도된 최적화입니다. 저장 작업도 배치 방식으로 처리되므로 쓰기 오버헤드는 관리 가능한 수준입니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Review Request coderabbitai 리뷰 요청 performance Performance problem detected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[perf] 티어표 로딩 속도

2 participants