Repository navigation
ERD
LeeYongmin edited this page Oct 15, 2026
·
8 revisions
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
}
-
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)
| 컬럼 | 타입 | 비고 |
|---|---|---|
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 |
| 컬럼 | 타입 | 비고 |
|---|---|---|
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_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를 가리키지 않고 내용을 복사해 둔다.
| 컬럼 | 타입 | 비고 |
|---|---|---|
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: 질문과 관련된 기사 검색 (한국어 대응)
| 컬럼 | 타입 | 비고 |
|---|---|---|
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에서 바로 읽는다.
| 버전 | 내용 |
|---|---|
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 다시 생성 |