Skip to content

[fix] 통합검색 성능 최적화 및 FULLTEXT 검색 개선#144

Merged
geleego merged 15 commits intodevelopfrom
test/search/chan
Sep 3, 2025
Merged

[fix] 통합검색 성능 최적화 및 FULLTEXT 검색 개선#144
geleego merged 15 commits intodevelopfrom
test/search/chan

Conversation

@ghkddlscks19
Copy link
Contributor

@ghkddlscks19 ghkddlscks19 commented Sep 3, 2025

#️⃣ Issue Number

📝 요약(Summary)

  • FULLTEXT 검색 점수 기준 조정: 관련도 점수를 4점 이상으로 설정하여 검색 품질 향상
  • 데이터 타입 통일: memberCount 컬럼을 Integer에서 Long으로 변경하여 타입 캐스팅 오류 해결
  • 쿼리 최적화: WHERE 절 우선순위 조정 및 서브쿼리 활용으로 성능 개선
  • 복합 인덱스 개선: 지역 및 관심사 검색을 위한 인덱스 최적화
  • 멤버 수 관리 로직 개선: 모임 생성/가입/탈퇴 시 실시간 멤버 수 업데이트

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📸스크린샷 (선택)

💬 공유사항 to 리뷰어

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

  • New Features
    • 클럽에 인원수(memberCount) 표시가 추가되고 생성/가입/탈퇴 시 자동으로 갱신됩니다.
    • WebSocket(/ws-native) 및 Grafana/InfluxDB 관련 모니터링·수집 엔드포인트 접근이 허용됩니다.
    • CORS 허용 도메인에 ngrok 패턴이 추가됩니다.
  • Refactor
    • 검색 정렬이 인원수·생성일 중심으로 개선되고 키워드 점수 임계값이 상향되었습니다.
    • 조회 경로와 인덱스 재구성으로 검색 성능이 향상됩니다.
  • Style
    • 불필요한 공백 제거 및 멤버수 타입 처리 방식이 정리되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Sep 3, 2025

Walkthrough

Club 엔티티에 Long형 memberCount 필드를 추가하고 증감 메서드를 도입했으며, 리포지토리 쿼리들을 userClub 조인/COUNT 기반에서 club.memberCount 컬럼 기반으로 변경하고 정렬/인덱스를 갱신했습니다. ClubService에 생성/가입/탈퇴 시 memberCount 동기화가 추가되었습니다. 보안 화이트리스트와 CORS 패턴도 확장되었습니다.

Changes

Cohort / File(s) Summary
Domain Entity: Club
src/main/java/com/example/onlyone/domain/club/entity/Club.java
memberCount(Long) 필드 추가(@column name = "member_count", @NotNull, default 0L), incrementMemberCount()decrementMemberCount() 메서드 추가, 테이블 인덱스 세트 재구성(멤버수 기반 복합 인덱스 추가)
Repository Queries (QueryDSL)
src/main/java/com/example/onlyone/domain/club/repository/ClubRepositoryImpl.java
userClub LEFT JOIN 및 GROUP BY 제거, 쿼리 프로젝션을 club 또는 [club, club.memberCount] 형태로 변경, 정렬을 memberCount 컬럼 기반으로 전환(새 createOrderSpecifiersWithColumn 메서드 추가), 키워드 점수 필터 임계값을 gt(4.0)로 조정
Service: Club membership sync
src/main/java/com/example/onlyone/domain/club/service/ClubService.java
createClub/joinClub 흐름에서 club.incrementMemberCount() 호출 추가, leaveClub에서 club.decrementMemberCount() 호출 추가
Search result mapping
src/main/java/com/example/onlyone/domain/search/service/SearchService.java
키워드 검색 결과의 memberCount 추출을 ((Number) ...).longValue()(Long) ... 캐스팅으로 변경(형변환 방식 수정), 불필요 공백 라인 제거
Security/CORS
src/main/java/com/example/onlyone/global/config/SecurityConfig.java
AUTH_WHITELIST에 /ws-native, /grafana/**, /influxdb/**, /write 추가; /ws-native 단일 경로도 허용; CORS 허용 origin 패턴에 https://*.ngrok-free.app 추가

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant S as ClubService
  participant R as UserClubRepository
  participant C as Club(Entity)

  rect rgba(220,240,255,0.5)
  note over U,S: 클럽 생성
  U->>S: createClub(...)
  S->>R: save(leader UserClub)
  R-->>S: persisted
  S->>C: incrementMemberCount()
  end

  rect rgba(220,255,220,0.5)
  note over U,S: 클럽 가입
  U->>S: joinClub(clubId)
  S->>R: save(member UserClub)
  R-->>S: persisted
  S->>C: incrementMemberCount()
  end

  rect rgba(255,235,220,0.5)
  note over U,S: 클럽 탈퇴
  U->>S: leaveClub(clubId)
  S->>R: deleteBy(user, club)
  R-->>S: deleted
  S->>C: decrementMemberCount()  %% 하한 0
  end
Loading
sequenceDiagram
  autonumber
  participant API as SearchService
  participant Q as ClubRepositoryImpl
  participant DB as DB

  API->>Q: searchByKeywordWithFilter(filter)
  note over Q: INNER JOIN interest\nmemberCount 컬럼 사용\n키워드 스코어 > 4.0
  Q->>DB: SELECT club, club.member_count ... ORDER BY score?, member_count, created_at
  DB-->>Q: rows [club, memberCount]
  Q-->>API: results (cast memberCount to Long)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Search, Club

Suggested reviewers

  • doehy
  • geleego
  • NamYeonW00
  • gkdudans
  • choigpt

Poem

깡총, 칼럼 하나 더 심었지요, memberCount라 불러요 🐇
가입할 땐 톡 올리고, 탈퇴땐 살짝 내리고
바닥은 영원히 0, 음수는 싫어요
검색은 이제 숫자 따라 흐르고, 인덱스도 새 옷을 입었네 ✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/search/chan

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 5

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/com/example/onlyone/global/config/SecurityConfig.java (1)

75-82: ngrok 와일드카드 Origin 제거 및 CORS 설정 외부화

https://*.ngrok-free.app 과 setAllowCredentials(true) 조합은 운영 환경에서 임의 서브도메인에 쿠키 포함 요청을 허용해 보안 취약을 초래합니다. 개발 프로파일로 분리하거나 환경 변수(app.allowed-origin-patterns)로 제어하고, 운영에서는 명확한 Origin만 화이트리스트에 등록하세요.

-        configuration.setAllowedOriginPatterns(List.of(
+        configuration.setAllowedOriginPatterns(List.of(
             "http://localhost:8080",
             "http://localhost:5173",
             "https://only-one-front-delta.vercel.app",
-            "https://*.ngrok-free.app",
-            baseUrl
+            // 운영 환경 제거: "https://*.ngrok-free.app",
+            baseUrl
         ));
🧹 Nitpick comments (7)
src/main/java/com/example/onlyone/global/config/SecurityConfig.java (1)

131-131: 중복 허용 규칙 정리 제안: /ws-native가 두 곳에서 permitAll 됩니다.

AUTH_WHITELISTauthorizeHttpRequests 모두에 /ws-native가 있어 유지보수성이 떨어집니다. 한 곳으로 통일해 주세요.

-                        .requestMatchers("/ws-native", "/ws-native/**").permitAll()
+                        // AUTH_WHITELIST에 이미 포함되어 있으면 여기서는 제거
src/main/java/com/example/onlyone/domain/club/service/ClubService.java (1)

101-113: 상세 조회 인원수는 memberCount 컬럼을 사용해 일관성/성능을 확보하세요.

countByClub_ClubId 대신 club.getMemberCount() 사용이 가볍고, 검색/정렬에서도 동일 소스(memberCount)를 사용하므로 일관됩니다.

-        int userCount = userClubRepository.countByClub_ClubId(club.getClubId());
+        long userCount = club.getMemberCount();
src/main/java/com/example/onlyone/domain/search/service/SearchService.java (2)

167-167: 캐스팅을 Long으로 고정하면 DB/드라이버에 따라 ClassCastException 위험이 있습니다. Number 기반 변환으로 복원력 확보.

-                    .memberCount((Long) result[6])
+                    .memberCount(((Number) result[6]).longValue())

156-158: Enum → String → Enum 왕복 변환은 불필요합니다. Repo에서 Category를 그대로 반환하도록 바꾸면 안전하고 간결합니다.

Repo 매핑 변경 제안(아래 RepositoryImpl 코멘트 참고) 후:

-            String categoryName = (String) result[5];
-            String koreanCategoryName = Category.valueOf(categoryName).getKoreanName();
+            Category category = (Category) result[5];
+            String koreanCategoryName = category.getKoreanName();
src/main/java/com/example/onlyone/domain/club/entity/Club.java (1)

17-21: 정렬이 memberCount 중심으로 바뀐 만큼 인덱스에 member_count 포함을 고려하세요.

현재 인덱스는 created_at 중심입니다. 다음 조합(예: Flyway SQL로 생성)을 권장합니다.

  • (interest_id, city, district, member_count DESC, created_at DESC)
  • (interest_id, member_count DESC, created_at DESC)
  • (city, district, member_count DESC, created_at DESC)
  • (member_count DESC, created_at DESC)

JPA @Index로는 DESC 표기가 이식성에 제한이 있으니, SQL 마이그레이션으로 명시 생성하는 것이 안전합니다.

src/main/java/com/example/onlyone/domain/club/repository/ClubRepositoryImpl.java (2)

82-90: 카테고리를 문자열로 변환했다가 서비스에서 다시 Enum으로 복원하는 왕복 변환을 제거하세요.

-                tuple.get(interest.category).name(), // Category enum을 String으로 변환
+                tuple.get(interest.category),        // Category Enum 그대로 전달

위 변경 시 SearchService의 캐스팅/한글명 조회도 단순화됩니다(해당 파일 코멘트 참고).


21-21: 불필요한 import 정리 제안(Projections 등).

가독성을 위해 미사용 import 제거를 권장합니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cad183e and fdc05a1.

📒 Files selected for processing (5)
  • src/main/java/com/example/onlyone/domain/club/entity/Club.java (3 hunks)
  • src/main/java/com/example/onlyone/domain/club/repository/ClubRepositoryImpl.java (6 hunks)
  • src/main/java/com/example/onlyone/domain/club/service/ClubService.java (3 hunks)
  • src/main/java/com/example/onlyone/domain/search/service/SearchService.java (1 hunks)
  • src/main/java/com/example/onlyone/global/config/SecurityConfig.java (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-06T07:54:25.923Z
Learnt from: ghkddlscks19
PR: GoormOnlyOne/OnlyOne-Back#86
File: src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java:14-18
Timestamp: 2025-08-06T07:54:25.923Z
Learning: In the OnlyOne-Back project, the ClubRepository search methods use List<Object[]> return type instead of Page<Object[]> even with Pageable parameters. This is intentionally designed for infinite scrolling implementation on the frontend, where only the actual data is needed without pagination metadata.

Applied to files:

  • src/main/java/com/example/onlyone/domain/club/repository/ClubRepositoryImpl.java
📚 Learning: 2025-08-06T08:12:05.969Z
Learnt from: ghkddlscks19
PR: GoormOnlyOne/OnlyOne-Back#86
File: src/main/java/com/example/onlyone/domain/club/repository/ClubRepository.java:66-70
Timestamp: 2025-08-06T08:12:05.969Z
Learning: OnlyOne-Back 프로젝트의 검색 시스템은 관심사, 지역, 최신순, 멤버순 필터가 병렬적으로 선택 가능하도록 설계되어 있어서, ORDER BY 절에서 여러 CASE 문을 사용하여 다중 정렬 조건을 동시에 적용할 수 있습니다.

Applied to files:

  • src/main/java/com/example/onlyone/domain/club/repository/ClubRepositoryImpl.java
🔇 Additional comments (3)
src/main/java/com/example/onlyone/global/config/SecurityConfig.java (1)

120-121: SSE 필터 → JWT 필터 순서 배치는 적절합니다.

SSE 경로 선처리 후 JWT 인증으로 이어지는 체인이 의도에 부합합니다.

src/main/java/com/example/onlyone/domain/club/repository/ClubRepositoryImpl.java (2)

60-62: 정렬 스펙 분리 좋습니다. 키워드/비키워드 케이스가 명확해졌습니다.


243-247: FULLTEXT 함수 템플릿 사용 확인 필요: 모드/검색 필드 구성과 임계치(>4.0)의 정합성 검증 권장.

현재 function('match', {0}, {1}, {2})로 등록된 사용자 함수가 MySQL MATCH(name, description) AGAINST (? IN NATURAL LANGUAGE MODE)에 매핑되는지 확인이 필요합니다. BOOLEAN/NATURAL 모드에 따라 점수 스케일이 달라 4.0 임계값 영향이 큽니다.

원한다면 문서화/테스트 케이스(키워드 vs 비키워드, 점수 분포) 작성 도와드릴게요.

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

♻️ Duplicate comments (1)
src/main/java/com/example/onlyone/domain/club/entity/Club.java (1)

59-61: 마이그레이션/백필 누락 시 운영 장애 가능 — 기존 데이터에 대해 NULL 제거·백필을 보장하세요.

  • 기존 club 레코드의 member_count를 user_club 기준으로 역산/0 세팅 후 NOT NULL 제약 적용 순서를 지켜 주세요.
  • 배포 전/후 검증 쿼리와 롤백 플랜 포함 권장.

원하시면 Flyway/Liquibase 스크립트 초안을 드리겠습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fdc05a1 and e112e55.

📒 Files selected for processing (1)
  • src/main/java/com/example/onlyone/domain/club/entity/Club.java (3 hunks)
🔇 Additional comments (2)
src/main/java/com/example/onlyone/domain/club/entity/Club.java (2)

59-61: LGTM: memberCount 필드에 nullable=false와 기본값 0L 적용이 적절합니다.

이전 위험(NPE/DB 제약 위반) 해소에 도움이 됩니다.


17-23: 인덱스 DDL 휴대성·중복성 검토 필요

  • 휴대성: @Index(columnList)"DESC" 지정 시, H2 등 일부 Dialect에서 DDL 오류가 발생할 수 있습니다. 마이그레이션 스크립트로 명시적 DDL 관리하거나 "DESC" 제거를 검토하세요.
  • 중복성:
    (interest_id, member_count DESC, created_at DESC)(interest_id, city, district, member_count DESC, created_at DESC)가 각각 (interest_id)(interest_id, city, district)를 좌측 접두어로 포함합니다.
    • 실제 쿼리 플랜에서 사용 여부를 확인한 뒤, 불필요한 인덱스를 제거해 쓰기 오버헤드 및 스토리지 절감하세요.
  • 매핑: BaseTimeEntity@Column(name="created_at")가 정의되어 있어 인덱스 대상 컬럼 매핑은 올바릅니다.

@geleego geleego merged commit 27d0fd6 into develop Sep 3, 2025
1 check passed
@geleego
Copy link
Contributor

geleego commented Sep 3, 2025

수고하셨습니다!

choigpt pushed a commit that referenced this pull request Sep 17, 2025
[fix] 통합검색 성능 최적화 및 FULLTEXT 검색 개선
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