Skip to content

fix: Article 1+4N 문제 해결#2268

Merged
Soundbar91 merged 5 commits into
developfrom
fix/2266-article-loading
May 27, 2026
Merged

fix: Article 1+4N 문제 해결#2268
Soundbar91 merged 5 commits into
developfrom
fix/2266-article-loading

Conversation

@Soundbar91
Copy link
Copy Markdown
Collaborator

@Soundbar91 Soundbar91 commented May 25, 2026

🔍 개요

Image
  • 키워드 알림 API 요청값으로 들어온 articleId들을 통해 Article를 조회하는 과정에서 다음과 같이 쿼리 발생
# 쿼리 전문 횟수
1 select a1_0.id, a1_0.board_id, a1_0.content, a1_0.created_at, a1_0.hit, a1_0.is_deleted, a1_0.is_notice, a1_0.title, a1_0.updated_at from new_articles a1_0 where ( a1_0.is_deleted = ? ) and a1_0.id in ( ? ) 1
2 select k1_0.id, k1_0.article_id, k1_0.created_at, k1_0.is_deleted, k1_0.updated_at, k1_0.user_id from new_koin_articles k1_0 where k1_0.article_id = ? and ( k1_0.is_deleted = ? ) 4
3 select k1_0.id, k1_0.article_id, k1_0.author, k1_0.created_at, k1_0.is_deleted, k1_0.portal_hit, k1_0.portal_num, k1_0.registered_at, k1_0.updated_at, k1_0.url from new_koreatech_articles k1_0 where k1_0.article_id = ? and ( k1_0.is_deleted = ? ) 4
4 select l1_0.id, l1_0.article_id, l1_0.author_id, l1_0.category, l1_0.created_at, l1_0.found_at, l1_0.found_date, l1_0.found_place, l1_0.is_council, l1_0.is_deleted, l1_0.is_found, l1_0.type, l1_0.updated_at from lost_item_articles l1_0 where l1_0.article_id = ? 4
5 select k1_0.id, k1_0.admin_id, k1_0.article_id, k1_0.created_at, k1_0.is_deleted, k1_0.updated_at from koin_notice k1_0 where k1_0.article_id = ? and ( k1_0.is_deleted = ? ) 4
합계 17
  • new_articles과 new_koin_articles, new_koreatech_articles, lost_item_articles, koin_notice의 OneToOne 관계로 인해 발생하는 문제이다.

  • new_articles은 연관관계의 주인이 아니기 때문에 직접 참조하기 전까지는 연관 객체를 가지고 있는지 확인할 수 없다.

  • 그렇기 때문에 Hibernate에서는 new_articles에서 LAZY로 설정했더라도 조회하는 시점에서 연관 객체를 즉시 로딩한다.

  • 해당 문제를 개선한다.

  • close [공통] Article 1+4N 문제 해결 #2266


🚀 주요 변경 내용

  • Fetch Join
    • 키워드 알림 발송에는 new_articles의 id, board_id, title 필드만 필요하다
    • 4개의 자식 테이블을 모두 join하면 사용하지 않는 데이터까지 가져오게 된다.
    • 또한, OneToOne 4개에 대한 LEFT JOIN은 카테시안 곱과 NULL 컬럼 다수를 유발한다. 리소스 낭비라고 판단
  • DTO Projection
    • 필요한 id, board_id, title 만 골라 DTO로 직접 조회한다.
    • Article 엔티티를 로딩하지 않으므로 OneToOne의 즉시 로딩 메커니즘 자체가 트리거되지 않아 N+1을 원천 차단할 수 있다.
    • 또한 네트워크 전송량과 영속성 컨텍스트 부담도 함께 줄어든다.

-> DTO Project으로 Article를 조회하도록 수정


💬 참고 사항


✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

Summary by CodeRabbit

  • Refactor
    • Optimized article data retrieval to use lightweight summaries instead of loading full article entities, improving system performance for keyword notification operations.

Review Change Stack

@Soundbar91 Soundbar91 self-assigned this May 25, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new ArticleSummary read-model record containing id, boardId, and title fields. A new repository method findAllSummariesByIdIn uses JPQL constructor expressions to efficiently fetch these summaries. KeywordService.sendKeywordNotification is refactored to load summaries instead of full Article entities, reducing database queries significantly.

Changes

Article Summary Read-Model and Repository

Layer / File(s) Summary
ArticleSummary read-model contract
src/main/java/in/koreatech/koin/domain/community/article/model/readmodel/ArticleSummary.java
New immutable ArticleSummary record exposes id, boardId, and title fields as a lightweight projection.
Repository summary query support
src/main/java/in/koreatech/koin/domain/community/article/repository/ArticleRepository.java
ArticleRepository.findAllSummariesByIdIn method uses JPQL constructor to fetch ArticleSummary projections for a collection of article IDs in a single query.
KeywordService integration with summaries
src/main/java/in/koreatech/koin/domain/community/keyword/service/KeywordService.java
sendKeywordNotification loads ArticleSummary records instead of full Article entities, extracts keywords from summary titles, and constructs events using summary fields.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • BCSDLab/KOIN_API_V2#2266: This PR directly addresses the 1+4N query problem during keyword notification processing by replacing full entity loading with lightweight ArticleSummary projections.

Possibly related PRs

  • BCSDLab/KOIN_API_V2#2213: Both PRs modify KeywordService.sendKeywordNotification; this PR switches to ArticleSummary projections while the other modifies event publishing logic in the same method.
  • BCSDLab/KOIN_API_V2#2217: Both PRs modify KeywordService.sendKeywordNotification in the keyword-notification flow; this PR introduces ArticleSummary read-model usage while the other adjusts event publication timing or structure.

Suggested labels

성능개선

Suggested reviewers

  • kih1015

Poem

🐰 A rabbit hops through query fields,
With projections lightweight, no full load revealed,
ArticleSummary springs forth, lean and bright,
Keyword notifications now take flight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: Article 1+4N 문제 해결' directly refers to resolving the N+1 query problem with Article retrieval, which is the main objective of this changeset.
Linked Issues check ✅ Passed The PR successfully addresses the N+1 query problem by introducing ArticleSummary read-model and modifying KeywordService to use projection instead of loading full Article entities, thereby preventing eager loading of OneToOne relations.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving the N+1 query problem: ArticleSummary creation, repository method addition, and KeywordService refactoring are all necessary components of the DTO projection solution.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/2266-article-loading

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

❤️ Share

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

@github-actions github-actions Bot added 공통 백엔드 공통으로 작업할 이슈입니다. 버그 정상적으로 동작하지 않는 문제상황입니다. labels May 25, 2026
@github-actions github-actions Bot requested review from ImTotem and kih1015 May 25, 2026 05:44
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

Unit Test Results

660 tests   657 ✔️  1m 21s ⏱️
163 suites      3 💤
163 files        0

Results for commit 420f794.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown
Contributor

@dh2906 dh2906 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

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.

🧹 Nitpick comments (1)
src/main/java/in/koreatech/koin/domain/community/keyword/service/KeywordService.java (1)

155-157: ⚡ Quick win

Skip repository access when there are no target article IDs.

Add an early return before querying summaries to avoid unnecessary DB calls on empty notification batches.

Proposed patch
 public void sendKeywordNotification(KeywordNotificationRequest request) {
     Set<Integer> updateNotificationIds = request.updateNotification();
+    if (updateNotificationIds == null || updateNotificationIds.isEmpty()) {
+        return;
+    }
     List<ArticleSummary> articleSummaries = articleRepository.findAllSummariesByIdIn(updateNotificationIds);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/in/koreatech/koin/domain/community/keyword/service/KeywordService.java`
around lines 155 - 157, The code calls
articleRepository.findAllSummariesByIdIn(...) even when the set of IDs is empty;
add an early return right after computing Set<Integer> updateNotificationIds =
request.updateNotification() that checks if updateNotificationIds is empty and
returns (or skips further processing) to avoid the DB call; modify the method
containing updateNotificationIds and
articleRepository.findAllSummariesByIdIn(...) to short-circuit when
updateNotificationIds.isEmpty() before invoking articleRepository.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/main/java/in/koreatech/koin/domain/community/keyword/service/KeywordService.java`:
- Around line 155-157: The code calls
articleRepository.findAllSummariesByIdIn(...) even when the set of IDs is empty;
add an early return right after computing Set<Integer> updateNotificationIds =
request.updateNotification() that checks if updateNotificationIds is empty and
returns (or skips further processing) to avoid the DB call; modify the method
containing updateNotificationIds and
articleRepository.findAllSummariesByIdIn(...) to short-circuit when
updateNotificationIds.isEmpty() before invoking articleRepository.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28959010-3ba4-49f7-a37d-b362caa5b972

📥 Commits

Reviewing files that changed from the base of the PR and between 52f85ac and 420f794.

📒 Files selected for processing (3)
  • src/main/java/in/koreatech/koin/domain/community/article/model/readmodel/ArticleSummary.java
  • src/main/java/in/koreatech/koin/domain/community/article/repository/ArticleRepository.java
  • src/main/java/in/koreatech/koin/domain/community/keyword/service/KeywordService.java

@Soundbar91 Soundbar91 merged commit 839e50b into develop May 27, 2026
7 checks passed
@Soundbar91 Soundbar91 deleted the fix/2266-article-loading branch May 27, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

공통 백엔드 공통으로 작업할 이슈입니다. 버그 정상적으로 동작하지 않는 문제상황입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[공통] Article 1+4N 문제 해결

2 participants