Skip to content

[공통] 키워드 알림 매칭 단계 DB 조회 최적화 #2218

@Soundbar91

Description

@Soundbar91

Problem

Image

  • 키워드 알림 매칭 단계에서 다량의 쿼리 발생

    횟수 쿼리 원문 설명
    5 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 = ? ) 각 Article이 코인 게시판(학생 작성 글)에 속하는지 확인 — Article 상세 타입 분기
    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 = ? ) 각 Article이 코인 공지인지 확인 — Article 상세 타입 분기
    5 select k1_0.id, k1_0.article_id, k1_0.author, ..., k1_0.url from new_koreatech_articles k1_0 where ... 각 Article이 코리아텍(포털) 크롤링 글인지 확인 — Article 상세 타입 분기
    5 select l1_0.id, l1_0.article_id, l1_0.author_id, l1_0.category, ..., l1_0.is_found, l1_0.type, ... from lost_articles l1_0 where ... 각 Article이 분실물 게시글인지 확인 — Article 상세 타입 분기
    3 select a1_0.id, a1_0.created_at, a1_0.is_deleted, a1_0.is_filtered, a1_0.keyword, a1_0.last_used_at, a1_0.updated_at from article_keywords a1_0 where ( a1_0.is_deleted = ? ) limit ? 활성 키워드 전체 목록 페이징 조회 (키워드 매칭 루프 진입점)
    3 select a1_0.id, a1_0.keyword_id, a1_0.created_at, a1_0.is_deleted, a1_0.updated_at, a1_0.user_id from article_keyword_user_map a1_0 where ( a1_0.is_deleted = ? ) and a1_0.keyword_id in ( ? ) 각 키워드를 등록한 구독 유저 ID 목록 조회
    3 select u1_0.id, u1_0.anonymous_nickname, u1_0.created_at, u1_0.device_token, u1_0.email, u1_0.gender, u1_0.is_authed, u1_0.is_deleted, u1_0.last_logged_at, u1_0.user_id, u1_0.password, u1_0.name, u1_0.nickname, u1_0.phone_number, u1_0.profile_image_url, u1_0.updated_at, u1_0.user_type from users u1_0 where u1_0.id in ( ? ) and ( u1_0.is_deleted = ? ) ArticleKeywordUserMap와 관계를 맺은 User 조회 쿼리
    2 select count ( a1_0.id ) from article_keywords a1_0 where ( a1_0.is_deleted = ? ) 활성 키워드 전체 개수 — 페이징을 위한 count 쿼리
    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 (...) 알림 대상이 될 신규 Article들을 IN 절로 일괄 조회 (매칭 파이프라인 시작점)
    • Articles과 KoreatechArticle, KoinArticle, LostItemArticle, KoinNotice의 OneToOne 관계로 인해 1+4N 쿼리 발생
      • 키워드 매칭 과정에서는 Articles의 id, title만 사용
      • 4개의 연관관계 엔티티를 로딩할 필요성이 없음
    • ArticleKeywords/ArticleKeywordUserMap의 데이터 양에 비해 페이징 루프 필요성이 낮음
      • 현재 데이터: ArticleKeyword 162건, ArticleKeywordUserMap 438건 (1년 6개월 누적)
      • 월평균 증가: 약 8.1건 / 21.9건
    • ArticleKeywordUserMap과 연관관계를 맺고 있는 User 엔티티의 Eager 로딩으로 인해 추가 쿼리 발생

Proposed Change

  • DTO Projection으로 Article 조회
  • ArticleKeyword/ArticleKeywordUserMap의 페이징 루프를 제거하고 일괄 조회로 변경
  • 페이징 제거에 따라 count 쿼리 부가 발생도 함께 해소
  • ArticleKeywordUserMap의 User 엔티티를 Fetch Join을 통해 가져와 추가 쿼리 개선

Tasks

  • Article 조회를 DTO Projection으로 변경 (id, title만 조회)
  • KeywordExtractor의 페이징 루프 제거 및 일괄 조회로 전환
  • 변경 전후 쿼리 카운트 측정

Acceptance Criteria

  • 키워드 매칭 단계 총 쿼리 수
    • Article 5건 기준 32회 → 6회 (예상값)
    • Article 상세 타입 조회: 20회 → 0회 (DTO Projection)
    • ArticleKeyword 조회: 3회 → 1회 (페이징 제거)
    • ArticleKeywordUserMap 조회: 3회 → 1회 (페이징 제거)
    • count 쿼리: 2회 → 0회 (페이징 제거)
    • User 조회: 3회 → 0회 (Fetch Join)

Metadata

Metadata

Assignees

Labels

성능개선기능개선과 관련된 이슈입니다.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions