Skip to content

fix: 버스 공지 n+1 쿼리 개선#2199

Merged
kih1015 merged 3 commits intodevelopfrom
perf/2195-improve-bus-notice-scheduler-query
Mar 24, 2026
Merged

fix: 버스 공지 n+1 쿼리 개선#2199
kih1015 merged 3 commits intodevelopfrom
perf/2195-improve-bus-notice-scheduler-query

Conversation

@kih1015
Copy link
Contributor

@kih1015 kih1015 commented Mar 24, 2026

🔍 개요

  • 버스 공지 스케줄러(updateBusNoticeArticle)에서 Article 엔티티 전체를 조회하던 쿼리를 Projection으로 개선하여 N+1 문제 및 불필요한 컬럼 조회를 제거합니다.
  • 최근 버스 공지 5건을 조회하는 과정에서 각 공지별로 4개씩 추가 쿼리가 실행되어, 총 21개의 쿼리가 발생하는 N+1 문제를 확인했습니다.
image

🚀 주요 변경 내용

  • BusArticleProjection 클래스 신규 추가
    • id, title, createdAt 필드만 포함하는 Projection DTO
  • ArticleRepository.findBusArticlesTop5OrderByCreatedAtDesc() 쿼리 수정
    • SELECT *SELECT a.id, a.title, a.created_at 로 필요한 컬럼만 조회
    • 반환 타입 List<Article>List<BusArticleProjection> 변경
  • BusNoticeArticle.from(BusArticleProjection) 팩토리 메서드 추가
  • ArticleSyncService.updateBusNoticeArticle() 에서 BusArticleProjection 타입으로 처리하도록 변경

💬 참고 사항

  • 기존 Article 엔티티 전체를 로딩할 경우 연관 엔티티에 의한 N+1 쿼리가 발생할 수 있었으나, Projection 적용으로 필요한 3개 컬럼만 단일 쿼리로 조회합니다.
  • BusArticleProjection은 JPA Native Query 결과 매핑을 위한 일반 클래스로 구현되었습니다 (인터페이스 Projection 대신 생성자 기반 매핑 사용).

✅ Checklist (완료 조건)

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

Summary by CodeRabbit

  • Refactor
    • Improved bus notice retrieval: now fetches and processes a compact, paginated set of recent bus-related posts (top 5) with more precise keyword matching, resulting in faster, more efficient notice updates and reduced data stored for notices.
    • Minor stability and formatting improvements.

kih1015 and others added 2 commits March 24, 2026 22:06
버스 공지 게시글 조회 시 Article 엔티티를 전체 로드하면서
@PostLoad updateAuthor()가 트리거되어 @OnetoOne LAZY 관계
(koinArticle, koreatechArticle 등)에 대한 N+1 쿼리가 발생하던 문제 수정.

필요한 필드(id, title, createdAt)만 조회하는 BusArticleProjection을 도입해
엔티티 로드 없이 단일 쿼리로 처리되도록 개선.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kih1015 kih1015 self-assigned this Mar 24, 2026
@kih1015 kih1015 added the 성능개선 기능개선과 관련된 이슈입니다. label Mar 24, 2026
@github-actions github-actions bot added the Team Campus 캠퍼스 팀에서 작업할 이슈입니다 label Mar 24, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

Introduces a new projection DTO for bus articles and updates repository and service code to use that projection with pagination; adjusts Redis model factory to accept primitive fields and persists the selected projection as a Redis notice.

Changes

Cohort / File(s) Summary
Projection DTO
src/main/java/in/koreatech/koin/domain/community/article/dto/BusArticleProjection.java
Added new public record BusArticleProjection(Integer id, String title, LocalDateTime createdAt) for lightweight projection.
Repository query
src/main/java/in/koreatech/koin/domain/community/article/repository/ArticleRepository.java
Changed method to return List<BusArticleProjection> and accept Pageable; replaced REGEXP native query with JPQL constructor projection and LIKE-based title filters; removed LIMIT in favor of pagination.
Redis model factory
src/main/java/in/koreatech/koin/domain/community/article/model/redis/BusNoticeArticle.java
Replaced static factory that accepted full Article with of(int id, String title) to build Redis notice from projection fields.
Service integration
src/main/java/in/koreatech/koin/domain/community/article/service/ArticleSyncService.java
updateBusNoticeArticle() now requests a paginated list of BusArticleProjection, selects latest via projection accessors, and saves using the new BusNoticeArticle.of(...) factory. Minor formatting adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant Scheduler as ArticleSyncService (Scheduler)
    participant DB as ArticleRepository (DB)
    participant Redis as BusNoticeArticle Repository

    Scheduler->>DB: findBusArticlesTop5OrderByCreatedAtDesc(PageRequest.of(0,5))
    DB-->>Scheduler: List<BusArticleProjection> (id, title, createdAt)
    Scheduler->>Scheduler: select latest projection (by createdAt/title/id)
    Scheduler->>Redis: save BusNoticeArticle.of(latest.id, latest.title)
    Redis-->>Scheduler: ack
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • duehee
  • krSeonghyeon
  • Soundbar91

Poem

🐰 A quick hop, a tiny record found,
I fetch just fields from the ground.
Id and title, time so bright,
N+1 now takes flight tonight! ✨

🚥 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: 버스 공지 n+1 쿼리 개선' directly and concisely describes the main change—fixing N+1 query performance issues in bus notice scheduler, which is the core objective of this PR.
Linked Issues check ✅ Passed All code changes directly implement the N+1 query fix [#2195]: BusArticleProjection DTO eliminates unnecessary column fetching, repository query now uses JPQL with projection returning only required fields (id, title, createdAt), and service updated to process projections instead of full entities.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the N+1 query problem: new projection DTO, repository query refactoring, factory method updates, and service logic modifications. No unrelated code changes or refactoring detected.

✏️ 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 perf/2195-improve-bus-notice-scheduler-query

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

❤️ Share

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

@kih1015 kih1015 changed the title perf: 버스 공지 n+1 쿼리 개선 fix: 버스 공지 n+1 쿼리 개선 Mar 24, 2026
@github-actions
Copy link

github-actions bot commented Mar 24, 2026

Unit Test Results

691 tests   688 ✔️  1m 26s ⏱️
169 suites      3 💤
169 files        0

Results for commit 1e82b63.

♻️ This comment has been updated with latest results.

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

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

Inline comments:
In
`@src/main/java/in/koreatech/koin/domain/community/article/dto/BusArticleProjection.java`:
- Around line 5-27: The current class-based DTO BusArticleProjection causes
ConverterNotFoundException for the native query used by
findBusArticlesTop5OrderByCreatedAtDesc(); change BusArticleProjection from a
concrete class with fields/constructor/getters into an interface named
BusArticleProjection that declares the getter signatures Integer getId(), String
getTitle(), and LocalDateTime getCreatedAt() so Spring Data can map native query
results by column alias to the projection methods; remove the constructor/fields
and ensure the native query uses column aliases that match these getter names.

In
`@src/main/java/in/koreatech/koin/domain/community/article/service/ArticleSyncService.java`:
- Around line 79-83: The code assumes articles is non-empty and calls
articles.get(0), which can throw IndexOutOfBoundsException; update the
ArticleSyncService logic around
articleRepository.findBusArticlesTop5OrderByCreatedAtDesc() to check if the
returned List<BusArticleProjection> articles is empty before accessing index 0
and handle that case (e.g., return/skip processing or set latestArticles to an
empty list) so subsequent use of latestDate and latestArticles is safe; adjust
the block around articles, latestDate and latestArticles accordingly to
early-return or guard against empty results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93439d7d-c1a8-441e-a8fa-593109c7b664

📥 Commits

Reviewing files that changed from the base of the PR and between 33ec82c and 87653fb.

📒 Files selected for processing (4)
  • src/main/java/in/koreatech/koin/domain/community/article/dto/BusArticleProjection.java
  • src/main/java/in/koreatech/koin/domain/community/article/model/redis/BusNoticeArticle.java
  • src/main/java/in/koreatech/koin/domain/community/article/repository/ArticleRepository.java
  • src/main/java/in/koreatech/koin/domain/community/article/service/ArticleSyncService.java

Copy link
Collaborator

@Soundbar91 Soundbar91 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다

- BusArticleProjection class → record로 교체
- Native Query + REGEXP → JPQL + NEW 키워드 + LIKE 조건으로 변경
- LIMIT 5 → Pageable(PageRequest.of(0, 5))로 처리
- BusNoticeArticle.from() → of()로 정리

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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: 2

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

Inline comments:
In
`@src/main/java/in/koreatech/koin/domain/community/article/repository/ArticleRepository.java`:
- Around line 200-202: The current query backing
findBusArticlesTop5OrderByCreatedAtDesc uses only "ORDER BY a.createdAt DESC"
which can produce non-deterministic results when timestamps tie; update the
JPQL/SQL in the repository method to add a deterministic tie-breaker (e.g. ",
a.id DESC" or another unique column) so the top-5 selection is stable across
runs, keeping the method name findBusArticlesTop5OrderByCreatedAtDesc and same
Pageable parameter unchanged.
- Around line 193-200: The JPQL/SQL fragment that selects articles with title
LIKE filters and "a.isNotice = true" is missing the explicit soft-delete
predicate used elsewhere; update the WHERE clause in the query inside
ArticleRepository (the query containing the title LIKE '%통학버스%' ... and
a.isNotice = true) to also include the soft-delete check (e.g. add "AND
a.isDeleted = false" or "AND a.is_deleted = false" to match the repository's
naming convention) so the query is consistent with other repository methods.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a667d4df-cf3a-4bd1-83e5-3fbedd5f14da

📥 Commits

Reviewing files that changed from the base of the PR and between 87653fb and 1e82b63.

📒 Files selected for processing (4)
  • src/main/java/in/koreatech/koin/domain/community/article/dto/BusArticleProjection.java
  • src/main/java/in/koreatech/koin/domain/community/article/model/redis/BusNoticeArticle.java
  • src/main/java/in/koreatech/koin/domain/community/article/repository/ArticleRepository.java
  • src/main/java/in/koreatech/koin/domain/community/article/service/ArticleSyncService.java
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/in/koreatech/koin/domain/community/article/dto/BusArticleProjection.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/in/koreatech/koin/domain/community/article/service/ArticleSyncService.java
  • src/main/java/in/koreatech/koin/domain/community/article/model/redis/BusNoticeArticle.java

@kih1015 kih1015 merged commit e54717d into develop Mar 24, 2026
7 checks passed
@kih1015 kih1015 deleted the perf/2195-improve-bus-notice-scheduler-query branch March 24, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Team Campus 캠퍼스 팀에서 작업할 이슈입니다 성능개선 기능개선과 관련된 이슈입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[캠퍼스] 버스 최신 공지 스케줄러 N+1 쿼리 개선

2 participants