Project/트렌드 매거진

[트렌드매거진] 25.03.22 개발일지/ 카테고리별 아티클 조회 알고리즘 고민

한33 2025. 3. 23. 01:12

 

📚 서비스 개요와 설계 목표

⚡ 고민

메인 카테고리만으로 글을 나열하면 사용자가 세부 관심사를 반영하기 어렵다. 예를 들어, "스포츠" 안에 "축구"와 "농구"가 섞이면 사용자가 원하는 주제를 필터링하기 힘들다. 나는 AI가 상세 카테고리를 동적으로 생성해 이를 해결하고자 했다. 

 

Grok3 는 상세 카테고리를 초기에는 최대 5개로 제한하고, 최소 2개 글이 있어야 생성하도록 제안했다. 이는 콘텐츠 부족 문제를 피하면서도 유연성을 유지할 거 같지만 더 생각해봐야겠다.


📚 상세 카테고리 설계와 문제 해결

🚨 동적 상세 카테고리 도입

상세 카테고리(태그)는 게시글당 5개까지 동적으로 생성된다. 나는 이를 AI가 글 내용을 분석해 자동으로 분류하는 방법을 생각했다.

예를 들어, "손흥민 골 기록" 글은 "유럽축구", "프리미어리그", "손흥민" 태그가 붙는다.

🚨 태그 폭증과 유사성 문제

태그가 동적으로 생성되면 "해외축구", "유럽축구", "프리미어리그"처럼 비슷한 태그가 기하급수적으로 늘어난다. 나는 사용자가 "해외축구"에 관심 있어도 "유럽축구" 태그 글을 놓칠까 걱정했다.

 

AI가 태그 유사도를 분석해 해결하면 어떨까.

 

예를 들어, "해외축구"와 "유럽축구"의 유사도가 0.85라면, 사용자의 관심도를 연결해 매칭한다. 유사도는 BERT 같은 모델로 계산하며, 결과는 데이터베이스에 저장된다.

🚨 고정 vs 동적 태그 논의

나는 태그를 고정으로 지정할지 고민했다. 그러나 초기에 고정 태그를 10개로 제한하면 글이 부족해 허술해 보일 수 있다. 초기는 동적 태그로 시작하고, 콘텐츠가 쌓이면 인기 태그를 고정으로 전환하는 하이브리드 방식도 생각해보았다.


📚 사용자 관심도와 매칭 로직

💡 관심도 점수화

사용자 관심도는 행동 기반으로 계산된다. 클릭은 +1점, 좋아요와 저장은 +3점이다.

예를 들어, 사용자가 "해외축구" 글을 5번 클릭하면 5점이 된다.

💡 매칭도 계산

매칭도는 사용자 관심도, 태그 유사도, 태그 중요도를 곱해 구한다.

예시:

  • 사용자: "해외축구=5".
  • 게시글: "유럽축구=0.9", 유사도 0.85.
  • 계산: 5 × 0.85 × 0.9 = 3.825.

이를 사전 계산해 저장하거나, 조회 시 캐시에서 처리하면  DB 부하를 줄일 수 있을거라 예상한다.

💡 구현 난이도와 비용

AI 가 모든 게시글을 조회하면서 분석해 관심도를 점수화하고 매칭도를 계산하는 이 구조가 다소 복잡하다 생각이 들었고 AI 토큰 비용이 걱정되었다.

 

실시간 분석 대신 등록 시 태그 중요도를 한 번만 계산(500단어=500~700토큰, 약 $0.005)하고, 조회는 단순 연산으로 처리하면 비용이 적을 것으로 예상된다. (게시글 100개 분석은 약 $0.50, 게시글 당 500단어 기준)


🗄️ 데이터베이스 설계와 최적화

📋 테이블 구조

예상 테이블 구조:

  • Posts: 게시글 (post_id, title, main_category, created_at).
  • Tags: 태그 (tag_id, tag_name).
  • Post_Tags: 게시글-태그 (post_id, tag_id, importance).
  • Tag_Similarities: 태그 유사도 (tag_id_1, tag_id_2, similarity).
  • Users: 사용자 (user_id, username).
  • User_Interests: 관심도 (user_id, tag_id, interest_score).

* 테이블 구조와 예시 데이터

더보기
 

💡 테이블 구조와 예시 데이터

 
Posts (게시글 정보)
  • 용도: 게시글의 기본 데이터를 저장.
  • 구조: post_id (PK), title, content, main_category, created_at.
Tags (태그 목록)
  • 용도: 모든 상세 카테고리(태그)를 관리.
  • 구조: tag_id (PK), tag_name.
Post_Tags (게시글-태그 관계)
  • 용도: 게시글에 붙은 태그와 AI가 계산한 중요도를 저장.
  • 구조: post_id (FK), tag_id (FK), importance.
Tag_Similarities (태그 유사도)
  • 용도: 태그 간 유사도를 저장해서 "해외축구"와 "유럽축구" 같은 관계를 연결.
  • 구조: tag_id_1 (FK), tag_id_2 (FK), similarity.
Users (사용자 정보)
  • 용도: 사용자 기본 정보 저장.
  • 구조: user_id (PK), username.
User_Interests (사용자 관심도)
  • 용도: 사용자가 태그에 얼마나 관심 있는지 점수로 기록.
  • 구조: user_id (FK), tag_id (FK), interest_score.

 

 

💡부하 관리 추가 테이블

  1. User_Post_Matchings (사전 계산 매칭도)
    • 용도: 매칭도를 미리 계산해서 저장, 실시간 부하 줄이기.
    • 구조: user_id (FK), post_id (FK), matching_score, updated_at.

* 데이터 흐름 예시

더보기

데이터 흐름 예시

  1. 게시글 등록:
    • 기자가 "손흥민 프리미어리그 골 기록" 올리면 Posts에 저장(post_id=1).
    • AI가 태그 분석해서 Post_Tags에 "유럽축구=0.9", "프리미어리그=0.7", "손흥민=0.5" 저장.
  2. 사용자 행동:
    • user001이 게시글 1에 좋아요 → User_Interests에서 "손흥민" 점수가 3에서 6으로 증가.
  3. 매칭도 계산:
    • user001의 관심도("해외축구=5, 손흥민=6")와 게시글 1 태그 비교.
    • 계산:
      • "유럽축구": 5 × 0.85 × 0.9 = 3.825 (해외축구 유사도 반영).
      • "손흥민": 6 × 1.0 × 0.5 = 3.0.
      • 총 매칭도: 3.825 + 3.0 = 6.825.
    • 이 결과가 User_Post_Matchings에 저장돼.
  4. 조회:
    • user001이 "스포츠" 요청 → Redis 캐시("스포츠:user001") 확인.
    • 캐시 없으면 User_Post_Matchings에서 post_id=1, matching_score=6.825 꺼내서 보여줘.
    • 캐시에 저장 후 다음 요청은 빠르게 처리.

부하 관리 적용

  • 캐싱: Redis에 "스포츠:user001": {"post_id": 1, "matching_score": 6.825} 저장. TTL 1시간.
  • : 좋아요 후 매칭도 갱신 작업을 Kafka에 넣어 비동기로 처리. 예: {user_id: "001", action: "recalculate"}.
  • 인덱싱: Posts.main_category, Post_Tags.post_id에 인덱스 추가로 검색 속도 업.

📋 DB 부하 고민

조인 쿼리로 매칭도를 계산하면 DB 부하가 크다보니 IO 요청 증가가 우려됐다.

📋 부하 관리 방안

  • 캐싱: Redis에 매칭도("스포츠:user001")와 태그 유사도를 저장한다. TTL은 1시간이다.
  • 사전 계산: User_Post_Matchings 테이블에 매칭도를 저장한다.
  • : Kafka로 비동기 계산한다. 나는 큐가 필요한지 물었고, Grok 3는 초기엔 없어도 되지만 규모 커지면 부하 분산에 유용하다고 했다.
  • 인덱싱: 주요 필드에 인덱스를 추가한다.

예시: 캐시 히트 시 DB 요청이 80% 줄어든다.


⏰ 최신성과 다양성 반영 ( 고민 )

🛠️ 최신성 문제

나는 관심도만으로 정렬하면 최신 글이 하단으로 밀린다고 생각했다. 정보 서비스이니 등록일도 중요하다.

🛠️ 해결 방안

  • 혼합 점수: final_score = (matching_score * 0.7) + (freshness_score * 0.3). 예: 게시글 1(6.825, 3일 전)=5.6775.
  • 랜덤성: 상위 10개 중 2~3개를 최신 글로 교체한다.
  • 섹션 분리: 추천(관심도)과 최신순을 나눈다.

나는 혼합 점수와 랜덤성을 초기 구현으로 추천받았다.

🛠️ 예시

  • 사용자 001: "해외축구=5, 손흥민=3".
  • 게시글 1: "유럽축구=0.9, 손흥민=0.5", 3일 전 → 5.6775.
  • 게시글 3: "유럽축구=0.9", 1시간 전 → 5.6775. 최신성이 반영돼 순서가 조정된다.