Skip to content

[refactor] 북마크 API DB 쿼리 최적화, 인덱스 개선 및 perf-analyzer 에이전트 추가#353

Merged
uykm merged 10 commits intodevelopfrom
refactor/#352-optimize-bookmark-query
Mar 30, 2026
Merged

[refactor] 북마크 API DB 쿼리 최적화, 인덱스 개선 및 perf-analyzer 에이전트 추가#353
uykm merged 10 commits intodevelopfrom
refactor/#352-optimize-bookmark-query

Conversation

@uykm
Copy link
Copy Markdown
Collaborator

@uykm uykm commented Mar 23, 2026

개요

Artillery 부하테스트(최대 50 RPS)에서 아래 API들이 DB 커넥션 풀을 소진하는 문제를 발견하고 원인을 제거했습니다.

  • GET /api/places/bookmarks/folders/preview
  • GET /api/places?isBookmarkSearch=true
  • GET /api/courses/bookmarks/folders/preview
  • GET /api/courses/bookmarks

원인 분석

1. N+1 쿼리 (장소 API)

PlaceRepositoryImpl에서 장소 목록 조회 후 loadPlaceTagsAndTags()를 별도 호출하는 구조였으나, 이 메서드는 Place.placeTags 컬렉션을 초기화하지 못했습니다. 결과적으로 이후 place.getMainTag() 호출 시 장소 수만큼 추가 쿼리가 발생(N+1)하여 50 RPS 기준 성공률이 15.8%까지 떨어졌습니다.

// Before — 요청당 최대 수십 쿼리
① SELECT place WHERE (북마크 IN + town + active)
② SELECT place_tag JOIN tag WHERE place_id IN (...)  ← placeTags 초기화 안 됨
③~N place.getMainTag() 호출마다 SELECT 발생

2. 중복 Redis 호출 (장소 API)

PlaceService.getPlacesByTownAndTag()에서 getBookmarkedPlaceIdsForTown()을 2회 호출하는 구조.

3. 인덱스 불일치 + 콜드 스타트 동시 백필 (코스 API)

테이블 기존 인덱스 실제 쿼리 패턴 문제
bookmarks (user_id, created_at) WHERE user_id=? AND target_type=? target_type 누락 → user_id 매칭 전체 행을 메모리 필터링
places (town_id) WHERE town_id=? AND active=true active 누락 → town_id 매칭 전체 행을 메모리 필터링

코스 API는 Redis ZSET 캐시를 사용하므로 정상 상태에서는 캐시 히트로 DB 조회가 발생하지 않습니다. 그러나 캐시 콜드 스타트 시 getLatestBookmarkedCourseIdPerTown()에서 동기화 없이 towns-set 미스를 감지한 모든 동시 요청이 fullBackfillCourses()를 동시에 실행합니다(single-flight 패턴 없음).

기존 인덱스로는 이 백필 쿼리(findAllCourseBookmarksWithTownId)가 느려 커넥션을 오래 점유했고, 50 RPS 부하에서 콜드 스타트 구간에 동시 백필이 몰리면서 커넥션 풀이 고갈되었습니다. 인덱스 최적화 후 백필 쿼리가 빠르게 완료되면서 이 구간의 영향이 제거되었습니다.

실제 서비스 환경에서는 캐시가 이미 적재된 상태가 대부분이라 영향이 제한적이나, 근본적인 해결책은 single-flight 패턴 도입입니다.


변경 사항

Step 1 — 쿼리 최적화

PlaceRepositoryImpl

  • findPlacesWithoutTags() / findPlacesWithTags(): loadPlaceTagsAndTags() 제거 → LEFT JOIN FETCH로 1쿼리 통합, N+1 완전 제거
  • loadPlaceTagsAndTags() 메서드 삭제

PlaceService

  • getBookmarkedPlaceIdsForTown() 상단 1회 호출 후 재사용 (중복 호출 제거)
  • isBookmarkSearch=true일 때 조회된 orderedIds를 북마크 최신순 정렬에도 활용

Step 2 — 인덱스 최적화 (Flyway V10, V11)

-- V10: bookmarks — target_type 컬럼 추가로 백필 쿼리 최적화
DROP INDEX idx_bookmark_user_created ON bookmarks;
CREATE INDEX idx_bookmark_user_type
    ON bookmarks (user_id, target_type);

-- V10: places — active 컬럼 추가로 메모리 필터링 제거
CREATE INDEX idx_places_town_active
    ON places (town_id, active);
DROP INDEX idx_places_town_id ON places;

-- V11: 위 두 인덱스에서 created_at DESC 제거
-- backfill 쿼리에 ORDER BY가 없고, LIMIT 없는 전체 조회에서 filesort 절감 효과도 없음
-- target_id가 인덱스에 없어 커버링 인덱스도 아니므로 created_at 포함 이점 없음

Step 3 — 부하테스트 시나리오 개선

  • $env.TEST_TOKEN 단일 유저 방식 → users.csv 토큰 풀 + order: random으로 전환
  • 단일 유저 테스트는 첫 백필 이후 모든 요청이 캐시 히트가 되어 DB 부하를 현실적으로 측정하기 어렵기 때문
  • load-test/data/ gitignore 추가 (토큰 포함 파일 보호)

성능 측정 결과

EXPLAIN 실행 계획 비교

places 테이블 — WHERE town_id=? AND active=true

type key rows Extra
기존 (town_id) ALL - 320 Using where
신규 (town_id, active) ref idx_places_town_active 59 Using index condition

풀 테이블 스캔 → 인덱스 탐색으로 개선. active 필터링이 인덱스 내에서 처리되어 스캔 행 수가 320 → 59로 감소합니다. 해당 쿼리는 LIMIT 없이 전체 행을 조회하므로 filesort는 애초에 유의미한 오버헤드가 아니며, created_at은 인덱스에서 제외했습니다.

bookmarks 테이블 — 유니크 제약이 커버링 인덱스로 동작하는 두 쿼리

uk_bookmark_user_target UNIQUE (user_id, target_type, target_id)는 단순 중복 방지용 제약이 아닌, 아래 두 쿼리에서 커버링 인덱스로 동작합니다. InnoDB에서 UNIQUE 제약은 B-Tree 인덱스로 구현되므로, 쿼리 실행에 필요한 모든 컬럼이 인덱스에 포함될 경우 테이블 heap 접근이 발생하지 않습니다.

① EXISTS 체크 — existsByUserIdAndTargetTypeAndTargetId

SELECT 1 FROM bookmarks
WHERE user_id = ? AND target_type = ? AND target_id = ?
LIMIT 1

SELECT 절이 상수(1)이므로 WHERE 조건 컬럼만 인덱스에 있으면 충분합니다. (user_id, target_type, target_id) 세 컬럼이 모두 UK에 포함되어 테이블 접근 없이 인덱스만으로 존재 여부를 판정합니다.

② backfill 조회 — findBookmarkedTargetIdsByTargetIds

SELECT b.target_id FROM bookmarks b
WHERE b.user_id = ? AND b.target_type = ? AND b.target_id IN (...)

SELECT 절의 target_id도 인덱스에 포함되어 있어 테이블 heap 접근이 0회입니다. IN (...) 조건은 (user_id, target_type) 등치로 범위를 먼저 좁힌 뒤 target_id를 인덱스 내에서만 필터링하므로, range scan이더라도 추가 I/O가 발생하지 않습니다.

쿼리 type key Extra
EXISTS 체크 const uk_bookmark_user_target Using index
backfill IN 조회 range uk_bookmark_user_target Using index

신규 인덱스 (user_id, target_type)는 위 두 쿼리가 아닌, target_id 없이 전체를 읽는 패턴(findAllCourseBookmarksWithTownId 등)에서 효과를 냅니다. 두 인덱스가 서로 다른 쿼리 패턴을 담당하는 구조입니다.

부하테스트 (Artillery 50 RPS)

장소 API

지표 베이스라인 쿼리 최적화 후 인덱스 최적화 후
성공률 15.8% 100% 100%
Median - 8.9ms 7.9ms
p95 - 22.9ms 19.1ms
p99 - 122.7ms 67.4ms (-45%)
Max - 892ms 623ms

베이스라인은 N+1 타임아웃으로 정상 지표 측정 불가

코스 API (캐시 콜드 스타트 기준)

지표 인덱스 최적화 전 인덱스 최적화 후
성공률 100% 100%
Median 18ms 10.9ms
p95 1,978ms 74.4ms (-96%)
p99 2,836ms 376.2ms (-87%)
Max 5,738ms 1,117ms

코스 API의 극적인 개선은 콜드 스타트 시 동시 백필 쿼리가 새 인덱스를 타면서 빠르게 완료된 결과입니다.


체크리스트

  • 빌드 확인 (./gradlew build -x test)
  • 부하테스트 재측정 (장소·코스 각각)
  • Flyway 마이그레이션 로컬 적용 확인
  • 엔티티 @Index 어노테이션 동기화
  • 부하테스트 다중 유저 CSV 방식으로 전환

Closes #352

uykm added 3 commits March 24, 2026 02:07
- findPlacesWithoutTags: loadPlaceTagsAndTags 제거 → LEFT JOIN FETCH 통합 (2쿼리 → 1쿼리)
- findPlacesWithTags: LEFT JOIN FETCH 추가로 PlaceTag N+1 방지
- PlaceService: getBookmarkedPlaceIdsForTown 중복 호출 제거, orderedIds 재사용으로 북마크 최신순 정렬 유지
- bookmarks: (user_id, created_at) → (user_id, target_type, created_at DESC)
  - target_type 필터링 시 인덱스 풀 스캔 제거
- places: (town_id) → (town_id, active, created_at DESC)
  - active 필터 + created_at 정렬을 인덱스 내에서 처리
- 엔티티 @Index 어노테이션 동기화
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

북마크 및 장소 조회 API의 데이터베이스 부하를 개선하기 위해 쿼리 최적화와 인덱스 재구성을 수행했습니다. LEFT JOIN FETCH로 N+1 쿼리를 제거하고, 복합 인덱스를 추가하며, 북마크 검색 로직을 개선했습니다.

Changes

Cohort / File(s) Summary
데이터베이스 인덱스 최적화
src/main/resources/db/migration/V10__optimize_indexes_for_bookmark_query.sql, src/main/resources/db/migration/V11__simplify_places_town_active_index.sql
북마크 테이블의 (user_id, created_at) 인덱스를 (user_id, target_type, created_at DESC) 복합 인덱스로 변경하고, 장소 테이블의 인덱스를 (town_id, active, created_at DESC)(town_id, active)로 최적화했습니다.
엔티티 인덱스 정의 업데이트
src/main/java/.../bookmark/entity/Bookmark.java, src/main/java/.../place/entity/Place.java
JPA 엔티티의 @Index 정의를 데이터베이스 마이그레이션과 일치하도록 업데이트했습니다.
쿼리 최적화
src/main/java/.../place/repository/querydsl/PlaceRepositoryImpl.java
findPlacesWithoutTags()findPlacesWithTags()에서 LEFT JOIN FETCH를 통합하여 N+1 쿼리를 제거하고, 불필요한 loadPlaceTagsAndTags() 메서드를 제거했습니다.
서비스 로직 개선
src/main/java/.../place/service/PlaceService.java
북마크 검색 시 getBookmarkedPlaceIdsForTown() 중복 호출을 제거하고, 북마크 정렬 결과를 재사용하도록 개선했습니다.
P6Spy 데이터베이스 쿼리 로깅
build.gradle, src/main/resources/spy.properties
p6spy 런타임 의존성을 추가하고 쿼리 로깅 설정을 구성했습니다.
테스트 구성 및 부하 테스트
src/test/resources/application-test.yml, load-test/scenarios/bookmark-course.yml, load-test/scenarios/bookmark-place.yml
테스트 환경 설정 및 부하 테스트 시나리오에서 CSV 기반 토큰 페이로드를 사용하도록 업데이트했습니다.
개발 도구 및 설정
.gitignore, .claude/agents/perf-analyzer.md, .claude/settings.local.json
부하 테스트 결과물 디렉토리를 gitignore에 추가하고, 성능 분석 에이전트 지침 및 권한 설정을 추가했습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • bykbyk0401
  • leejisoo0617

Poem

🐰 쿼리는 줄이고 인덱스는 늘리고,
N+1의 악몽은 물리치고,
LEFT JOIN FETCH로 한 번에 쓸어내고,
북마크 검색은 더 빠르게~
DB 부하 개선, 완벽해요! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive p6spy 설정 파일, perf-analyzer 에이전트, load-test 시나리오 개선 등 범위를 벗어난 변경사항이 포함되어 있습니다. 이들은 주요 목표 달성을 지원하는 부가적 개선이지만 #352 이슈의 직접 요구사항은 아닙니다. 범위 외 변경사항(p6spy, perf-analyzer, load-test)이 부하테스트 검증을 위한 지원 목적인지, 아니면 향후 논의가 필요한 별도 개선인지 명확히 하시기 바랍니다.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경사항인 북마크 API DB 쿼리 최적화, 인덱스 개선, perf-analyzer 에이전트 추가를 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR의 모든 코딩 관련 요구사항이 충족되었습니다: PlaceRepositoryImpl에서 N+1 쿼리 제거, PlaceService에서 중복 호출 제거, 두 단계 모두의 인덱스 최적화 완료.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#352-optimize-bookmark-query

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.

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (1)
src/main/java/org/sopt/solply_server/domain/place/service/PlaceService.java (1)

113-134: 회피 가능한 북마크 조회가 아직 두 경로에 남아 있습니다.

Line 117-119는 getPlacesByCondition() 내부의 태그 검증보다 먼저 실행돼서, 잘못된 태그 조합 요청도 getBookmarkedPlaceIdsForTown()을 한 번 타게 됩니다. 그리고 non-bookmark 조회에서 places가 비어도 Line 128-130이 다시 북마크 ID를 읽습니다. 이 PR의 목적이 중복 Redis/DB 호출 축소라면 두 경로 모두 먼저 short-circuit하는 편이 낫습니다.

♻️ 제안 수정안
+        if (mainTagId != null) {
+            tagValidator.validatePlaceTagConditions(mainTagId, subTagAIdList, subTagBIdList);
+        }
+
         // isBookmarkSearch=true 시 orderedIds를 한 번만 조회해서 장소 목록 필터링과 북마크 Set에 재사용
         // (기존에는 getBookmarkedPlacesByLatest()와 북마크 Set 생성 시 각각 1회씩, 총 2회 호출했음)
         List<Long> bookmarkedOrderedIds = (isOnlyBookmarkSearch && userId != null)
                 ? placeBookmarkFacade.getBookmarkedPlaceIdsForTown(userId, townId)
                 : null;
@@
         // 북마크 검색 시 orderedIds를 함께 전달 → 내부에서 북마크 최신순으로 재정렬
         List<Place> places = getPlacesByCondition(townId, isOnlyBookmarkSearch, mainTagId, subTagAIdList, subTagBIdList, bookmarkedOrderedIds);
+
+        if (places.isEmpty()) {
+            return PlaceFilterGetResponse.from(List.of());
+        }

이렇게 올리면 아래 getPlacesByCondition()의 동일 검증은 함께 제거해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/sopt/solply_server/domain/place/service/PlaceService.java`
around lines 113 - 134, The code currently calls
placeBookmarkFacade.getBookmarkedPlaceIdsForTown(...) before validating the tag
combination inside getPlacesByCondition, causing unnecessary Redis/DB hits;
change the flow so you first validate/tag-filter via getPlacesByCondition (or
extract its validation logic into a pre-check) and only call
placeBookmarkFacade.getBookmarkedPlaceIdsForTown when isOnlyBookmarkSearch is
true AND the tag validation passes (or when places is non-empty for non-bookmark
logged-in flows), then reuse that ordered list to build bookmarkedIds; also
remove the now-duplicated tag validation from getPlacesByCondition so no
double-check occurs (referencing isOnlyBookmarkSearch, bookmarkedOrderedIds,
getPlacesByCondition, getBookmarkedPlaceIdsForTown, and bookmarkedIds).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/resources/db/migration/V10__optimize_indexes_for_bookmark_query.sql`:
- Around line 3-5: The migration drops idx_bookmark_user_created before creating
the new idx_bookmark_user_type_created which leaves bookmarks without the main
index during migration; change the migration to create the new index on
bookmarks (CREATE INDEX [CONCURRENTLY] idx_bookmark_user_type_created ON
bookmarks (user_id, target_type, created_at DESC)) first, wait until it is
ready, and only then drop the old idx_bookmark_user_created (DROP INDEX
[CONCURRENTLY] idx_bookmark_user_created) to avoid a window with no index and
minimize locking.

In `@src/main/resources/spy.properties`:
- Around line 1-10: spy.properties is loaded in all profiles and P6Spy is on the
main classpath (see build.gradle), causing SQL logging in production; fix by
choosing one of: move spy.properties into a profile-specific resource directory
(e.g., src/main/resources-<profile>/spy.properties) so only that profile enables
P6Spy, or add spring.autoconfigure.exclude for the P6Spy auto-configuration in
application-prod.yml to disable it in production, or change the P6Spy dependency
scope in build.gradle to testOnly so it is not on the main classpath; update the
relevant artifact (spy.properties, build.gradle, or application-prod.yml)
accordingly and verify production profile does not load P6Spy.

---

Nitpick comments:
In `@src/main/java/org/sopt/solply_server/domain/place/service/PlaceService.java`:
- Around line 113-134: The code currently calls
placeBookmarkFacade.getBookmarkedPlaceIdsForTown(...) before validating the tag
combination inside getPlacesByCondition, causing unnecessary Redis/DB hits;
change the flow so you first validate/tag-filter via getPlacesByCondition (or
extract its validation logic into a pre-check) and only call
placeBookmarkFacade.getBookmarkedPlaceIdsForTown when isOnlyBookmarkSearch is
true AND the tag validation passes (or when places is non-empty for non-bookmark
logged-in flows), then reuse that ordered list to build bookmarkedIds; also
remove the now-duplicated tag validation from getPlacesByCondition so no
double-check occurs (referencing isOnlyBookmarkSearch, bookmarkedOrderedIds,
getPlacesByCondition, getBookmarkedPlaceIdsForTown, and bookmarkedIds).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: e856d228-94e4-4d2a-9388-868a271da4ce

📥 Commits

Reviewing files that changed from the base of the PR and between c8756b0 and e78654a.

📒 Files selected for processing (8)
  • .gitignore
  • build.gradle
  • src/main/java/org/sopt/solply_server/domain/bookmark/entity/Bookmark.java
  • src/main/java/org/sopt/solply_server/domain/place/entity/Place.java
  • src/main/java/org/sopt/solply_server/domain/place/repository/querydsl/PlaceRepositoryImpl.java
  • src/main/java/org/sopt/solply_server/domain/place/service/PlaceService.java
  • src/main/resources/db/migration/V10__optimize_indexes_for_bookmark_query.sql
  • src/main/resources/spy.properties

Comment thread src/main/resources/spy.properties
uykm added 2 commits March 24, 2026 04:01
- $env.TEST_TOKEN 환경변수 → users.csv 토큰 풀로 교체
- order: random으로 요청마다 다른 유저 선택
- load-test/data/ gitignore 추가 (토큰 포함 파일 보호)
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 23, 2026
@uykm uykm self-assigned this Mar 24, 2026
@uykm uykm requested review from 88guri and ImHyungsuk March 24, 2026 02:05
@uykm uykm changed the title #352 refactor: 북마크 API DB 쿼리 최적화 및 인덱스 개선 [refactor] 북마크 API DB 쿼리 최적화 및 인덱스 개선 Mar 24, 2026
- Artillery 리포트·p6spy 쿼리 로그·EXPLAIN 실행계획 분석용 Claude Code 로컬 에이전트
- .claude/agents/perf-analyzer.md 생성
@uykm uykm changed the title [refactor] 북마크 API DB 쿼리 최적화 및 인덱스 개선 [refactor] 북마크 API DB 쿼리 최적화, 인덱스 개선 및 perf-analyzer 에이전트 추가 Mar 24, 2026
Copy link
Copy Markdown

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/agents/perf-analyzer.md:
- Around line 40-44: The markdown blocks in .claude/agents/perf-analyzer.md are
missing fenced code block language tags causing markdownlint MD040 warnings; for
each example block (the "분석 대상" list, the N+1 detection block, the slow query
block, and the EXPLAIN analysis block) add an appropriate language tag (e.g.,
text for plain text examples, sql for queries, markdown where applicable)
immediately after the opening ``` so the fences become ```text or ```sql as
shown in the suggested snippets; update every similar code fence referenced
(around the sections you noted: lines ~40, 108-114, 120-124, 151-161) to remove
the CI warning.
- Around line 76-80: The KPI thresholds under "KPI 임계값" are overlapping for p95
(p95 > 3,000ms and p95 > 1,000ms) and must be made mutually exclusive or
prioritized so only one severity is emitted; update the p95 rules in the "KPI
임계값" block to use exclusive ranges (e.g., p95 > 3000ms → [심각], 1000ms < p95 ≤
3000ms → [경고], p95 ≤ 1000ms → [주의]) or explicitly document a priority order
(e.g., check >3000ms first, then >1000ms) so the analyzer logic tied to these
labels produces a single severity per measurement.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 05a1b65c-8875-471a-83bf-818b2571e6e5

📥 Commits

Reviewing files that changed from the base of the PR and between e142fe8 and 03d272d.

📒 Files selected for processing (5)
  • .claude/agents/perf-analyzer.md
  • .claude/settings.local.json
  • build.gradle
  • src/main/resources/spy.properties
  • src/test/resources/application-test.yml
✅ Files skipped from review due to trivial changes (3)
  • src/test/resources/application-test.yml
  • .claude/settings.local.json
  • src/main/resources/spy.properties
🚧 Files skipped from review as they are similar to previous changes (1)
  • build.gradle

Comment thread .claude/agents/perf-analyzer.md
Comment thread .claude/agents/perf-analyzer.md Outdated
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
.claude/agents/perf-analyzer.md (1)

40-44: ⚠️ Potential issue | 🟡 Minor

펜스 코드블록 언어 태그를 지정해 MD040 경고를 제거해주세요.

Line 40, Line 151의 코드블록 시작 펜스에 언어가 없어 markdownlint 경고가 발생합니다. 두 블록 모두 text로 지정하면 됩니다.

제안 수정안
-```
+```text
 분석 대상:
 - 리포트: place/01~04 (4개), course/01,02,04 (3개)
 - 쿼리 로그: place-list-bookmark/01,03,04 (3개), folder-preview/01,03,04 (3개)

- +text
EXPLAIN 분석 결과
테이블: bookmarks
type: ALL → [심각] Full Table Scan 감지
key: NULL → 인덱스 미사용
rows: 50,000

문제: user_id + target_type 복합 인덱스 없음
권장: INDEX(user_id, target_type, target_id) 추가
예상 효과: rows 50,000 → ~10 (사용자당 평균 북마크 수 기준)

Also applies to: 151-161

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/agents/perf-analyzer.md around lines 40 - 44, Two fenced code blocks
in .claude/agents/perf-analyzer.md are missing a language tag (causing MD040);
update the opening backticks for both blocks (the one around "분석 대상:" and the
one around "EXPLAIN 분석 결과") to include the language tag "text" (i.e., change ```
to ```text) so markdownlint no longer warns; ensure both occurrences (the blocks
at the earlier section and the later EXPLAIN section) are updated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.claude/agents/perf-analyzer.md:
- Around line 40-44: Two fenced code blocks in .claude/agents/perf-analyzer.md
are missing a language tag (causing MD040); update the opening backticks for
both blocks (the one around "분석 대상:" and the one around "EXPLAIN 분석 결과") to
include the language tag "text" (i.e., change ``` to ```text) so markdownlint no
longer warns; ensure both occurrences (the blocks at the earlier section and the
later EXPLAIN section) are updated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 5a57b8af-cfd6-4fa6-a338-357ec0a1efc1

📥 Commits

Reviewing files that changed from the base of the PR and between 03d272d and 7e6c81b.

📒 Files selected for processing (1)
  • .claude/agents/perf-analyzer.md

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 24, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 26, 2026
@uykm uykm merged commit 64e1f01 into develop Mar 30, 2026
2 checks passed
@uykm uykm deleted the refactor/#352-optimize-bookmark-query branch March 30, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Refactor] 북마크 API DB 부하 개선 - 쿼리 최적화 및 인덱스 추가

1 participant