Skip to content

Conversation

@NCookies
Copy link
Collaborator

@NCookies NCookies commented Nov 28, 2025

  • 우선은 검색어 자동완성 기능을 비활성화하고, 문제 검색 기능은 제목과 설명만 포함되도록 함
  • ES 비활성화 테스트를 위한 임시방편이므로 추후 추가적인 작업 필요

Summary by CodeRabbit

릴리스 노트

  • 성능 개선

    • 문제 검색 결과에 대한 캐싱 기능을 추가하여 검색 성능이 개선되었습니다.
  • 개선 사항

    • 검색 기능의 내부 구조를 재정비하여 안정성과 유지보수성이 향상되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

- 우선은 검색어 자동완성 기능을 비활성화하고, 문제 검색 기능은 제목과 설명만 포함되도록 함
- ES 비활성화 테스트를 위한 임시방편이므로 추후 추가적인 작업 필요
@NCookies NCookies self-assigned this Nov 28, 2025
@NCookies NCookies added the enhancement New feature or request label Nov 28, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

이 PR은 Elasticsearch 기반의 ProblemSearchDocument에서 JPA 기반의 Problem 엔티티로 검색 기능을 마이그레이션합니다. 새로운 저장소 인터페이스와 구현체를 도입하고, 도메인 서비스를 업데이트하며, 검색 서비스에 캐싱을 추가하고, ProblemSearchResponse에 팩토리 메서드를 확장합니다.

Changes

응집도 / 파일(들) 변경 요약
Response DTO 업데이트
src/main/java/org/ezcode/codetest/application/problem/dto/response/ProblemSearchResponse.java
Problem 객체로부터 ProblemSearchResponse를 구성하는 새로운 public static 팩토리 메서드 from(Problem problem) 추가; Problem import 추가
애플리케이션 서비스 레이어
src/main/java/org/ezcode/codetest/application/problem/service/ProblemSearchService.java
getProblemSuggestions(String) 메서드에 @Cacheable 어노테이션 추가; 기존 변환 로직을 제거하고 null 반환으로 변경
도메인 레이어 저장소 인터페이스
src/main/java/org/ezcode/codetest/domain/problem/repository/ProblemSearchRepository.java
새로운 public 인터페이스 ProblemSearchRepository 추가; searchProblems(String keyword)로 List<Problem> 반환하는 메서드 정의
도메인 서비스
src/main/java/org/ezcode/codetest/domain/problem/service/ProblemSearchDomainService.java
ProblemDocumentRepository에서 ProblemSearchRepository로 저장소 타입 변경; getSuggestionsByKeyword에서 searchByKeywordMatch로 메서드명 변경; 반환 타입을 Set<ProblemSearchDocument>에서 List<Problem>으로 변경; 메서드 본문을 새로운 저장소 메서드 호출로 업데이트
JPA 저장소
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchJpaRepository.java
Problem 엔티티용 새로운 Spring Data JPA 저장소 인터페이스 ProblemSearchJpaRepository 추가; 제목 또는 설명에서 키워드 검색을 위한 네이티브 SQL 쿼리 메서드 searchByKeyword(String keyword) 추가
저장소 구현체
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchRepositoryImpl.java
ProblemSearchRepository를 구현하는 새로운 Spring 관리 저장소 구현체 ProblemSearchRepositoryImpl 추가; ProblemSearchJpaRepository에 의존성 주입; searchProblems 메서드를 JPA 저장소의 searchByKeyword에 위임

Sequence Diagram

sequenceDiagram
    participant Client
    participant ProblemSearchService as ProblemSearchService<br/>(캐싱)
    participant ProblemSearchDomainService as ProblemSearchDomainService
    participant ProblemSearchRepository as ProblemSearchRepository<br/>(인터페이스)
    participant ProblemSearchRepositoryImpl as ProblemSearchRepositoryImpl
    participant ProblemSearchJpaRepository as ProblemSearchJpaRepository<br/>(JPA)
    participant Database as Database

    rect rgb(200, 220, 255)
    note over Client,Database: 새로운 검색 흐름 (JPA 기반)
    Client->>ProblemSearchService: getProblemSuggestions(keyword)
    alt 캐시 히트
        ProblemSearchService-->>Client: 캐시된 결과
    else 캐시 미스
        ProblemSearchService->>ProblemSearchDomainService: searchByKeywordMatch(keyword)
        ProblemSearchDomainService->>ProblemSearchRepository: searchProblems(keyword)
        ProblemSearchRepository->>ProblemSearchRepositoryImpl: searchProblems(keyword)
        ProblemSearchRepositoryImpl->>ProblemSearchJpaRepository: searchByKeyword(keyword)
        ProblemSearchJpaRepository->>Database: SELECT * FROM problems<br/>WHERE title LIKE ? OR description LIKE ?
        Database-->>ProblemSearchJpaRepository: List<Problem>
        ProblemSearchJpaRepository-->>ProblemSearchRepositoryImpl: List<Problem>
        ProblemSearchRepositoryImpl-->>ProblemSearchRepository: List<Problem>
        ProblemSearchRepository-->>ProblemSearchDomainService: List<Problem>
        ProblemSearchDomainService-->>ProblemSearchService: List<Problem>
        ProblemSearchService->>ProblemSearchService: 결과 캐싱
        ProblemSearchService-->>Client: List<Problem>
    end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 특별 주의 필요 영역:
    • ProblemSearchService.getProblemSuggestions() 메서드의 null 반환이 기존 호출처에 영향을 미치지 않는지 확인 필요
    • ProblemSearchDomainService의 메서드명 변경(getSuggestionsByKeyword → searchByKeywordMatch)이 모든 호출처에서 업데이트되었는지 확인
    • ProblemSearchJpaRepository의 네이티브 SQL 쿼리가 데이터베이스 방언(dialect)과 호환되는지 검증
    • 캐싱 전략(@Cacheable)이 검색 기능의 정확성과 신선도 요구사항과 맞는지 검토

Possibly related PRs

  • feat : 검색엔진 알고리즘 강화 #34: 같은 검색 관련 클래스(ProblemSearchService, ProblemSearchDomainService, ProblemSearchResponse)를 수정하지만, 이 PR이 Elasticsearch ProblemSearchDocument 기반에서 JPA Problem 엔티티로 마이그레이션하는 반면 해당 PR은 Elasticsearch 기반 검색을 구현/강화합니다.

Suggested reviewers

  • minjee2758
  • Kimminu7
  • pokerbearkr
  • chat26666
  • thezz9

Poem

🐰 검색이 달라졌어요,

Elasticsearch에서 JPA로 변신했네요.

캐시도 추가되고, 저장소 계층도 깔끔해졌어요.

데이터베이스와 더 가까워졌어요! 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 Elasticsearch를 임시 코드로 대체한다는 주요 변경사항을 명확하게 설명하고 있으며, 실제 변경사항(ES 기반 검색을 임시 코드로 변경)과 정확히 일치합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/replace-es

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

@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)
src/main/java/org/ezcode/codetest/domain/problem/service/ProblemSearchDomainService.java (1)

3-5: 사용하지 않는 import 제거.

Line 3의 HashSet과 Line 5의 Set import가 더 이상 사용되지 않습니다. 코드를 정리하기 위해 제거하세요.

다음 수정을 적용하세요:

-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79aa8ea and 166a448.

📒 Files selected for processing (6)
  • src/main/java/org/ezcode/codetest/application/problem/dto/response/ProblemSearchResponse.java (2 hunks)
  • src/main/java/org/ezcode/codetest/application/problem/service/ProblemSearchService.java (2 hunks)
  • src/main/java/org/ezcode/codetest/domain/problem/repository/ProblemSearchRepository.java (1 hunks)
  • src/main/java/org/ezcode/codetest/domain/problem/service/ProblemSearchDomainService.java (2 hunks)
  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchJpaRepository.java (1 hunks)
  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchRepositoryImpl.java (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: Kimminu7
Repo: ezcode-my/backend PR: 133
File: src/test/java/org/ezcode/codetest/domain/problem/service/ProblemDomainServiceTest.java:92-99
Timestamp: 2025-07-02T12:05:54.917Z
Learning: ProblemDomainService의 removeProblem 메서드는 DB에서 Problem을 삭제한 후 Elasticsearch에서도 해당 ProblemSearchDocument를 찾아서 삭제합니다. 만약 Elasticsearch에서 문서를 찾지 못하면 ProblemException(PROBLEM_NOT_FOUND)을 던지므로, 테스트에서는 problem.getId()와 searchRepository.findById() 모두 적절하게 mock해야 합니다.
Learnt from: chat26666
Repo: ezcode-my/backend PR: 31
File: src/main/java/org/ezcode/codetest/domain/problem/model/entity/ProblemSearchDocument.java:49-56
Timestamp: 2025-06-05T00:52:54.460Z
Learning: ProblemSearchDocument.from() 메서드는 트랜잭션 내에서 DB에서 실제로 조회한 Problem 엔티티만 사용하므로 null 체크가 불필요함. 이 컨텍스트에서는 null Problem이 있을 경우 이미 이전 단계에서 예외가 발생함.
Learnt from: Kimminu7
Repo: ezcode-my/backend PR: 63
File: src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemQueryRepositoryImpl.java:24-40
Timestamp: 2025-06-14T14:33:58.372Z
Learning: ProblemController에서 ProblemSearchCondition 객체는 항상 new ProblemSearchCondition(category, difficulty)로 유효한 인스턴스를 생성해서 전달하므로, ProblemQueryRepositoryImpl의 searchByCondition 메서드에서 searchCondition 파라미터 자체에 대한 null 체크는 불필요하다. category와 difficulty 필드만 각각 null일 수 있다.
Learnt from: chat26666
Repo: ezcode-my/backend PR: 31
File: src/main/java/org/ezcode/codetest/domain/problem/service/ProblemDomainService.java:25-27
Timestamp: 2025-06-04T15:11:19.343Z
Learning: In ProblemDomainService, when saving problems to both database and Elasticsearch, maintain transactional consistency by allowing rollback if either save operation fails. Data integrity between DB and ES is prioritized over availability - if Elasticsearch save fails, the entire transaction should roll back to prevent data inconsistency.
📚 Learning: 2025-07-02T12:05:54.917Z
Learnt from: Kimminu7
Repo: ezcode-my/backend PR: 133
File: src/test/java/org/ezcode/codetest/domain/problem/service/ProblemDomainServiceTest.java:92-99
Timestamp: 2025-07-02T12:05:54.917Z
Learning: ProblemDomainService의 removeProblem 메서드는 DB에서 Problem을 삭제한 후 Elasticsearch에서도 해당 ProblemSearchDocument를 찾아서 삭제합니다. 만약 Elasticsearch에서 문서를 찾지 못하면 ProblemException(PROBLEM_NOT_FOUND)을 던지므로, 테스트에서는 problem.getId()와 searchRepository.findById() 모두 적절하게 mock해야 합니다.

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/problem/repository/ProblemSearchRepository.java
  • src/main/java/org/ezcode/codetest/application/problem/dto/response/ProblemSearchResponse.java
  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchJpaRepository.java
  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchRepositoryImpl.java
  • src/main/java/org/ezcode/codetest/domain/problem/service/ProblemSearchDomainService.java
  • src/main/java/org/ezcode/codetest/application/problem/service/ProblemSearchService.java
📚 Learning: 2025-06-14T14:33:58.372Z
Learnt from: Kimminu7
Repo: ezcode-my/backend PR: 63
File: src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemQueryRepositoryImpl.java:24-40
Timestamp: 2025-06-14T14:33:58.372Z
Learning: ProblemController에서 ProblemSearchCondition 객체는 항상 new ProblemSearchCondition(category, difficulty)로 유효한 인스턴스를 생성해서 전달하므로, ProblemQueryRepositoryImpl의 searchByCondition 메서드에서 searchCondition 파라미터 자체에 대한 null 체크는 불필요하다. category와 difficulty 필드만 각각 null일 수 있다.

Applied to files:

  • src/main/java/org/ezcode/codetest/domain/problem/repository/ProblemSearchRepository.java
  • src/main/java/org/ezcode/codetest/application/problem/dto/response/ProblemSearchResponse.java
  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchJpaRepository.java
  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchRepositoryImpl.java
  • src/main/java/org/ezcode/codetest/domain/problem/service/ProblemSearchDomainService.java
📚 Learning: 2025-06-05T00:52:54.460Z
Learnt from: chat26666
Repo: ezcode-my/backend PR: 31
File: src/main/java/org/ezcode/codetest/domain/problem/model/entity/ProblemSearchDocument.java:49-56
Timestamp: 2025-06-05T00:52:54.460Z
Learning: ProblemSearchDocument.from() 메서드는 트랜잭션 내에서 DB에서 실제로 조회한 Problem 엔티티만 사용하므로 null 체크가 불필요함. 이 컨텍스트에서는 null Problem이 있을 경우 이미 이전 단계에서 예외가 발생함.

Applied to files:

  • src/main/java/org/ezcode/codetest/application/problem/dto/response/ProblemSearchResponse.java
  • src/main/java/org/ezcode/codetest/domain/problem/service/ProblemSearchDomainService.java
📚 Learning: 2025-06-15T04:37:29.231Z
Learnt from: chat26666
Repo: ezcode-my/backend PR: 64
File: src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java:0-0
Timestamp: 2025-06-15T04:37:29.231Z
Learning: EncounterChoiceRepositoryImpl in src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java is intentionally a skeleton implementation that is work-in-progress and will be completed later.

Applied to files:

  • src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchRepositoryImpl.java
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/main/java/org/ezcode/codetest/domain/problem/repository/ProblemSearchRepository.java (1)

7-11: LGTM!

검색 전용 리포지토리 인터페이스가 깔끔하게 정의되어 있습니다. 메서드 시그니처도 명확합니다.

src/main/java/org/ezcode/codetest/application/problem/service/ProblemSearchService.java (1)

26-32: LGTM!

새로운 searchByKeywordMatch 메서드를 사용하고 Problem 엔티티에서 ProblemSearchResponse로 변환하는 로직이 올바르게 구현되었습니다.

src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/problem/ProblemSearchRepositoryImpl.java (1)

11-21: LGTM!

리포지토리 구현이 깔끔하게 JPA 리포지토리에 위임하고 있습니다. 구조가 명확하고 단순합니다.

src/main/java/org/ezcode/codetest/domain/problem/service/ProblemSearchDomainService.java (1)

17-23: LGTM!

ProblemSearchRepository로 교체하고 메서드명을 searchByKeywordMatch로 변경한 것이 새로운 검색 아키텍처와 잘 맞습니다. List<Problem>을 반환하는 것도 적절합니다.

Comment on lines +41 to +52
public static ProblemSearchResponse from(Problem problem) {

return ProblemSearchResponse.builder()
.id(problem.getId())
.title(problem.getTitle())
.category(null) // TODO: 카테고리 입력해줘야 함
.difficulty(problem.getDifficulty().getDifficulty())
.reference(problem.getReference().toString())
.description(problem.getDescription())
.score(problem.getScore())
.build();
}
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
# Search for ProblemSearchResponse usages and how the category field is accessed

# Find files that import or use ProblemSearchResponse
rg -nP --type=java 'ProblemSearchResponse' -B2 -A5 | head -100

# Also specifically look for category field access
echo "=== Searching for category field/method access on ProblemSearchResponse ==="
rg -nP --type=java '\.getCategory\(\)|\.category' -B3 -A3

Repository: ezcode-my/backend

Length of output: 16749


🏁 Script executed:

#!/bin/bash
# Search for tests or client code that use ProblemSearchResponse
rg -nP --type=java '\.category|ProblemSearchResponse' src/test -B2 -A2 | head -80

# Also check for frontend files that might consume this API
find . -type f \( -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" \) | grep -i problem | head -20

Repository: ezcode-my/backend

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Read the frontend file to see how category is used
cat src/main/resources/static/js/problem-search.js

Repository: ezcode-my/backend

Length of output: 4297


The category field must be populated before sending the response to clients.

Line 46 sets category to null with a TODO comment. The frontend code (problem-search.js, line 72-73) directly renders this field without null checks: <div><b>Category:</b> ${item.category}</div>. When category is null, the UI will display "Category: null", degrading user experience.

Additionally, there's an inconsistency: the from(ProblemSearchDocument) factory method (line 33) populates category with document.getCategories(), while from(Problem) (line 46) leaves it null. API consumers will receive inconsistent response structures.

Populate the category field using Problem's category relationship before the TODO is resolved, or remove the field temporarily if it cannot be populated reliably.

🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/application/problem/dto/response/ProblemSearchResponse.java
around lines 41-52, the from(Problem) factory sets category to null which causes
the frontend to render "null" and creates inconsistency with the
from(ProblemSearchDocument) factory that uses document.getCategories(); replace
the null assignment by populating category from the Problem entity (match the
same shape as the other factory: e.g. use problem.getCategories() or map
problem.getCategory() to the same type), and guard against null by defaulting to
an empty list/string as appropriate so the response always contains a non-null
category value.

Comment on lines +19 to 24
// Redis 캐시 적용 (Cache Name: suggestions, Key: keyword)
@Cacheable(value = "suggestions", key = "#keyword", unless = "#result.isEmpty()")
public Set<String> getProblemSuggestions(String keyword) {

return searchDomainService
.getSuggestionsByKeyword(keyword)
.stream()
.flatMap(doc -> {
Stream.Builder<String> searchedKeyword = Stream.builder();

Optional.ofNullable(doc.getTitle()).ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getReference())
.map(Enum::toString)
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getDifficulty())
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getDescription())
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getDifficultyEn())
.map(Enum::toString)
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getReferenceKor())
.ifPresent(searchedKeyword::add);

Optional.ofNullable(doc.getCategories())
.stream()
.flatMap(Collection::stream)
.forEach(searchedKeyword::add);

Optional.ofNullable(doc.getCategoriesKor())
.stream()
.flatMap(Collection::stream)
.forEach(searchedKeyword::add);

return searchedKeyword.build();
})
.map(String::toUpperCase)
.collect(Collectors.toSet());
return null;
}
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

null 반환으로 인한 NullPointerException 위험.

이 메서드는 다음과 같은 심각한 문제들이 있습니다:

  1. 반환 타입은 Set<String>인데 null을 반환하면 호출자에서 NullPointerException이 발생합니다.
  2. unless = "#result.isEmpty()"null에 대해 작동하지 않습니다. null.isEmpty()를 평가하려다 예외가 발생하거나, 캐시 조건이 제대로 작동하지 않습니다.
  3. 결과적으로 null 값이 캐시에 저장될 수 있습니다.

자동완성 기능을 비활성화하려면 빈 Set을 반환하거나, 메서드 시그니처를 변경하는 것이 안전합니다.

다음 수정을 적용하세요:

 @Cacheable(value = "suggestions", key = "#keyword", unless = "#result.isEmpty()")
 public Set<String> getProblemSuggestions(String keyword) {
-	return null;
+	return Set.of();  // 빈 Set 반환으로 NPE 방지
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Redis 캐시 적용 (Cache Name: suggestions, Key: keyword)
@Cacheable(value = "suggestions", key = "#keyword", unless = "#result.isEmpty()")
public Set<String> getProblemSuggestions(String keyword) {
return searchDomainService
.getSuggestionsByKeyword(keyword)
.stream()
.flatMap(doc -> {
Stream.Builder<String> searchedKeyword = Stream.builder();
Optional.ofNullable(doc.getTitle()).ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getReference())
.map(Enum::toString)
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getDifficulty())
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getDescription())
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getDifficultyEn())
.map(Enum::toString)
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getReferenceKor())
.ifPresent(searchedKeyword::add);
Optional.ofNullable(doc.getCategories())
.stream()
.flatMap(Collection::stream)
.forEach(searchedKeyword::add);
Optional.ofNullable(doc.getCategoriesKor())
.stream()
.flatMap(Collection::stream)
.forEach(searchedKeyword::add);
return searchedKeyword.build();
})
.map(String::toUpperCase)
.collect(Collectors.toSet());
return null;
}
// Redis 캐시 적용 (Cache Name: suggestions, Key: keyword)
@Cacheable(value = "suggestions", key = "#keyword", unless = "#result.isEmpty()")
public Set<String> getProblemSuggestions(String keyword) {
return Set.of(); // 빈 Set 반환으로 NPE 방지
}
🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/application/problem/service/ProblemSearchService.java
around lines 19 to 24, the method currently returns null which risks
NullPointerException and breaks the Cacheable unless expression; change the
method to never return null (return an empty Set when no suggestions, e.g.
Collections.emptySet()) and update the cache condition to safely handle null by
using unless = "#result == null || #result.isEmpty()" (or keep unless =
"#result.isEmpty()" if you ensure non-null returns), ensuring no null values are
stored in the cache.

Comment on lines +12 to +14
// TODO: FULLTEXT 인덱스 추가
@Query(value = "SELECT * FROM problem WHERE title LIKE CONCAT('%', :keyword, '%') OR description LIKE CONCAT('%', :keyword, '%')", nativeQuery = true)
List<Problem> searchByKeyword(@Param("keyword") String keyword);
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

LIKE 패턴의 성능 문제와 결과 제한 필요.

Line 13의 쿼리에 다음 문제들이 있습니다:

  1. Leading wildcard (%keyword%)는 인덱스를 사용할 수 없어 전체 테이블 스캔이 발생합니다. 데이터가 많아질수록 심각한 성능 저하가 예상됩니다.
  2. 결과 개수 제한이 없어 대량의 결과가 반환될 수 있습니다.
  3. TODO에서 언급된 FULLTEXT 인덱스를 추가하면 성능이 개선되지만, 현재는 구현되지 않았습니다.

임시 코드라고 하더라도 프로덕션 환경에서 성능 문제를 일으킬 수 있습니다.

다음 개선 방안을 제안합니다:

  1. 결과에 LIMIT 절을 추가하여 최대 반환 개수를 제한
  2. FULLTEXT 인덱스 구현 이슈를 생성하여 추적

개선된 쿼리를 생성하거나 FULLTEXT 인덱스 구현 이슈를 생성하시겠습니까?

@NCookies NCookies merged commit fb8282e into dev Nov 28, 2025
2 checks passed
@NCookies NCookies deleted the refactor/replace-es branch November 28, 2025 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants