Skip to content
LeeYongmin edited this page Oct 15, 2026 · 8 revisions

ERD (개념)

erDiagram
    users ||--o{ questions : "질문한다"
    users ||--o{ saved_articles : "저장한다"

    users {
        bigint user_id PK
        varchar email UK
        varchar password "BCrypt"
        varchar nickname
        datetime created_at
        datetime updated_at
    }
    questions {
        bigint question_id PK
        bigint user_id FK
        varchar query_text
        varchar status "PENDING PROCESSING COMPLETED REJECTED FAILED"
        text answer
        json sources "근거 기사"
        datetime saved_at "질문 저장(별), NULL이면 저장 안 함"
        datetime created_at
        datetime started_at
        datetime completed_at
    }
    saved_articles {
        bigint saved_article_id PK
        bigint user_id FK
        varchar title
        varchar url "user_id와 함께 UK"
        varchar press
        datetime published_at
        datetime created_at "저장 시각"
    }
    news_articles {
        bigint article_id PK
        varchar category
        varchar title
        text description
        varchar press
        varchar url UK
        datetime published_at
        datetime collected_at
    }
    news_issues {
        bigint issue_id PK
        varchar category
        datetime generated_at "요약 배치 시각"
        int rank_no
        varchar title
        text summary "한 줄 요약"
        json key_points "핵심 포인트"
        json timeline "전개 흐름 (근거 기사로 만듦)"
        json glossary "용어 풀이"
        json sources "근거 기사"
        boolean mock
    }
Loading
  • news_articles(수집한 원문, 질문 답변의 근거 검색 대상)와 news_issues(30분마다 만든 분야별 요약)는 사용자와 무관한 테이블이라 관계선이 없다.

  • 시간 컬럼은 모두 DATETIME(UTC 저장, 응답 시 +09:00으로 변환).

  • 문자셋 utf8mb4.

  • 분야 코드(category): AI, IT_GENERAL, MOBILE_TELECOM, COMPUTER, INTERNET, SCIENCE (IT 6개 분야, docs/api-spec.md)

유저: users

컬럼 타입 비고
user_id BIGINT PK, Auto Increment
email VARCHAR(255) NOT NULL, UNIQUE
password VARCHAR(255) NOT NULL, BCrypt 해시
nickname VARCHAR(20) NOT NULL
created_at DATETIME NOT NULL, Default CURRENT_TIMESTAMP
updated_at DATETIME NOT NULL, Default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

질문: questions

컬럼 타입 비고
question_id BIGINT PK, Auto Increment
user_id BIGINT NOT NULL, FK (users.user_id) ON DELETE CASCADE
query_text VARCHAR(500) NOT NULL
status VARCHAR(20) NOT NULL (PENDING, PROCESSING, COMPLETED, REJECTED, FAILED)
answer TEXT NULL
sources JSON NULL, 근거 기사 [{press, title, url, publishedAt}]
saved_at DATETIME NULL, 질문 저장(별) 시각. NULL이면 저장 안 함
created_at DATETIME NOT NULL, Default CURRENT_TIMESTAMP
started_at DATETIME NULL, 워커가 처리 시작한 시각 (기록용. 60초 타임아웃은 created_at 기준)
completed_at DATETIME NULL

인덱스

  • (user_id, created_at): 질문 기록 목록, 하루 질문 횟수 집계
  • (user_id, saved_at): 저장한 질문 목록
  • (status, created_at): 오래된 PENDING 재투입, 60초 타임아웃 판정

기존 saved_questions 테이블은 questions.saved_at으로 대체했다. 질문 1개에 별은 하나뿐이라 별도 테이블이 필요 없고, 기록을 삭제하면 별도 같이 사라지는 규칙이 저절로 지켜진다.

저장한 기사: saved_articles

컬럼 타입 비고
saved_article_id BIGINT PK, Auto Increment
user_id BIGINT NOT NULL, FK (users.user_id) ON DELETE CASCADE
title VARCHAR(255) NOT NULL
url VARCHAR(512) NOT NULL, http(s) 주소만
press VARCHAR(100) NULL
published_at DATETIME NULL
created_at DATETIME NOT NULL, Default CURRENT_TIMESTAMP (저장 시각)

인덱스

  • UNIQUE (user_id, url): 같은 기사를 두 번 저장하지 않는다
  • (user_id, created_at): 저장한 기사 목록 (최근 저장 순)

분야 페이지의 근거 기사 하나를 저장한다. 원본(news_articles)은 7일 뒤 지워지므로 news_articles를 가리키지 않고 내용을 복사해 둔다.

수집 기사: news_articles

컬럼 타입 비고
article_id BIGINT PK, Auto Increment
category VARCHAR(20) NOT NULL, 분야 코드
title VARCHAR(255) NOT NULL
description TEXT NULL, RSS가 주는 기사 요약문
press VARCHAR(100) NULL
url VARCHAR(512) NOT NULL, UNIQUE (같은 기사 중복 수집 방지. 여러 분야 피드에 같은 기사가 있어도 한 분야에만 저장)
published_at DATETIME NULL
collected_at DATETIME NOT NULL, Default CURRENT_TIMESTAMP

인덱스

  • (category, collected_at)
  • FULLTEXT (title, description) WITH PARSER ngram: 질문과 관련된 기사 검색 (한국어 대응)

분야 요약: news_issues

컬럼 타입 비고
issue_id BIGINT PK, Auto Increment
category VARCHAR(20) NOT NULL, 분야 코드
generated_at DATETIME NOT NULL, 요약 배치 시각 (같은 배치의 이슈는 같은 값)
rank_no INT NOT NULL, 분야 안에서의 순서 (1부터, 홈은 1~3)
title VARCHAR(255) NOT NULL
summary TEXT NOT NULL, 한 줄 요약
key_points JSON NOT NULL, 핵심 포인트 ["...", ...]
timeline JSON NOT NULL, 전개 흐름 [{date, text}]. 근거 기사의 발행일·제목으로 만든다 (AI가 만들지 않음). 빈 배열 가능
glossary JSON NOT NULL, 용어 풀이 [{term, description}]. 빈 배열 가능
sources JSON NOT NULL, [{press, title, url, publishedAt}]
mock BOOLEAN NOT NULL, mock AI로 만든 요약 여부

인덱스: (category, generated_at). 분야별로 가장 최근 generated_at의 이슈들을 보여준다. 갱신에 실패하면 새 행이 생기지 않으므로 직전 요약이 그대로 보인다.

Redis에는 홈·분야 응답을 캐시만 한다 (키 news:v2:home, news:v2:category:{분야}, TTL 30분. 응답 모양이 바뀌면 버전을 올린다). 요약 배치가 끝나면 캐시를 비워 새 요약이 바로 보인다. Redis가 죽으면 DB에서 바로 읽는다.

마이그레이션 (Flyway)

버전 내용
V1 초기 스키마: users, questions, saved_articles, news_articles, news_issues 및 FK·인덱스
V2 IT 6개 분야로 변경: 옛 분야 데이터(news_issues, news_articles) 삭제, 저장한 기사 기능 제외로 saved_articles 삭제
V3 분야 개편(GAME 제외, AI 추가): GAME 기사 삭제. 이슈 상세 필드 추가: 옛 news_issues 삭제 후 key_points·timeline·glossary 컬럼 추가 (다음 배치가 다시 채움)
V4 저장한 기사 기능 복구 (FE 마이페이지): saved_articles 다시 생성

Clone this wiki locally