Skip to content

Perf: [BADA-368] 추천 고도화#399

Merged
marineAqu merged 6 commits intodevelopfrom
performance/BADA-368-recommend-pgvector
Aug 7, 2025
Merged

Perf: [BADA-368] 추천 고도화#399
marineAqu merged 6 commits intodevelopfrom
performance/BADA-368-recommend-pgvector

Conversation

@marineAqu
Copy link
Copy Markdown
Contributor

#️⃣연관된 이슈

close: #392

🚀 작업 내용

  • 서버 DB pgvector 설치
  • float형 필드 생성
  • float형을 사용한 유사도 검색 API 개발
  • pgvector를 사용한 유사도 검색 API 개발

🔍 리뷰 요청 사항

@marineAqu marineAqu added this to the 💰 거래 milestone Aug 7, 2025
@marineAqu marineAqu requested a review from Copilot August 7, 2025 01:25
@marineAqu marineAqu self-assigned this Aug 7, 2025
@marineAqu marineAqu added the ⚡️performance 최적화/고도화 label Aug 7, 2025
@github-actions
Copy link
Copy Markdown

github-actions bot commented Aug 7, 2025

Failed to generate code suggestions for PR

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements an enhanced recommendation system by introducing pgvector support for similarity-based recommendations, along with new float-based and double-based vector implementations. The changes enable more efficient and scalable recommendation calculations by leveraging database-level vector operations.

Key changes:

  • Added pgvector integration for database-level similarity calculations
  • Implemented multiple vector data types (double, float, byte) for different use cases
  • Created separate service layers for different vector implementations
  • Enhanced the recommendation API with multiple endpoints for testing different approaches

Reviewed Changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
PostServiceTest.java Updates test mocks to use new double vector-based post vectorizer
VectorUtilsPg.java Utility class for pgvector operations including float-to-byte conversion and normalization
UserProfileVectorizerPg.java Pgvector implementation of user profile vectorization with float arrays
RecommendServicePg.java Pgvector-based recommendation service using JDBC repository for similarity queries
VectorUtilsFloat.java Float-based vector utility operations and similarity calculations
UserProfileVectorizerFloat.java Float implementation of user profile vectorization
RecommendServiceFloat.java Float-based recommendation service with in-memory similarity calculations
PostVectorizerFloat.java Float implementation of post vectorization
VectorUtilsDouble.java Double-based vector utility operations and similarity calculations
UserProfileVectorizerDouble.java Double implementation of user profile vectorization
RecommendServiceDouble.java Double-based recommendation service
PostVectorizerDouble.java Double implementation of post vectorization
GlobalRecommendService.java Service for batch updating vector data across all posts
PostService.java Enhanced to support multiple vector types and bulk data generation
PostSearchService.java Added RDB-based search functionality
PostVector.java New entity for storing pgvector data
Gifticon.java Updated to support both double and float vector storage
RecommendController.java Enhanced with multiple recommendation endpoints for different vector types
PostController.java Added RDB-based post search endpoint
MockController.java New controller for generating test data


// 3. 수치형 선호도 (가격, 마감 임박일수 등)
final float[] numericalPreferences = {
vectorUtilsPg.normalizePrice(userProfile.getPreparePrice() * PRICE_WEIGHT),
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

The PRICE_WEIGHT is being applied twice - once here during normalization and once earlier at line 153 during calculation. This could lead to incorrect weighting. Consider applying the weight only once, either during calculation or during normalization.

Suggested change
vectorUtilsPg.normalizePrice(userProfile.getPreparePrice() * PRICE_WEIGHT),
vectorUtilsPg.normalizePrice(userProfile.getPreparePrice()),

Copilot uses AI. Check for mistakes.
// 3. 수치형 선호도 (가격, 마감 임박일수 등)
final float[] numericalPreferences = {
vectorUtilsPg.normalizePrice(userProfile.getPreparePrice() * PRICE_WEIGHT),
vectorUtilsPg.normalizeDaysToExpiry(userProfile.getAcceptableDaysToExpiry() * DAYS_TO_EXPIRY_WEIGHT)
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

Similar to PRICE_WEIGHT, DAYS_TO_EXPIRY_WEIGHT is being applied twice - once here and once during calculation at line 154. This duplicates the weighting factor.

Suggested change
vectorUtilsPg.normalizeDaysToExpiry(userProfile.getAcceptableDaysToExpiry() * DAYS_TO_EXPIRY_WEIGHT)
vectorUtilsPg.normalizeDaysToExpiry(userProfile.getAcceptableDaysToExpiry())

Copilot uses AI. Check for mistakes.
// 3. 수치형 선호도 (가격, 마감 임박일수 등)
final float[] numericalPreferences = {
userProfile.getPreparePrice() * PRICE_WEIGHT,
vectorUtilsFloat.normalizeDaysToExpiry((int) userProfile.acceptableDaysToExpiry) * DAYS_TO_EXPIRY_WEIGHT
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

DAYS_TO_EXPIRY_WEIGHT is applied twice - once during normalization here and once during calculation. This duplicates the weighting. Also, the cast to int may lose precision from the float value.

Suggested change
vectorUtilsFloat.normalizeDaysToExpiry((int) userProfile.acceptableDaysToExpiry) * DAYS_TO_EXPIRY_WEIGHT
vectorUtilsFloat.normalizeDaysToExpiry(userProfile.acceptableDaysToExpiry) * DAYS_TO_EXPIRY_WEIGHT

Copilot uses AI. Check for mistakes.
final PostDocument postDocument = PostDocument.from(savedGifticon);
postDocumentRepository.save(postDocument);

//TODO: 여기
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

There is a TODO comment that should be addressed or removed before merging to production. The comment appears to be in Korean and doesn't provide clear guidance on what needs to be done.

Suggested change
//TODO: 여기

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +54
// gifticon.updateVectorPg(
// vector
// //pgVectorUtilsPg.vectorFloatToByte(vector)
// );
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

Commented-out code should be removed rather than left in the codebase. If this functionality is needed later, it can be retrieved from version control.

Suggested change
// gifticon.updateVectorPg(
// vector
// //pgVectorUtilsPg.vectorFloatToByte(vector)
// );

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +128
//floatVector
//byteVector
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

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

Commented-out code should be removed. These comment lines don't add value and clutter the codebase.

Suggested change
//floatVector
//byteVector

Copilot uses AI. Check for mistakes.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

이번 변경 사항은 추천 시스템의 벡터 기반 아키텍처를 대대적으로 확장 및 리팩토링하는 내용을 포함합니다. 주요 변경점으로는 Gifticon 엔티티의 벡터 필드 분리(double, float), PostgreSQL 벡터 엔티티(PostVector) 및 관련 유틸리티/서비스 도입, double/float/pgVector 기반 추천 서비스 및 벡터라이저 클래스 신설, 전체 벡터 일괄 갱신 서비스(GlobalRecommendService) 추가, Redis 캐시를 활용한 추천 결과 관리, 그리고 Gifticon 대량 생성용 Mock API 추가 등이 있습니다. 또한, 기존 PostService, RecommendController 등 주요 서비스와 컨트롤러가 새로운 벡터 구조를 지원하도록 리팩토링되었습니다. 기존의 단일 벡터화 및 추천 서비스는 모두 주석 처리되어 비활성화되었으며, JdbcRepository를 통한 PostgreSQL 벡터 검색 및 벡터 저장 기능도 새로 도입되었습니다.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

  • Complexity label: Critical
  • 근거: 새로운 엔티티, 서비스, 유틸리티 클래스가 다수 추가되었고, 기존 추천 시스템의 핵심 로직이 double/float/pgVector 방식으로 분기되어 대규모로 확장되었습니다. 컨트롤러, 서비스, 엔티티, 벡터라이저, 유틸리티, 캐싱 등 다양한 계층에 걸쳐 변화가 이루어졌으며, 추천 로직의 정확성, 데이터 일관성, 성능, 트랜잭션 처리, 예외 처리, 테스트 코드 적합성까지 종합적으로 검토가 필요합니다.
  • 예상 리뷰 소요 시간: 최소 90분 이상 (수십 개 파일, 고난이도 로직, 신규 클래스/메서드, 데이터베이스 연동 및 캐싱, 대량 데이터 처리 등 포함)

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a87380 and 604d8ee.

📒 Files selected for processing (2)
  • src/test/java/com/TwoSeaU/BaData/domain/trade/service/PostServiceTest.java (2 hunks)
  • src/test/java/com/TwoSeaU/BaData/domain/user/service/UserServiceTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/test/java/com/TwoSeaU/BaData/domain/trade/service/PostServiceTest.java
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: dionisos198
PR: Ureca-Final-Project-Team2/be_badata#388
File: src/main/java/com/TwoSeaU/BaData/domain/rental/service/RestockDeleteTargetingService.java:37-60
Timestamp: 2025-08-05T05:48:10.447Z
Learning: dionisos198는 be_badata 프로젝트에서 PR의 주요 목적에 집중하기 위해 성능 최적화 이슈를 추후 수정으로 미루는 접근 방식을 선호한다. N+1 쿼리 최적화와 같은 성능 개선 사항은 별도 작업으로 분리하여 처리한다.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#269
File: src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java:24-27
Timestamp: 2025-07-28T01:53:53.618Z
Learning: marineAqu prefers to keep method names as-is in temporary Mock APIs that are planned for deletion, prioritizing development efficiency over perfect naming conventions for code that will be removed later.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#365
File: src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostSearchService.java:44-44
Timestamp: 2025-08-03T14:04:42.957Z
Learning: marineAqu는 개별 API의 완벽한 구현보다는 모든 API 간의 일관된 구현 패턴을 유지하는 것을 우선시한다. 보안이나 성능 개선 사항이 있더라도 전체 시스템에 대한 논의 후 일괄 적용하는 것을 선호한다.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#358
File: src/main/java/com/TwoSeaU/BaData/domain/trade/service/RecommendService.java:155-169
Timestamp: 2025-08-03T04:35:46.490Z
Learning: marineAqu는 곧 삭제될 예정인 API의 경우, 최적화보다는 개발 효율성을 우선시하여 현재 구현을 임시로 유지하는 것을 선호한다. 삭제될 코드에 대해서는 완벽한 구현보다는 개발 속도를 중시한다.
⏰ 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-test
🔇 Additional comments (1)
src/test/java/com/TwoSeaU/BaData/domain/user/service/UserServiceTest.java (1)

189-189: Gifticon 생성자 변경에 맞춘 테스트 코드 업데이트가 적절합니다.

새로 추가된 float 벡터 필드에 대응하여 null 파라미터를 추가한 것이 올바릅니다. 테스트 데이터 생성 시 벡터 필드가 필요하지 않은 상황에서 null을 전달하는 것은 합리적인 선택입니다.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch performance/BADA-368-recommend-pgvector

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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
  • 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 src/utils.ts and explain its main purpose.
    • @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 comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai 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

Documentation and Community

  • 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
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: 25

🧹 Nitpick comments (16)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/PostVectorizerDouble.java (1)

27-28: 정적 맵의 동시성 문제를 고려하세요.

정적 맵들이 @PostConstruct에서 초기화되지만, 멀티스레드 환경에서의 안전성을 보장하기 위해 ConcurrentHashMap 사용을 고려하세요.

-    public final static Map<String, Integer> categoryToIndex = new HashMap<>();
-    public final static Map<String, Integer> partnerToIndex = new HashMap<>();
+    public final static Map<String, Integer> categoryToIndex = new ConcurrentHashMap<>();
+    public final static Map<String, Integer> partnerToIndex = new ConcurrentHashMap<>();
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/VectorUtilsPg.java (1)

25-32: 정규화 메서드의 일관성을 개선하세요.

반환 타입과 캐스팅이 일관되지 않습니다. 모든 메서드가 float를 반환하므로 불필요한 캐스팅을 제거하세요.

 public float normalizePrice(float price) {
-    return (float) Math.min(price / MAX_PRICE, 1.0F);
+    return Math.min(price / (float) MAX_PRICE, 1.0F);
 }

 public float normalizeDaysToExpiry(float days) {
-    return (float) Math.min(days / MAX_DAYS_TO_EXPIRY, 1.0);
+    return Math.min(days / (float) MAX_DAYS_TO_EXPIRY, 1.0F);
 }
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/PostVectorizerFloat.java (1)

66-67: 타입 변환 문제

ChronoUnit.DAYS.between()long을 반환하는데 float 가중치와 곱셈 시 정밀도 손실이 발생할 수 있습니다.

 final float[] numericalFeatures = {
     vectorUtilsFloat.normalizePrice(price.intValue() * PRICE_WEIGHT),
-    vectorUtilsFloat.normalizeDaysToExpiry(ChronoUnit.DAYS.between(LocalDate.now(), deadLine) * DAYS_TO_EXPIRY_WEIGHT)
+    vectorUtilsFloat.normalizeDaysToExpiry((float) ChronoUnit.DAYS.between(LocalDate.now(), deadLine) * DAYS_TO_EXPIRY_WEIGHT)
 };
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/GlobalRecommendService.java (2)

9-9: Spring 트랜잭션 어노테이션 사용 권장

jakarta.transaction.Transactional 대신 Spring의 @Transactional을 사용하는 것이 Spring 환경에서 더 적합합니다.

-import jakarta.transaction.Transactional;
+import org.springframework.transaction.annotation.Transactional;

20-20: 사용하지 않는 코드 정리

pgVectorUtilsPg는 주입받지만 사용되지 않고 있으며, 주석 처리된 코드가 있습니다. 사용하지 않는다면 제거하거나, 향후 구현 계획이 있다면 TODO 주석으로 명확히 표시하세요.

-    private final VectorUtilsPg pgVectorUtilsPg;
     private final JdbcRepository jdbcRepository;

     // ...
     
     gifticon.updateFloatVector(vector);
     jdbcRepository.updatePostVector(gifticon.getId(), vector);
-//            gifticon.updateVectorPg(
-//                    vector
-//                    //pgVectorUtilsPg.vectorFloatToByte(vector)
-//            );
+    // TODO: pgVector 지원 추가 예정

Also applies to: 51-54

src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/UserProfileVectorizerDouble.java (1)

124-130: 스트림 사용 최적화

Map의 값들을 합산할 때 불필요하게 복잡한 스트림 연산을 사용하고 있습니다.

-double totalCategory = userProfileCategoryPreferences.values().stream()
-        .mapToDouble(Double::doubleValue)
-        .sum();
+double totalCategory = userProfileCategoryPreferences.values().stream()
+        .reduce(0.0, Double::sum);

-double totalPartner = userProfilePartnerPreferences.values().stream()
-        .mapToDouble(Double::doubleValue)
-        .sum();
+double totalPartner = userProfilePartnerPreferences.values().stream()
+        .reduce(0.0, Double::sum);
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/RecommendServiceDouble.java (2)

32-32: Redis 키 네이밍 개선 필요

rec:user: 키가 너무 일반적입니다. 벡터 타입을 구분할 수 있도록 더 구체적인 키를 사용하세요.

-    final static String REDIS_KEY = "rec:user:";
+    final static String REDIS_KEY = "rec:double:user:";

90-92: 운영 환경 로깅 고려사항

추천 결과를 상세히 로깅하고 있는데, 운영 환경에서는 성능과 로그 용량을 고려하여 debug 레벨로 변경하는 것이 좋습니다.

-    log.info("추천 게시글: {}, 유사도: {}, 최종 점수: {}", r.getPost().getId(), r.getSimilarity(), r.getFinalScore());
+    log.debug("추천 게시글: {}, 유사도: {}, 최종 점수: {}", r.getPost().getId(), r.getSimilarity(), r.getFinalScore());
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/VectorUtilsDouble.java (1)

103-110: 파라미터 타입 일관성 개선 필요

VectorUtilsFloat는 float 파라미터를 사용하는데, 여기서는 int를 사용합니다. double 기반 구현이므로 double 파라미터를 사용하는 것이 일관성 있습니다.

-    public double normalizePrice(int price) {
+    public double normalizePrice(double price) {
         return Math.min(price / MAX_PRICE, 1.0);
     }
 
-    public double normalizeDaysToExpiry(int days) {
+    public double normalizeDaysToExpiry(double days) {
         return Math.min(days / MAX_DAYS_TO_EXPIRY, 1.0);
     }
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/UserProfileVectorizerFloat.java (1)

71-76: 성능 최적화 가능

이 메서드는 여러 번 호출될 수 있으며, 배치 페칭이나 캐싱을 통해 성능을 개선할 수 있습니다.

N+1 쿼리 문제를 방지하기 위해 @EntityGraph나 fetch join 사용을 고려하세요. 또한 결과를 캐싱하면 반복 호출 시 성능이 향상됩니다.

src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/RecommendServicePg.java (1)

83-87: 불완전한 주석 및 로깅 개선

  1. 불완전한 주석 "//pg" 제거 또는 완성
  2. System.out.println 대신 로거 사용
-            //pg
-
-            //종료 시간
             Long endTime = System.currentTimeMillis();
-            System.out.println("추천 게시글 계산 시간: " + (endTime - startTime) + "ms");
+            log.info("pgVector 추천 게시글 계산 시간: {}ms", (endTime - startTime));
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/VectorUtilsFloat.java (1)

32-48: 코사인 유사도 계산 최적화 가능

카테고리 유사도 계산이 올바르게 구현되어 있지만, 성능 최적화가 가능합니다.

Math.pow(user[i], 2) 대신 직접 곱셈을 사용하면 성능이 향상됩니다:

for (int i = 0; i < CATEGORY_COUNT; i++) {
    dotProduct += user[i] * post[i];
-   normA += Math.pow(user[i], 2);
-   normB += Math.pow(post[i], 2);
+   normA += user[i] * user[i];
+   normB += post[i] * post[i];
}
src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostService.java (3)

125-129: 주석 정리 필요

주석으로 처리된 코드들(//floatVector, //byteVector)을 정리해주세요.

                doubleVector,
                floatVector
-               //floatVector
-               //byteVector

135-136: TODO 주석과 JDBC 삽입

//TODO: 여기 주석을 더 구체적으로 작성하거나 제거해주세요. float 벡터의 JDBC 삽입 로직은 올바르게 구현되었습니다.

-       //TODO: 여기
+       // float 벡터를 별도 테이블에 저장
        jdbcRepository.insertPostVector(savedGifticon.getId(), floatVector);

323-324: 하드코딩된 사용자 ID

사용자 ID가 2L로 하드코딩되어 있습니다. Mock 데이터 생성용이지만 더 안전한 방법을 사용해주세요.

-       User user = userRepository.findById(2L)
-               .orElseThrow(() -> new GeneralException(UserException.USER_NOT_FOUND));
+       User user = userRepository.findByUsername("admin")
+               .orElseGet(() -> createMockUser());
src/main/java/com/TwoSeaU/BaData/domain/trade/controller/RecommendController.java (1)

37-37: 주석 처리된 URL 정리

//@GetMapping("/pgvector") 주석을 제거하거나 필요하다면 활성화해주세요.

-   //@GetMapping("/pgvector")
    @GetMapping
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcb1918 and ca3b38b.

📒 Files selected for processing (20)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/PostController.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/RecommendController.java (2 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/entity/Gifticon.java (2 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/entity/PostVector.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostSearchService.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostService.java (7 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/GlobalRecommendService.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/PostVectorizerDouble.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/RecommendServiceDouble.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/UserProfileVectorizerDouble.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/VectorUtilsDouble.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/PostVectorizerFloat.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/RecommendServiceFloat.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/UserProfileVectorizerFloat.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/VectorUtilsFloat.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/RecommendServicePg.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/UserProfileVectorizerPg.java (1 hunks)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/VectorUtilsPg.java (1 hunks)
  • src/test/java/com/TwoSeaU/BaData/domain/trade/service/PostServiceTest.java (2 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: dionisos198
PR: Ureca-Final-Project-Team2/be_badata#388
File: src/main/java/com/TwoSeaU/BaData/domain/rental/service/RestockDeleteTargetingService.java:37-60
Timestamp: 2025-08-05T05:48:10.447Z
Learning: dionisos198는 be_badata 프로젝트에서 PR의 주요 목적에 집중하기 위해 성능 최적화 이슈를 추후 수정으로 미루는 접근 방식을 선호한다. N+1 쿼리 최적화와 같은 성능 개선 사항은 별도 작업으로 분리하여 처리한다.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#269
File: src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java:24-27
Timestamp: 2025-07-28T01:53:53.618Z
Learning: marineAqu prefers to keep method names as-is in temporary Mock APIs that are planned for deletion, prioritizing development efficiency over perfect naming conventions for code that will be removed later.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#365
File: src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostSearchService.java:44-44
Timestamp: 2025-08-03T14:04:42.957Z
Learning: marineAqu는 개별 API의 완벽한 구현보다는 모든 API 간의 일관된 구현 패턴을 유지하는 것을 우선시한다. 보안이나 성능 개선 사항이 있더라도 전체 시스템에 대한 논의 후 일괄 적용하는 것을 선호한다.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#358
File: src/main/java/com/TwoSeaU/BaData/domain/trade/service/RecommendService.java:155-169
Timestamp: 2025-08-03T04:35:46.490Z
Learning: marineAqu는 곧 삭제될 예정인 API의 경우, 최적화보다는 개발 효율성을 우선시하여 현재 구현을 임시로 유지하는 것을 선호한다. 삭제될 코드에 대해서는 완벽한 구현보다는 개발 속도를 중시한다.
📚 Learning: in the updatepostrequest class at src/main/java/com/twoseau/badata/domain/trade/dto/request/updatepo...
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#146
File: src/main/java/com/TwoSeaU/BaData/domain/trade/dto/request/UpdatePostRequest.java:5-5
Timestamp: 2025-07-18T05:48:30.088Z
Learning: In the UpdatePostRequest class at src/main/java/com/TwoSeaU/BaData/domain/trade/dto/request/UpdatePostRequest.java, the user explicitly stated they don't use the default constructor and are handling JSON deserialization differently, rejecting the suggestion to add NoArgsConstructor alongside AllArgsConstructor.

Applied to files:

  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java
  • src/test/java/com/TwoSeaU/BaData/domain/trade/service/PostServiceTest.java
  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/RecommendController.java
  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/PostController.java
  • src/main/java/com/TwoSeaU/BaData/domain/trade/entity/PostVector.java
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostService.java
📚 Learning: marineaqu prefers to keep method names as-is in temporary mock apis that are planned for deletion, p...
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#269
File: src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java:24-27
Timestamp: 2025-07-28T01:53:53.618Z
Learning: marineAqu prefers to keep method names as-is in temporary Mock APIs that are planned for deletion, prioritizing development efficiency over perfect naming conventions for code that will be removed later.

Applied to files:

  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java
📚 Learning: choyunju prefers to defer creating dedicated response dtos for temporary controllers like ocrcontrol...
Learnt from: choyunju
PR: Ureca-Final-Project-Team2/be_badata#195
File: src/main/java/com/TwoSeaU/BaData/domain/trade/controller/OCRController.java:27-28
Timestamp: 2025-07-23T00:25:08.415Z
Learning: choyunju prefers to defer creating dedicated response DTOs for temporary controllers like OCRController until the integration phase with PostService, rather than creating DTOs that might need modification during the integration process.

Applied to files:

  • src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java
📚 Learning: trendingpostservice에서 redis ttl 관리는 의도적으로 갱신하지 않도록 구현되었다. 이는 인기 게시글이 지속적인 활동에도 불구하고 일정 시간(3시간) 후 자동 ...
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#341
File: src/main/java/com/TwoSeaU/BaData/domain/trade/service/TrendingPostService.java:77-86
Timestamp: 2025-08-01T04:20:26.108Z
Learning: TrendingPostService에서 Redis TTL 관리는 의도적으로 갱신하지 않도록 구현되었다. 이는 인기 게시글이 지속적인 활동에도 불구하고 일정 시간(3시간) 후 자동 만료되어 트렌딩 목록이 정기적으로 갱신되도록 하는 설계 의도이다.

Applied to files:

  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/RecommendServiceFloat.java
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/RecommendServiceDouble.java
📚 Learning: in java, when classes are in the same package, no import statement is needed. classes in the same pa...
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#118
File: src/main/java/com/TwoSeaU/BaData/domain/trade/entity/Payment.java:32-32
Timestamp: 2025-07-15T06:31:54.894Z
Learning: In Java, when classes are in the same package, no import statement is needed. Classes in the same package can reference each other directly without explicit imports. This applies to the Post entity in the Payment.java file where both are in the com.TwoSeaU.BaData.domain.trade.entity package.

Applied to files:

  • src/test/java/com/TwoSeaU/BaData/domain/trade/service/PostServiceTest.java
  • src/main/java/com/TwoSeaU/BaData/domain/trade/entity/PostVector.java
  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostService.java
📚 Learning: review-reservation 간 1:1 관계에서 외래키 제약조건은 데이터 무결성을 보장하지만, jpa 지연 로딩으로 인한 lazyinitializationexception 방...
Learnt from: dionisos198
PR: Ureca-Final-Project-Team2/be_badata#144
File: src/main/java/com/TwoSeaU/BaData/domain/rental/dto/response/ShowReviewResponse.java:34-34
Timestamp: 2025-07-18T05:16:04.655Z
Learning: Review-Reservation 간 1:1 관계에서 외래키 제약조건은 데이터 무결성을 보장하지만, JPA 지연 로딩으로 인한 LazyInitializationException 방지를 위해서는 연관된 User 엔티티도 함께 fetch해야 한다. NPE 체크보다는 쿼리 개선이 더 나은 해결책이다.

Applied to files:

  • src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostService.java
🧬 Code Graph Analysis (3)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/PostVectorizerDouble.java (3)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/GlobalRecommendService.java (1)
  • Service (13-59)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/UserProfileVectorizerDouble.java (1)
  • Service (21-176)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/PostVectorizerFloat.java (1)
  • Service (19-97)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/PostVectorizerFloat.java (3)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/GlobalRecommendService.java (1)
  • Service (13-59)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/PostVectorizerDouble.java (1)
  • Service (20-92)
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/UserProfileVectorizerFloat.java (1)
  • Service (21-187)
src/main/java/com/TwoSeaU/BaData/domain/trade/entity/PostVector.java (1)
src/main/java/com/TwoSeaU/BaData/domain/trade/entity/Gifticon.java (1)
  • Entity (11-59)
🔇 Additional comments (21)
src/main/java/com/TwoSeaU/BaData/domain/trade/controller/PostController.java (1)

40-47: 임시 검색 API가 올바르게 구현되었습니다.

기존 /posts 엔드포인트와 동일한 구조로 RDB 기반 검색을 제공하는 대안 API가 적절하게 추가되었습니다. 성능 비교 목적의 임시적 구현으로 보이며, 파라미터 처리와 응답 구조가 일관되게 유지되고 있습니다.

src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostSearchService.java (1)

59-67: searchPostsByRDB 메서드 구현 검증 완료

postRepository.searchPostsByKeyword 메서드는

  • src/main/java/com/TwoSeaU/BaData/domain/trade/repository/PostQueryRepository.java 인터페이스에 선언되어 있고
  • src/main/java/com/TwoSeaU/BaData/domain/trade/repository/PostQueryRepositoryImpl.java에서 정상적으로 구현되어 있습니다.

따라서 해당 서비스 메서드는 의도대로 동작하며 추가 조치가 필요 없습니다.

src/test/java/com/TwoSeaU/BaData/domain/trade/service/PostServiceTest.java (1)

16-16: 테스트가 새로운 벡터화 구조를 올바르게 반영합니다.

PostVectorizer에서 PostVectorizerDouble로의 변경이 서비스 레이어의 이중 벡터화 접근 방식과 일치합니다. 테스트 로직은 변경되지 않아 기존 기능성을 유지합니다.

Also applies to: 72-72

src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java (1)

1-21: Mock API 구현이 개발 목적에 적합합니다.

임시 개발/테스트용 Mock 컨트롤러로서 단순하고 기능적인 구현입니다. 대량 기프티콘 생성을 위한 엔드포인트가 표준화된 응답 형식을 사용하여 일관성을 유지합니다.

src/main/java/com/TwoSeaU/BaData/domain/trade/entity/PostVector.java (1)

9-25: 엔티티 설계가 적절합니다.

@MapsId를 사용한 공유 기본키 설계와 지연 로딩 설정이 올바릅니다. PostgreSQL vector 타입에 대한 커스텀 컬럼 정의도 적절합니다.

src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/VectorUtilsPg.java (1)

10-11: 하드코딩된 유사도 정규화 상수 검증 및 중앙화 제안

  • 세 가지 벡터 유틸리티 클래스에서 동일한 상수(MAX_PRICE=30000, MAX_DAYS_TO_EXPIRY=365)를 사용 중입니다.
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/VectorUtilsPg.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/VectorUtilsFloat.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/VectorUtilsDouble.java
    → 유지보수를 위해 별도 상수 정의 클래스(Constant.java 등)로 중앙화하세요.

  • 하드코딩된 값이 실제 데이터베이스의 최대 가격·만료일 범위를 정확히 반영하는지 반드시 확인해야 합니다.
    예시 쿼리:

    SELECT MAX(price)           AS 실제_최대_가격   FROM trade_post;
    SELECT MAX(days_to_expiry)  AS 실제_최대_만료일 FROM trade_post;

    실제 범위와 다를 경우 상수값을 조정하고, 상수 정의부에 근거를 주석으로 남기세요.

src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/RecommendServiceDouble.java (1)

115-118: Redis TTL 갱신 정책 확인

매번 게시물을 추가할 때마다 TTL을 재설정하고 있습니다. TrendingPostService의 의도적인 TTL 비갱신 정책과 일관성을 고려하여 정책을 결정하세요.

추천 시스템의 TTL 갱신 정책이 의도된 것인지 확인이 필요합니다. 지속적인 추천 조회 시 캐시가 계속 갱신되어 사용자 선호도 변화가 반영되지 않을 수 있습니다.

src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/VectorUtilsDouble.java (1)

11-14: 정적 필드 초기화 순서 문제 가능성

PostVectorizerDouble의 정적 맵이 초기화되기 전에 이 클래스가 로드되면 NullPointerException이나 잘못된 크기 값이 발생할 수 있습니다. 의존성 주입이나 지연 초기화를 고려해보세요.

-    private static final int CATEGORY_COUNT = PostVectorizerDouble.categoryToIndex.size();
-    private static final int PARTNER_COUNT = PostVectorizerDouble.partnerToIndex.size();
+    private final int CATEGORY_COUNT;
+    private final int PARTNER_COUNT;
+    
+    public VectorUtilsDouble(PostVectorizerDouble postVectorizerDouble) {
+        this.CATEGORY_COUNT = postVectorizerDouble.getCategoryCount();
+        this.PARTNER_COUNT = postVectorizerDouble.getPartnerCount();
+        // ... 다른 필드들도 동일하게 처리
+    }

Likely an incorrect or invalid review comment.

src/main/java/com/TwoSeaU/BaData/domain/trade/entity/Gifticon.java (1)

26-28: JPA 배열 매핑 검증 필요

JPA는 기본적으로 double[]float[]를 엔티티 필드로 바로 처리하지 않습니다. 현재 코드베이스에서 해당 필드에 대한 변환기(converter)나 어노테이션 설정이 보이지 않으므로, 매핑이 올바르게 이루어지는지 수동으로 확인해주세요.

점검할 항목:

  • src/main/java/com/TwoSeaU/BaData/domain/trade/entity/Gifticon.java
    • private double[] doubleVector;
    • private float[] floatVector;
  • 배열 타입 변환기 구현 여부
    • AttributeConverter<double[], ?>, AttributeConverter<float[], ?> 구현 클래스
    • @Convert, @Type 어노테이션 적용 위치
  • 대안 매핑 방식 검토
    • @ElementCollection + Embeddable
    • 별도 테이블 매핑 등

위 설정이 누락된 경우, 컨버터 작성 또는 컬렉션 매핑으로 전환이 필요합니다.

src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/VectorUtilsFloat.java (4)

25-30: LGTM: 스칼라 유사도 계산

가격이나 만료일 같은 스칼라 값의 유사도를 계산하는 로직이 올바르게 구현되어 있습니다. 차이를 정규화하여 0-1 범위의 유사도를 반환하는 방식이 적절합니다.


90-100: LGTM: 효율적인 배열 연결

concatenate 메서드가 System.arraycopy를 사용하여 효율적으로 구현되어 있습니다. 여러 벡터를 하나로 결합하는 로직이 올바릅니다.


68-88: 가중치 합계 및 정규화 의도 검증

현재 VectorUtilsFloat, PostVectorizerFloat, UserProfileVectorizerFloat, VectorUtilsDouble, UserProfileVectorizerDouble 등 주요 벡터 계산 클래스에서 동일한 가중치
(CATEGORY_WEIGHT=1.3, PARTNER_WEIGHT=0.3, PRICE_WEIGHT=0.9, DAYS_TO_EXPIRY_WEIGHT=0.5)를 사용하고 있으며, 이들의 합이 3.0으로 설정되어 있습니다.
일반적으로 가중치 합을 1.0으로 맞추거나(사전 정규화) 최종 결과를 0~1 범위로 스케일링하는 것이 권장됩니다.

  • 확인 대상 파일
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/VectorUtilsFloat.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/PostVectorizerFloat.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/UserProfileVectorizerFloat.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/VectorUtilsDouble.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/UserProfileVectorizerDouble.java

  • 확인 요청 사항

    1. 가중치(1.3, 0.3, 0.9, 0.5) 설계 근거(도메인 요구사항·실험 결과 등)가 있는지 공유 부탁드립니다.
    2. 필요 시 가중치 합을 1.0으로 사전 정규화하거나, 계산 후 결과를 정규화하는 방안을 적용할지 결정해 주세요.

102-110: 정규화 상수 일치 여부 확인 필요

MAX_PRICE(30000)MAX_DAYS_TO_EXPIRY(365)가 실제 데이터베이스 컬럼 정의 또는 운영 데이터의 최대값과 일치하는지 확인해주세요.

  • 점검 대상 파일
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/floatVector/VectorUtilsFloat.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/VectorUtilsPg.java
    • src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/doubleVector/VectorUtilsDouble.java

데이터베이스 스키마(DDL)나 운영 통계 조회를 통해 상수값의 적절성을 검증하고, 다르다면 상수 또는 비즈니스 로직을 조정해주세요.

src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostService.java (6)

14-16: 새로운 벡터 서비스 의존성 추가 확인

float 벡터와 pgVector 지원을 위한 새로운 의존성들이 적절히 추가되었습니다. 추천 시스템의 다중 벡터 전략을 지원하는 구조입니다.


28-28: 로깅을 위한 Slf4j 추가 확인

새로 추가된 @Slf4j 어노테이션이 generateGifticons 메서드의 로깅에 활용됩니다.


32-36: 새로운 임포트들이 적절히 추가됨

벌크 데이터 생성을 위한 BigDecimal, LocalDate, ArrayList, List, Stream 임포트가 generateGifticons 메서드에서 사용됩니다.


52-58: 이중 벡터화 접근 방식 도입

double과 float 벡터를 모두 생성하는 이중 벡터화 전략이 도입되었습니다. 성능 비교 테스트를 위한 합리적인 접근입니다.


100-112: 벡터 생성 로직이 올바르게 구현됨

동일한 입력으로 double과 float 벡터를 모두 생성하는 로직이 적절합니다. 두 벡터화 서비스가 일관된 매개변수를 받고 있습니다.


292-313: 수정 시에도 이중 벡터 업데이트

게시글 수정 시에도 double과 float 벡터를 모두 업데이트하는 로직이 올바르게 구현되었습니다. 일관성 있는 접근 방식입니다.

src/main/java/com/TwoSeaU/BaData/domain/trade/controller/RecommendController.java (2)

6-9: 다중 추천 서비스 아키텍처 도입

double, float, pgVector 기반의 세 가지 추천 서비스가 도입되어 성능 비교 테스트가 가능한 구조입니다. 추천 시스템 고도화 목표에 부합합니다.


27-41: 일관성 있는 API 엔드포인트 설계

세 가지 벡터 타입별로 별도의 엔드포인트를 제공하는 것은 성능 비교에 적합한 설계입니다. 기본 엔드포인트가 pgVector를 사용하는 것도 합리적입니다.

Comment on lines +21 to +24
private final RecommendServiceDouble doubleRecommendServiceDouble;
private final RecommendServiceFloat floatRecommendServiceFloat;
private final RecommendServicePg pgvectorRecommendServicePg;
private final GlobalRecommendService globalRecommendService;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

필드명과 타입 불일치

필드명에 타입 정보가 중복되어 있어 가독성이 떨어집니다.

-private final RecommendServiceDouble doubleRecommendServiceDouble;
-private final RecommendServiceFloat floatRecommendServiceFloat;
-private final RecommendServicePg pgvectorRecommendServicePg;
+private final RecommendServiceDouble recommendServiceDouble;
+private final RecommendServiceFloat recommendServiceFloat;
+private final RecommendServicePg recommendServicePg;

이에 따라 메서드 호출부도 수정해야 합니다.

📝 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
private final RecommendServiceDouble doubleRecommendServiceDouble;
private final RecommendServiceFloat floatRecommendServiceFloat;
private final RecommendServicePg pgvectorRecommendServicePg;
private final GlobalRecommendService globalRecommendService;
private final RecommendServiceDouble recommendServiceDouble;
private final RecommendServiceFloat recommendServiceFloat;
private final RecommendServicePg recommendServicePg;
private final GlobalRecommendService globalRecommendService;
🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/controller/RecommendController.java
around lines 21 to 24, the field names redundantly include type information,
reducing readability. Rename the fields to remove type details so that names
clearly represent their purpose without duplicating type info. After renaming,
update all method calls and references to these fields accordingly to match the
new names.

Comment on lines +48 to +56
@PatchMapping("/vector/update/all/double")
public ResponseEntity<ApiResponse<String>> updateAllPostForDouble() {
return ResponseEntity.ok().body(ApiResponse.success(globalRecommendService.updateAllDoubleVector()));
}

@PatchMapping("/vector/update/all/float")
public ResponseEntity<ApiResponse<String>> updateAllPostVectorForPgvector() {
return ResponseEntity.ok().body(ApiResponse.success(globalRecommendService.updateAllFloatAndPgVector()));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

벌크 벡터 업데이트 엔드포인트 보안 우려

벌크 벡터 업데이트 엔드포인트들이 인증 없이 노출되어 있어 보안상 위험할 수 있습니다.

관리자 권한 검증을 추가해주세요:

@PatchMapping("/vector/update/all/double")
-public ResponseEntity<ApiResponse<String>> updateAllPostForDouble() {
+public ResponseEntity<ApiResponse<String>> updateAllPostForDouble(@AuthenticationPrincipal User user) {
+    // 관리자 권한 검증 로직 추가
    return ResponseEntity.ok().body(ApiResponse.success(globalRecommendService.updateAllDoubleVector()));
}

또는 별도의 관리자 컨트롤러로 분리하는 것을 고려해주세요.

🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/controller/RecommendController.java
around lines 48 to 56, the bulk vector update endpoints lack authentication and
authorization checks, posing a security risk. Add an authorization check to
ensure only users with admin privileges can access these endpoints, such as
using a security annotation like @PreAuthorize("hasRole('ADMIN')") or
equivalent. Alternatively, move these endpoints to a separate controller
dedicated to admin operations and secure that controller accordingly.

}

@PatchMapping("/vector/update/all/float")
public ResponseEntity<ApiResponse<String>> updateAllPostVectorForPgvector() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

메서드명과 기능 불일치

updateAllPostVectorForPgvector 메서드명이 실제로는 float와 pgVector를 모두 업데이트하는 기능과 일치하지 않습니다.

-public ResponseEntity<ApiResponse<String>> updateAllPostVectorForPgvector() {
+public ResponseEntity<ApiResponse<String>> updateAllPostVectorForFloatAndPg() {
📝 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
public ResponseEntity<ApiResponse<String>> updateAllPostVectorForPgvector() {
public ResponseEntity<ApiResponse<String>> updateAllPostVectorForFloatAndPg() {
🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/controller/RecommendController.java
at line 54, the method name updateAllPostVectorForPgvector does not accurately
reflect its functionality, which updates both float and pgVector values. Rename
the method to a name that clearly indicates it updates all post vectors
including both float and pgVector types to improve code clarity and
maintainability.

Comment on lines +48 to 54
public void updateDoubleVector(final double[] doubleVector) {
this.doubleVector = doubleVector;
}

public void updateVector(final double[] vector) {
this.vector = vector;
public void updateFloatVector(final float[] floatVector) {
this.floatVector = floatVector;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

벡터 차원 검증 필요

벡터 업데이트 시 예상 차원과 일치하는지 검증이 없습니다. 잘못된 차원의 벡터가 저장되면 추천 시스템에서 오류가 발생할 수 있습니다.

     public void updateDoubleVector(final double[] doubleVector) {
+        // 예상 벡터 차원 검증 추가
+        if (doubleVector != null && doubleVector.length != EXPECTED_VECTOR_SIZE) {
+            throw new IllegalArgumentException("Invalid vector dimension: " + doubleVector.length);
+        }
         this.doubleVector = doubleVector;
     }
 
     public void updateFloatVector(final float[] floatVector) {
+        if (floatVector != null && floatVector.length != EXPECTED_VECTOR_SIZE) {
+            throw new IllegalArgumentException("Invalid vector dimension: " + floatVector.length);
+        }
         this.floatVector = floatVector;
     }

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

🤖 Prompt for AI Agents
In src/main/java/com/TwoSeaU/BaData/domain/trade/entity/Gifticon.java between
lines 48 and 54, the updateDoubleVector and updateFloatVector methods lack
validation for the vector dimensions. Add checks to verify that the input vector
length matches the expected dimension before assigning it to the class fields.
If the dimension does not match, throw an appropriate exception or handle the
error to prevent storing invalid vectors.

Comment on lines +18 to +19
@Column(name = "vector_pg", columnDefinition = "vector(57)")
private float[] vectorPg;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

벡터 차원 수를 검증하세요.

vector(57) 차원이 하드코딩되어 있습니다. 실제 벡터화된 특성의 차원 수와 일치하는지 확인이 필요합니다.

벡터 차원을 확인하기 위해 다음 스크립트를 실행하세요:


🏁 Script executed:

#!/bin/bash
# 벡터화 서비스에서 실제 생성되는 벡터 차원 확인
ast-grep --pattern 'vectorizePost($$$) {
  $$$
  return vectorUtils.concatenate($$$);
}'

# 카테고리와 파트너 인덱스 맵의 크기 확인
rg -A 3 "categoryToIndex.*put|partnerToIndex.*put"

Length of output: 2853


🏁 Script executed:

#!/bin/bash
# 전체 PostVectorizer 클래스 내용 및 벡터화 메서드 확인
sed -n '1,200p' src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostVectorizer.java

# vectorizePost 메서드 시그니처와 내부 concatenate 호출 위치 확인
rg "vectorizePost" -A5 src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostVectorizer.java
rg "concatenate" -A5 src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostVectorizer.java

# 카테고리 및 제휴사 리스트 초기화 위치 확인
rg "List<.*categories" -A3 src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostVectorizer.java
rg "List<.*partners" -A3 src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostVectorizer.java

Length of output: 4581


벡터 차원 크기 검증 및 상수화 필요

PostVector 엔티티의 @Column(columnDefinition = "vector(57)")에 하드코딩된 57이 실제 벡터화 로직과 일치하는지 반드시 확인하세요.
현재 vectorizePost(...) 메서드는
• 카테고리 수 (categoryToIndex.size())
• 제휴사 수 (partnerToIndex.size())
• 수치형 특성 2개
를 합쳐 벡터를 생성하므로, 총 차원 수는 (카테고리 개수 + 제휴사 개수 + 2)가 됩니다.
이 값이 57과 동일한지 점검하고, 유지보수를 위해 별도 상수 또는 설정값으로 분리하는 것을 권장합니다.

선택 사항:

  • PostVector 생성 전용 factory 메서드를 추가해 엔티티 생성 방식을 일관성 있게 관리할 수 있습니다.

– src/main/java/com/TwoSeaU/BaData/domain/trade/entity/PostVector.java, 18–19번 라인 확인

🤖 Prompt for AI Agents
In src/main/java/com/TwoSeaU/BaData/domain/trade/entity/PostVector.java at lines
18-19, the vector dimension size is hardcoded as 57 in the @Column annotation.
Verify that this matches the actual vector size calculated as
(categoryToIndex.size() + partnerToIndex.size() + 2). To fix, replace the
hardcoded 57 with a constant or configuration value representing the vector
dimension to ensure consistency and maintainability. Optionally, add a factory
method for creating PostVector instances to centralize and standardize entity
creation.

Comment on lines +109 to +112
catch (Exception e) {
log.error("유저 벡터화 실패: {}", e);
throw new GeneralException(TradeException.RECOMMENDATION_FAILED);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 처리 개선 필요

모든 예외를 동일하게 처리하면 디버깅이 어렵습니다. 구체적인 예외 타입별로 처리하세요.

-        catch (Exception e) {
-            log.error("유저 벡터화 실패: {}", e);
+        catch (GeneralException e) {
+            log.error("유저 벡터화 실패 - User: {}, Error: {}", user.getId(), e.getMessage(), e);
+            throw e;
+        } catch (Exception e) {
+            log.error("예상치 못한 오류 발생 - User: {}", user.getId(), e);
             throw new GeneralException(TradeException.RECOMMENDATION_FAILED);
         }
📝 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
catch (Exception e) {
log.error("유저 벡터화 실패: {}", e);
throw new GeneralException(TradeException.RECOMMENDATION_FAILED);
}
catch (GeneralException e) {
log.error("유저 벡터화 실패 - User: {}, Error: {}", user.getId(), e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("예상치 못한 오류 발생 - User: {}", user.getId(), e);
throw new GeneralException(TradeException.RECOMMENDATION_FAILED);
}
🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/RecommendServicePg.java
around lines 109 to 112, the catch block currently handles all exceptions
generically, which hinders debugging. Refactor the code to catch and handle
specific exception types separately, providing tailored logging and error
handling for each, instead of a single generic Exception catch.

Comment on lines +23 to +156
public class UserProfileVectorizerPg {
private static final float CATEGORY_WEIGHT = 1.3F;
private static final float PARTNER_WEIGHT = 0.3F;
private static final float PRICE_WEIGHT = 0.9F;
private static final float DAYS_TO_EXPIRY_WEIGHT = 0.5F;

private static final float LIKES_POST_WEIGHT = 0.6F; // 찜한 게시글의 가중치
private static final float PURCHASE_POST_WEIGHT = 1.5F; // 구매한 게시글의 가중치
private static final float RECOMMENDATION_LIKES_POST_WEIGHT = 0.9F; // 추천 게시글의 가중치

private final PostLikesRepository postLikesRepository;
private final PaymentRepository paymentRepository;
private final PostRepository postRepository;
private final UserProfileVectorizerFloat userProfileVectorizerFloat;
private final VectorUtilsFloat vectorUtilsFloat;
private final VectorUtilsPg vectorUtilsPg;

public float[] vectorizeUserProfile(final User user, final Set<Long> recommendedPostIds) {
final UserProfileVectorizerFloat.UserProfile userProfile = createUserProfile(user, recommendedPostIds);

// 1. 카테고리 선호도 벡터
final float[] categoryPreferences = userProfileVectorizerFloat.createPreferenceVector(
userProfile.getCategoryPreferences(),
PostVectorizerFloat.categoryToIndex
);

// 2. 제휴사 선호도 벡터
final float[] partnerPreferences = userProfileVectorizerFloat.createPreferenceVector(
userProfile.getPartnerPreferences(),
PostVectorizerFloat.partnerToIndex
);

for(int i = 0; i < categoryPreferences.length; i++) {
categoryPreferences[i] *= CATEGORY_WEIGHT;
}

for(int i = 0; i < partnerPreferences.length; i++) {
partnerPreferences[i] *= PARTNER_WEIGHT;
}

// 3. 수치형 선호도 (가격, 마감 임박일수 등)
final float[] numericalPreferences = {
vectorUtilsPg.normalizePrice(userProfile.getPreparePrice() * PRICE_WEIGHT),
vectorUtilsPg.normalizeDaysToExpiry(userProfile.getAcceptableDaysToExpiry() * DAYS_TO_EXPIRY_WEIGHT)
};

return vectorUtilsFloat.concatenate(categoryPreferences, partnerPreferences, numericalPreferences);
}

public UserProfileVectorizerFloat.UserProfile createUserProfile (final User user, final Set<Long> recommendedPostIds) {
final Map<String, Float> userProfileCategoryPreferences = new HashMap<>();
final Map<String, Float> userProfilePartnerPreferences = new HashMap<>();

float tempPreparePrice = 0;
float tempMaxAcceptableDaysToExpiry = 0;

//구매
List<Long> purchasedPostIds = paymentRepository.findBoughtPostIdByUserId(user.getId());
List<Gifticon> purchasePosts = userProfileVectorizerFloat.getPostsByIds(purchasedPostIds);

for (Gifticon post : purchasePosts) {
tempPreparePrice += post.getPrice().floatValue() * PURCHASE_POST_WEIGHT;
tempMaxAcceptableDaysToExpiry += ChronoUnit.DAYS.between(LocalDate.now(), post.getDeadLine()) * PURCHASE_POST_WEIGHT;

GifticonCategory category = post.getCategory();
userProfileCategoryPreferences.put(category.getCategoryName(),
userProfileCategoryPreferences.getOrDefault(category.getCategoryName(), 0.0F) + PURCHASE_POST_WEIGHT);

String partner = post.getPartner();
userProfilePartnerPreferences.put(partner,
userProfilePartnerPreferences.getOrDefault(partner, 0.0F) + PURCHASE_POST_WEIGHT);
}

//찜
List<Long> likedPostIds = postLikesRepository.findDistinctPostIdsByUserId(user.getId());
List<Gifticon> likePosts = userProfileVectorizerFloat.getPostsByIds(likedPostIds);

for (Gifticon post : likePosts) {
tempPreparePrice += post.getPrice().floatValue() * LIKES_POST_WEIGHT;
tempMaxAcceptableDaysToExpiry += ChronoUnit.DAYS.between(LocalDate.now(), post.getDeadLine()) * LIKES_POST_WEIGHT;

GifticonCategory category = post.getCategory();
userProfileCategoryPreferences.put(category.getCategoryName(),
userProfileCategoryPreferences.getOrDefault(category.getCategoryName(), 0.0F) + LIKES_POST_WEIGHT);

String partner = post.getPartner();
userProfilePartnerPreferences.put(partner,
userProfilePartnerPreferences.getOrDefault(partner, 0.0F) + LIKES_POST_WEIGHT);
}

//추천 시 좋아요
List<Gifticon> recommendLikePosts = userProfileVectorizerFloat.getPostsByIds(new ArrayList<>(recommendedPostIds));

for (Gifticon post : recommendLikePosts) {
tempPreparePrice += post.getPrice().floatValue() * RECOMMENDATION_LIKES_POST_WEIGHT;
tempMaxAcceptableDaysToExpiry += ChronoUnit.DAYS.between(LocalDate.now(), post.getDeadLine()) * RECOMMENDATION_LIKES_POST_WEIGHT;

GifticonCategory category = post.getCategory();
userProfileCategoryPreferences.put(category.getCategoryName(),
userProfileCategoryPreferences.getOrDefault(category.getCategoryName(), 0.0F) + RECOMMENDATION_LIKES_POST_WEIGHT);

String partner = post.getPartner();
userProfilePartnerPreferences.put(partner,
userProfilePartnerPreferences.getOrDefault(partner, 0.0F) + RECOMMENDATION_LIKES_POST_WEIGHT);
}

//정규화
float totalCategory = userProfileCategoryPreferences.values().stream()
.reduce(0.0F, Float::sum);

float totalPartner = userProfilePartnerPreferences.values().stream()
.reduce(0.0F, Float::sum);

if (totalCategory > 0) {
userProfileCategoryPreferences.replaceAll((category, value) -> value / totalCategory);
}

if (totalPartner > 0) {
userProfilePartnerPreferences.replaceAll((partner, value) -> value / totalPartner);
}

int totalPostCount = likePosts.size() + purchasePosts.size() + recommendLikePosts.size();

if(totalPostCount == 0) {
throw new GeneralException(TradeException.RECOMMENDATION_FAILED);
}

return new UserProfileVectorizerFloat.UserProfile(
userProfileCategoryPreferences,
userProfilePartnerPreferences,
vectorUtilsPg.normalizePrice(tempPreparePrice * PRICE_WEIGHT / totalPostCount),
vectorUtilsPg.normalizeDaysToExpiry(tempMaxAcceptableDaysToExpiry * DAYS_TO_EXPIRY_WEIGHT / totalPostCount)
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

중복 코드 리팩토링 필요

UserProfileVectorizerPg 클래스의 대부분이 UserProfileVectorizerFloat와 중복됩니다. 특히 createUserProfile 메서드(72-156줄)는 완전히 동일한 로직을 가지고 있습니다.

다음과 같은 리팩토링을 제안합니다:

  1. 공통 로직을 추상 클래스나 유틸리티 클래스로 추출
  2. 각 Vectorizer는 벡터 타입별 특화 로직만 구현
🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/UserProfileVectorizerPg.java
around lines 23 to 156, the createUserProfile method duplicates the exact logic
from UserProfileVectorizerFloat. Refactor by extracting the shared logic into a
common abstract base class or a utility class that both vectorizers can use.
Then, keep only the vector type-specific code in each vectorizer class, calling
the shared methods for common operations like building and normalizing user
profile preferences.

Comment on lines +36 to +37
private final UserProfileVectorizerFloat userProfileVectorizerFloat;
private final VectorUtilsFloat vectorUtilsFloat;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

불필요한 의존성 제거

userProfileVectorizerFloat를 주입받아 단순히 getPostsByIds 메서드만 사용하고 있습니다. 이는 불필요한 의존성을 만들고 있습니다.

getPostsByIds 메서드를 현재 클래스로 이동하거나 공통 유틸리티로 추출하세요.

-    private final UserProfileVectorizerFloat userProfileVectorizerFloat;
     private final VectorUtilsFloat vectorUtilsFloat;
     private final VectorUtilsPg vectorUtilsPg;
+    
+    private List<Gifticon> getPostsByIds(List<Long> postIds) {
+        return postRepository.findAllById(postIds).stream()
+                .filter(post -> post instanceof Gifticon)
+                .map(post -> (Gifticon) post)
+                .collect(Collectors.toList());
+    }

Also applies to: 41-41, 81-81

🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/UserProfileVectorizerPg.java
around lines 36-37, 41, and 81, the field userProfileVectorizerFloat is injected
but only used to call the getPostsByIds method, creating an unnecessary
dependency. To fix this, move the getPostsByIds method into the current class or
extract it into a common utility class, then update the calls accordingly and
remove the userProfileVectorizerFloat injection.

Comment on lines +65 to +67
vectorUtilsPg.normalizePrice(userProfile.getPreparePrice() * PRICE_WEIGHT),
vectorUtilsPg.normalizeDaysToExpiry(userProfile.getAcceptableDaysToExpiry() * DAYS_TO_EXPIRY_WEIGHT)
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

정규화 로직 불일치

vectorizeUserProfile 메서드(65-67줄)와 createUserProfile 메서드(153-154줄)에서 정규화 방식이 다릅니다:

  • 65-66줄: 가중치를 적용한 후 정규화
  • 153-154줄: 가중치를 적용한 값을 다시 가중치와 곱한 후 정규화

일관된 정규화 방식을 사용해야 합니다.

 return new UserProfileVectorizerFloat.UserProfile(
     userProfileCategoryPreferences,
     userProfilePartnerPreferences,
-    vectorUtilsPg.normalizePrice(tempPreparePrice * PRICE_WEIGHT / totalPostCount),
-    vectorUtilsPg.normalizeDaysToExpiry(tempMaxAcceptableDaysToExpiry * DAYS_TO_EXPIRY_WEIGHT / totalPostCount)
+    tempPreparePrice / totalPostCount,
+    tempMaxAcceptableDaysToExpiry / totalPostCount
 );

Also applies to: 153-154

🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/UserProfileVectorizerPg.java
around lines 65-67 and 153-154, the normalization logic is inconsistent between
vectorizeUserProfile and createUserProfile methods. To fix this, ensure both
methods apply the weighting and normalization in the same order—either multiply
by the weight then normalize, or normalize then multiply by the weight. Refactor
the code so that the normalization approach is consistent across both methods.

Comment on lines +13 to +23
public byte[] vectorFloatToByte(float[] vectorPg) {
if (vectorPg == null) {
return null;
}

ByteBuffer buffer = ByteBuffer.allocate(4 * vectorPg.length);
for (float f : vectorPg) {
buffer.putFloat(f);
}
return buffer.array();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

바이트 변환 메서드의 견고성을 개선하세요.

현재 구현에서 빈 배열에 대한 처리가 명확하지 않고, ByteBuffer 할당이 비효율적일 수 있습니다.

 public byte[] vectorFloatToByte(float[] vectorPg) {
     if (vectorPg == null) {
         return null;
     }
+    if (vectorPg.length == 0) {
+        return new byte[0];
+    }

-    ByteBuffer buffer = ByteBuffer.allocate(4 * vectorPg.length);
+    ByteBuffer buffer = ByteBuffer.allocate(Float.BYTES * vectorPg.length);
     for (float f : vectorPg) {
         buffer.putFloat(f);
     }
     return buffer.array();
 }
📝 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
public byte[] vectorFloatToByte(float[] vectorPg) {
if (vectorPg == null) {
return null;
}
ByteBuffer buffer = ByteBuffer.allocate(4 * vectorPg.length);
for (float f : vectorPg) {
buffer.putFloat(f);
}
return buffer.array();
}
public byte[] vectorFloatToByte(float[] vectorPg) {
if (vectorPg == null) {
return null;
}
if (vectorPg.length == 0) {
return new byte[0];
}
ByteBuffer buffer = ByteBuffer.allocate(Float.BYTES * vectorPg.length);
for (float f : vectorPg) {
buffer.putFloat(f);
}
return buffer.array();
}
🤖 Prompt for AI Agents
In
src/main/java/com/TwoSeaU/BaData/domain/trade/service/recommend/pgVector/VectorUtilsPg.java
between lines 13 and 23, improve the robustness of the vectorFloatToByte method
by explicitly handling empty input arrays to avoid unnecessary ByteBuffer
allocation. Add a check for an empty array and return an empty byte array
immediately. This prevents inefficient allocation and clarifies behavior for
empty inputs.

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

🧹 Nitpick comments (1)
src/main/java/com/TwoSeaU/BaData/domain/trade/repository/JdbcRepository.java (1)

22-22: 상수 선언 개선

상수는 final로 선언하고 Java 네이밍 컨벤션에 따라 대문자로 작성해야 합니다.

-    private final int LIMIT = 10;
+    private static final int LIMIT = 10;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca3b38b and 631ba86.

📒 Files selected for processing (1)
  • src/main/java/com/TwoSeaU/BaData/domain/trade/repository/JdbcRepository.java (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: dionisos198
PR: Ureca-Final-Project-Team2/be_badata#388
File: src/main/java/com/TwoSeaU/BaData/domain/rental/service/RestockDeleteTargetingService.java:37-60
Timestamp: 2025-08-05T05:48:10.447Z
Learning: dionisos198는 be_badata 프로젝트에서 PR의 주요 목적에 집중하기 위해 성능 최적화 이슈를 추후 수정으로 미루는 접근 방식을 선호한다. N+1 쿼리 최적화와 같은 성능 개선 사항은 별도 작업으로 분리하여 처리한다.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#269
File: src/main/java/com/TwoSeaU/BaData/domain/trade/controller/MockController.java:24-27
Timestamp: 2025-07-28T01:53:53.618Z
Learning: marineAqu prefers to keep method names as-is in temporary Mock APIs that are planned for deletion, prioritizing development efficiency over perfect naming conventions for code that will be removed later.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#365
File: src/main/java/com/TwoSeaU/BaData/domain/trade/service/PostSearchService.java:44-44
Timestamp: 2025-08-03T14:04:42.957Z
Learning: marineAqu는 개별 API의 완벽한 구현보다는 모든 API 간의 일관된 구현 패턴을 유지하는 것을 우선시한다. 보안이나 성능 개선 사항이 있더라도 전체 시스템에 대한 논의 후 일괄 적용하는 것을 선호한다.
Learnt from: marineAqu
PR: Ureca-Final-Project-Team2/be_badata#358
File: src/main/java/com/TwoSeaU/BaData/domain/trade/service/RecommendService.java:155-169
Timestamp: 2025-08-03T04:35:46.490Z
Learning: marineAqu는 곧 삭제될 예정인 API의 경우, 최적화보다는 개발 효율성을 우선시하여 현재 구현을 임시로 유지하는 것을 선호한다. 삭제될 코드에 대해서는 완벽한 구현보다는 개발 속도를 중시한다.
🔇 Additional comments (1)
src/main/java/com/TwoSeaU/BaData/domain/trade/repository/JdbcRepository.java (1)

81-91: LGTM!

벡터를 PostgreSQL 형식 문자열로 변환하는 로직이 올바르고 StringBuilder를 사용하여 효율적입니다.


import com.TwoSeaU.BaData.domain.trade.entity.Gifticon;
import com.TwoSeaU.BaData.domain.trade.exception.TradeException;
import com.TwoSeaU.BaData.domain.trade.service.recommend.floatVector.RecommendServiceFloat;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

아키텍처 계층 분리 위반

Repository 계층에서 Service 계층의 클래스(RecommendServiceFloat.RecommendationResult)를 import하는 것은 계층 간 의존성을 역전시킵니다. Repository는 도메인 엔티티나 DTO만 반환해야 합니다.

별도의 DTO 클래스를 생성하여 계층 간 의존성을 해결하는 것을 권장합니다:

-import com.TwoSeaU.BaData.domain.trade.service.recommend.floatVector.RecommendServiceFloat;
+// RecommendationResult를 별도 DTO로 분리하여 domain.dto 패키지에 위치

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

🤖 Prompt for AI Agents
In src/main/java/com/TwoSeaU/BaData/domain/trade/repository/JdbcRepository.java
at line 5, the import of RecommendServiceFloat from the service layer violates
architecture layering by creating a dependency from repository to service. To
fix this, remove the import and create a separate DTO class in the domain or a
shared package that represents the data needed, then have the repository return
that DTO instead of the service class. This decouples the repository from the
service layer and maintains proper layering.

Comment on lines +33 to +57
try (Connection conn = dataSource.getConnection()) {
return jdbcTemplate.query(
sql,
ps -> {
ps.setObject(1, toPgvectorString(userVector));
ps.setObject(2, toPgvectorString(userVector));
ps.setInt(3, LIMIT + alreadyViewCount);
},
(rs, rowNum) -> {
Long postId = rs.getLong("id");
System.out.println("Post ID: " + postId);
Gifticon gifticon = gifticonRepository.findById(postId)
.orElseThrow(() -> new GeneralException(TradeException.POST_NOT_FOUND));

return new RecommendServiceFloat.RecommendationResult(
gifticon,
rs.getDouble("similarity"),
rs.getDouble("similarity")
);
}
);

} catch (SQLException e) {
throw new RuntimeException("추천 게시글 검색 실패", e);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

리소스 관리 및 성능 개선 필요

여러 개선 사항이 필요합니다:

  1. 불필요한 Connection 생성: JdbcTemplate이 연결을 자체 관리하므로 수동 Connection 생성이 불필요합니다.
  2. 디버그 코드 제거: 프로덕션 코드에서 System.out.println을 제거해야 합니다.
  3. N+1 쿼리 문제: 각 결과마다 개별적으로 Gifticon을 조회합니다.

다음과 같이 개선하세요:

-        try (Connection conn = dataSource.getConnection()) {
-            return jdbcTemplate.query(
-                    sql,
-                    ps -> {
-                        ps.setObject(1, toPgvectorString(userVector));
-                        ps.setObject(2, toPgvectorString(userVector));
-                        ps.setInt(3, LIMIT + alreadyViewCount);
-                    },
-                    (rs, rowNum) -> {
-                        Long postId = rs.getLong("id");
-                        System.out.println("Post ID: " + postId);
-                        Gifticon gifticon = gifticonRepository.findById(postId)
-                                .orElseThrow(() -> new GeneralException(TradeException.POST_NOT_FOUND));
+        return jdbcTemplate.query(
+                sql,
+                ps -> {
+                    String vectorString = toPgvectorString(userVector);
+                    ps.setObject(1, vectorString);
+                    ps.setObject(2, vectorString);
+                    ps.setInt(3, LIMIT + alreadyViewCount);
+                },
+                (rs, rowNum) -> {
+                    Long postId = rs.getLong("id");
+                    Gifticon gifticon = gifticonRepository.findById(postId)
+                            .orElseThrow(() -> new GeneralException(TradeException.POST_NOT_FOUND));

-                        return new RecommendServiceFloat.RecommendationResult(
-                                gifticon,
-                                rs.getDouble("similarity"),
-                                rs.getDouble("similarity")
-                        );
-                    }
-            );
-
-        } catch (SQLException e) {
-            throw new RuntimeException("추천 게시글 검색 실패", e);
-        }
+                    return new RecommendServiceFloat.RecommendationResult(
+                            gifticon,
+                            rs.getDouble("similarity"),
+                            rs.getDouble("similarity")
+                    );
+                }
+        );
📝 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
try (Connection conn = dataSource.getConnection()) {
return jdbcTemplate.query(
sql,
ps -> {
ps.setObject(1, toPgvectorString(userVector));
ps.setObject(2, toPgvectorString(userVector));
ps.setInt(3, LIMIT + alreadyViewCount);
},
(rs, rowNum) -> {
Long postId = rs.getLong("id");
System.out.println("Post ID: " + postId);
Gifticon gifticon = gifticonRepository.findById(postId)
.orElseThrow(() -> new GeneralException(TradeException.POST_NOT_FOUND));
return new RecommendServiceFloat.RecommendationResult(
gifticon,
rs.getDouble("similarity"),
rs.getDouble("similarity")
);
}
);
} catch (SQLException e) {
throw new RuntimeException("추천 게시글 검색 실패", e);
}
return jdbcTemplate.query(
sql,
ps -> {
String vectorString = toPgvectorString(userVector);
ps.setObject(1, vectorString);
ps.setObject(2, vectorString);
ps.setInt(3, LIMIT + alreadyViewCount);
},
(rs, rowNum) -> {
Long postId = rs.getLong("id");
Gifticon gifticon = gifticonRepository.findById(postId)
.orElseThrow(() -> new GeneralException(TradeException.POST_NOT_FOUND));
return new RecommendServiceFloat.RecommendationResult(
gifticon,
rs.getDouble("similarity"),
rs.getDouble("similarity")
);
}
);
🤖 Prompt for AI Agents
In src/main/java/com/TwoSeaU/BaData/domain/trade/repository/JdbcRepository.java
lines 33 to 57, remove the manual Connection acquisition since JdbcTemplate
manages connections automatically. Eliminate the System.out.println debug
statement. To fix the N+1 query issue, refactor the code to fetch all Gifticon
entities in a single query based on the collected post IDs from the result set,
then map them accordingly when creating RecommendationResult objects.

Comment on lines +63 to +67
try (Connection conn = dataSource.getConnection()) {
jdbcTemplate.update(sql, postId, toPgvectorString(vector));
} catch (SQLException e) {
throw new RuntimeException("Post vector insert failed", e);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

불필요한 Connection 관리

JdbcTemplate이 연결을 내부적으로 관리하므로 수동 Connection 생성이 불필요합니다.

-        try (Connection conn = dataSource.getConnection()) {
-            jdbcTemplate.update(sql, postId, toPgvectorString(vector));
-        } catch (SQLException e) {
-            throw new RuntimeException("Post vector insert failed", e);
-        }
+        jdbcTemplate.update(sql, postId, toPgvectorString(vector));
📝 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
try (Connection conn = dataSource.getConnection()) {
jdbcTemplate.update(sql, postId, toPgvectorString(vector));
} catch (SQLException e) {
throw new RuntimeException("Post vector insert failed", e);
}
jdbcTemplate.update(sql, postId, toPgvectorString(vector));
🤖 Prompt for AI Agents
In src/main/java/com/TwoSeaU/BaData/domain/trade/repository/JdbcRepository.java
around lines 63 to 67, remove the manual Connection acquisition using
dataSource.getConnection() since JdbcTemplate manages connections internally.
Refactor the code to call jdbcTemplate.update(sql, postId,
toPgvectorString(vector)) directly without wrapping it in a try-with-resources
block for Connection.

Comment on lines +73 to +77
try (Connection conn = dataSource.getConnection()) {
jdbcTemplate.update(sql, toPgvectorString(vector), postId);
} catch (SQLException e) {
throw new RuntimeException("Post vector update failed", e);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Connection 관리 및 매개변수 순서 일관성

  1. JdbcTemplate이 연결을 관리하므로 수동 Connection 생성이 불필요합니다.
  2. insertPostVector와 매개변수 순서가 다릅니다 (일관성 부족).
-        try (Connection conn = dataSource.getConnection()) {
-            jdbcTemplate.update(sql, toPgvectorString(vector), postId);
-        } catch (SQLException e) {
-            throw new RuntimeException("Post vector update failed", e);
-        }
+        jdbcTemplate.update(sql, toPgvectorString(vector), postId);
📝 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
try (Connection conn = dataSource.getConnection()) {
jdbcTemplate.update(sql, toPgvectorString(vector), postId);
} catch (SQLException e) {
throw new RuntimeException("Post vector update failed", e);
}
jdbcTemplate.update(sql, toPgvectorString(vector), postId);
🤖 Prompt for AI Agents
In src/main/java/com/TwoSeaU/BaData/domain/trade/repository/JdbcRepository.java
around lines 73 to 77, remove the manual Connection creation since JdbcTemplate
manages connections automatically. Also, ensure the parameter order in
jdbcTemplate.update matches the order used in insertPostVector for consistency.
Adjust the method call accordingly to fix both issues.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Aug 7, 2025

Overall Project 16.74% -22.23%
Files changed 5.1%

File Coverage
Gifticon.java 67.57% -16.22%
PostService.java 34.75% -23.73%
PostVectorizerDouble.java 31.14% -68.86%
PostVectorizerFloat.java 29.89% -70.11%
VectorUtilsDouble.java 6.74% -93.26%
VectorUtilsFloat.java 6.32% -93.68%
VectorUtilsPg.java 5.77% -94.23%
PostSearchService.java 2.79% -7.32%
RecommendServiceDouble.java 1.45% -98.55%
RecommendServiceFloat.java 1.37% -98.63%
RecommendServicePg.java 1.37% -98.63%
JdbcRepository.java 0%
UserProfileVectorizerFloat.java 0%
UserProfileVectorizerPg.java 0%
MockController.java 0%
PostController.java 0% -11.35%
RecommendController.java 0% -80%
GlobalRecommendService.java 0%
UserProfileVectorizerDouble.java 0%

@marineAqu marineAqu merged commit bb19964 into develop Aug 7, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️performance 최적화/고도화

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BADA-368] [PERF] 추천 시스템 고도화

3 participants