[BE] ERD 설계 - 100-hours-a-week/6-nemo-wiki GitHub Wiki
ERD CLOUD
https://www.erdcloud.com/d/S8syM7aTHQCJK3qRm
테이블 정의서 (Notion)
https://www.notion.so/ERD-1dbe6294970680fc85e6db17be148dd6
ERD 설계 (4.22)
테이블 명세서 (Nemo 서비스)
추후 MSA 설계를 위해 Domain별로 분리해보았습니다.
공통 고려사항
MySQL 8.0.36 사용
-
DATETIME VS TIMESTAMP
- DATETIME
- 로컬 시간 저장
- 자동 초기값을 직접 설정해줘야함.
- 지원 범위가 9999년까지임 → 8byte이기 때문에
- 타임존 영향을 받지 않기 때문에, 글로벌 서비스에서 예약 시스템이나 이벤트 스케줄 관리 등에 사용하기 적합하다.
- TIMESTAMP
- UTC로 저장
- 1970년 1월 1일 자정을 기준으로 몇 초가 지났는지를 저장한다.
- 2038 문제 존재 (4byte 밖에 쓰지 못하기 때문에, MySQL에서 7byte도 가능)
- 하지만 3byte는 마이크로초를 위한 자리이므로 2038년 문제는 해소x
- 로그 데이터(예: 사용자의 마지막 로그인 시간)나 변경 추적 시스템에서 유용하며, 자동으로 갱신되는 기능을 활용할 수 있다.
- DATETIME
-
TEXT VS VARCHAR
- TEXT
- 실제 값은 외부에 저장하고 포인터만 저장한다.
- 인덱싱이 불가능하다.
- VARCHAR
- 인덱싱에 더 유용하다.
- 255자까지 저장가능하다.
→ TEXT
- TEXT
-
PK 단일키 VS FK 복합키
- PK 단일키
- FK 복합키
-
ENUM VS VARCHAR
상황 쓰는 방식 이유 값이 고정됨 (성별, 회원상태) ENUM ('MALE','FEMALE')
같은 건 변경 없음값이 확장 가능성 있다면? VARCHAR 값 추가할 때마다 DDL은 번거롭고 위험 그럼 제약이 없는 VARCHAR가 무조건적으로 좋은 거 아닌가?
→ 고정된 값들은 DB에서 관리하여 값을 강제할 수 있고, 실수를 방지할 수 있다.
-
알림 템플릿 DB VS HTML 파일 관리
DB에 저장 시
- 관리자가 직접 템플릿을 계속 바꾸고 싶을 때
HTML의 장점 디스크 캐시나 classpath 템플릿이 더 빠름 (초기화 속도 빠름)
1️⃣ User Domain
users 테이블
컬럼명 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | INT | PK, NOT NULL, UNSIGNED, AUTO_INCREMENT | - | 회원 고유 ID | 회원이 84억 이상이 되기 어렵기 때문에 INT 사용 |
provider | VARCHAR(20) | NOT NULL | - | OAuth 제공자 (ex: kakao, apple, google 등) | 20자 이내로 충분히 표현 가능 |
provider_id | VARCHAR(50) | NOT NULL | - | OAuth 제공자의 회원 고유 ID | 예: Apple OAuth ID 36자, 50자면 충분 |
VARCHAR(255) | NOT NULL | - | 회원 이메일 주소 | 이메일은 표준적으로 최대 255자 | |
nickname | VARCHAR(20) | NOT NULL | - | 서비스 내 활동명 | 카카오 닉네임 최대 길이 20자에 맞춤 |
profile_image_url | VARCHAR(512) | NOT NULL | - | 프로필 이미지 URL | URL 길이를 고려하여 512자 설정 |
status | ENUM('ACTIVE', 'WITHDRAWN') | NOT NULL | 'ACTIVE' | 회원 상태 (활성화/탈퇴) | 두 개의 고정 상태값이므로 ENUM 사용 |
created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 계정 생성일시 | 생성시 자동 기록을 위해 TIMESTAMP 사용 |
updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 계정 정보 수정일시 | 수정 시점 자동 기록을 위해 TIMESTAMP 사용 |
deleted_at | DATETIME | NULL | - | 회원 탈퇴일시 | 사람이 인식하는 일시 기준, 2038년 문제 없음 |
INDEX
- UNIQUE (
email
)- 이메일 중복 방지용 (닉네임은 중복 허용)
- INDEX (status, created_at)
고려 사항
- idx_users_created_at 인덱스 생성
- 매월 1일 개인정보이용내역 알림톡 발송 예정
- 알림톡 발송을 위해 매월 25일 user의 가입한 달을 기준으로 배치를 돌릴 예정입니다. (다음 달에 알림톡 보낼 대상을 테이블에 INSERT하는 작업의 성능을 위해 인덱스 생성하였습니다.)
user_tokens 테이블
컬럼명 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | BIGINT | PK, NOT NULL, UNSIGNED, AUTO_INCREMENT | - | 토큰 고유 ID | 토큰은 한 사용자당 여러 개 발급될 수 있어 BIGINT 사용 |
user_id | BIGINT | FK, NOT NULL | - | 회원 ID (User 테이블 참조) | 대규모 회원 수 대비 BIGINT 선택 |
refresh_token | VARCHAR(255) | NOT NULL | - | Refresh Token 값 | 토큰 문자열 길이를 고려해 255자 설정 |
device_info | VARCHAR(255) | NOT NULL | - | 로그인한 디바이스 정보 (브라우저, OS 등) | 일반적인 User-Agent 정보 저장을 위해 255자 확보 |
is_revoked | BOOLEAN | NOT NULL | FALSE | 토큰 무효화 여부 (로그아웃 시 TRUE) | 간단한 상태 관리용 BOOLEAN 사용 |
expired_at | DATETIME | NOT NULL | - | Refresh Token 만료 일시 | 특정 시점을 기록해야 하므로 DATETIME 사용 |
created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 토큰 생성일시 | 생성 시점 자동 기록용 |
updated_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 토큰 수정일시 | 수정 시점 자동 기록용 |
INDEX
- UNIQUE (
refresh_token
)- 해당 토큰이 있는지 없는지 파악 / 중복 X
- INDEX (
status
,expired_at
)- 배치로 만료된 토큰 삭제하기 위함.
고려 사항
-
추후 Redis로 토큰 관리 (MySQL VS Redis)
MVP 단계에서는 MySQL을 사용하는 이유
이유 MySQL Redis 개발이 빠름 처음부터 Redis 연동 없이 JPA/쿼리로 빠르게 개발 가능 로직 테스트 용이 로그아웃, 만료 로직 등을 쿼리로 직접 검증 가능 SELECT 쿼리가 없어서 KEYS *로 보아야하는데 위험하다 토큰 이력 관리가 가능 누가 언제 로그인했는지, 어떤 기기에서 로그인했는지 파악 가능 메모리 기반이기 때문에 휘발성이다. RDB/AOF 설정 Redis를 사용하는 이유
- 속도차이 - 인증은 매번 일어나므로 조회 속도와 부하처리가 빠른 Redis 사용
- JWT는 Stateless 하기 때문에 서버가 토큰 상태를 기억하지 않는다.
- 로그아웃을 해도 서버는 모르기 때문에 redis로 그 상태를 기억할 수 있다.
- TTL (만료 처리)에 유용하다.
- MySQL은 쿼리로 직접 관리해야하지만, Redis는 EXPIRE로 자동 삭제된다. → 유지보수 비용이 낮다.
- 서버 간 세션 공유가 쉽다. (분산 인증에 유리)
- 서버가 여러 대일 경우 Redis를 공통 저장소로 쓰면 어디서 로그인해도 동기화 가능하다.
- MySQL은 락 문제, 부하 문제가 발생할 수 있다. (동시성 문제 해결)
-
UNIQUE 와 INDEX의 차이
UNIQUE
는 중복 방지 + 인덱싱,INDEX
는 검색 성능 향상만항목 INDEX
UNIQUE INDEX
중복 허용 가능 중복 불가 목적 성능 향상 (검색 빠르게) 성능 + 유일성 보장 예시 사용처 user_id
,expired_at
email
,refresh_token
,username
등
2️⃣ Group Domain
groups 테이블
컬럼명 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | INT | PK, UNSIGNED, AUTO_INCREMENT | - | 모임 고유 ID | 그룹이 84억 개 이상 생성될 가능성이 낮아 INT 사용 |
owner_id | INT | FK, NOT NULL | - | 모임장 회원 ID (방장 위임 없음) | 방장이 변경될 가능성이 없으므로 안정적인 FK 설정 |
name | VARCHAR(64) | NOT NULL | - | 모임 이름 | 모임 이름은 최대 64자까지 입력 가능 |
summary | VARCHAR(100) | NULL | - | 모임 간단 소개 | 최대 100자 이내로 요약 작성 가능 |
description | TEXT | NULL | - | 모임 상세 설명 | 상세 설명은 최대 약 1000자 기준 |
category | VARCHAR(30) | NOT NULL | - | 모임 카테고리 (학습, 운동 등) | 카테고리 확장성을 고려해 VARCHAR 선택 |
location | VARCHAR(100) | NULL | - | 주요 활동 지역 | 지역명 저장 (ex: 서울 강남구) |
image_url | VARCHAR(512) | NOT NULL | - | 모임 대표 사진 URL | URL 길이를 고려해 512자 설정 |
current_user | TINYINT | NOT NULL, UNSIGNED | 1 | 현재 참여 인원 수 | 모임 생성 시 1명(모임장) default 설정 |
max_user | TINYINT | NULL, UNSIGNED | NULL | 최대 참여 가능 인원 (제한 없으면 NULL) | 최대 인원 100명 이내로 가정해 TINYINT 사용 |
status | VARCHAR(10) | NOT NULL | 'ACTIVE' | 모임 상태 (ACTIVE: 운영중, DISBANDED: 해체됨) | CHECK (status IN ('ACTIVE', 'DISBANDED')) 적용 예정 |
created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 모임 생성 일시 | 생성 시 자동 기록 |
deleted_at | TIMESTAMP | NULL | NULL | 모임 해체 일시 | 해체 시점 기록 (NULL 가능) |
INDEX
- INDEX(status, category, created_at DESC)
- group의 list를 출력할 때 카테고리별로 정렬할 때 사용 (해체된 모임 제외)
- INDEX (status, created_at, owner_id DESC)
- 최신순 조회 (해체된 모임 빼고)
고려사항
-
current_user
- 추후 가입 승인을 할 때마다 +1을 하여 그룹 참여자 테이블 조회(모임에 가입한 사용자를 count하는 작업)를 줄이고자 함.
- 최대 인원이 존재하기 때문에 동시성 문제도 고려할 예정
-
cursor 기반 무한스크롤
-
cursor VS offset
항목 Offset 방식 ( OFFSET, LIMIT
)Cursor 방식 ( WHERE 기준 < cursor
)기본 원리 전체 데이터 중 앞에서부터 건너뛰고 일부만 조회 특정 기준값(시간, ID) 이후(or 이전)의 데이터 조회 페이지 번호 지원 page=1
,page=2
등 명확함페이지 번호 개념 없음 중간 데이터 변경 시 정합성 삽입/삭제로 인해 중복/누락 발생 가능 기준점 이후부터 조회→ 중복/누락 없음 성능 (데이터 많을수록) OFFSET N
이 커질수록 느려짐 ``(앞에서부터 모두 읽고 잘라냄)성능 매우 우수 (인덱스 타고 빠르게 탐색) 쿼리 인덱스 활용도 낮음 ( OFFSET
자체는 인덱스 안 탐)높음 (인덱스가 있으면 빠름) 데이터 정확성 우선 불리 (순서 꼬임 발생 가능) 유리 (항상 커서 이후 데이터만 조회) 검색, 필터 조합 유연하게 사용 가능 정렬 기준 컬럼 하나로 고정되어야 함 ( ORDER BY created_at
) -
우리 서비스에는 cursor가 적합!
- 무한 스크롤 사용 → 전체 페이지 갯수를 파악할 필요 X
- 필터/검색 기능보단 인기순(회원이 많은 모임순)/최신순 정렬이 더 필요하다.
- 삽입/삭제가 빈번하며, 피드 서비스이므로 정렬 중심임.
-
-
모임 검색창
- Elastisearch 사용 고려
-
views → 동시성 문제/DB 성능 최적화 고려
추후 저희 서비스에서는 "모임 상세 페이지" 또는 "모임 카드"를 열람한 횟수를 기반으로
모임의 인기순, 탐색률, 노출 우선순위 등을 판단하고자 합니다.
단순히 매 요청마다
UPDATE group SET view_count = view_count + 1
식으로 처리하면- DB에 부하가 매우 큽니다.
- 다수 사용자가 동시에 열람할 경우 동시성 이슈와 락 경쟁이 발생할 수 있습니다.
조회수 누적은 단순한 기능처럼 보이지만, 트래픽이 몰릴 경우에는 동시성 문제가 발생할 수 있습니다.
Kafka를 사용하는 것은 안정적인 비동기 처리와 확장성을 위한 선택이지만,
서비스 초기에는 Redis 기반 누적 + 주기적 DB 반영 방식으로 충분하며,
점진적으로 메시지 브로커 기반 구조로 확장해갈 수 있을 것 같습니다.
→ MVP 기준에선 DB+1을 사용하고 단기적으로는 Redis를, 장기적으로는 Kafka 기반 비동기 이벤트 처리 + Redis 누적 + 주기적 DB 업데이트를 사용하는 방식으로 동시성 문제 처리와 DB 성능 최적화를 해보고 싶습니다.
-
- Redis 처리흐름
[1] 사용자 A가 게시글 상세 페이지 열람
[2] Spring 서버에서 Redis에 INCR 요청 → view_count:post:{id}
[3] Redis는 즉시 +1 수행 (메모리 연산 → 빠름)
[4] 서버에서는 즉시 응답, DB는 건드리지 않음
[5] 별도 배치 / Scheduled 작업이 일정 주기마다 Redis 누적값 → DB 반영
-
- Kafka 처리흐름
[1] 사용자가 모임 상세 보기 → Kafka Topic("group-view")에 view 이벤트 발행
[2] Kafka Consumer가 메시지를 수신하고, Redis에 모임별 누적 카운트 증가
[3] 일정 주기마다 Redis에서 누적된 view 수를 읽어와 DB에 일괄 반영
(e.g. UPDATE groups SET view_count = view_count + 57 WHERE id = ?)
hashtags 테이블
여보 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | INT | PK, | |||
UNSIGNED | - | 태그 식별 ID | hashtag가 84억개 이상 생성되지 않는다고 생각했습니다. | ||
tag | VARCHAR(20) | NOT NULL | - | 20글자 이내로 생성 |
AI가 직접 생성해주는 tag를 저장
group_hashtags 테이블
여보 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | BIGINT | PK, UNSIGNED | - | 식별 ID | id값의 제한을 두지 X |
group_id | INT | FK | - | 모임 ID | |
tag_id | INT | FK | - | 태그 ID |
INDEX
- INDEX(group_id)
- 그룹 id의 hashtag 조회
3️⃣ Group Participant Domain
group_participants 테이블
여보 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | BIGINT | PK, UNSIGNED | - | 식별 ID | 한 명이 다양한 모임을 참여할 수 있기 때문에 충분한 저장공간이 필요합니다. |
user_id | INT | FK | - | ||
group_id | INT | FK | - | ||
role | ENUM | NOT NULL | - | 역할: LEADER: 모임장, MEMBER: 일반회원 | 두 가지의 경우밖에 존재하지 않기 때문에 ENUM으로 고정하였습니다. |
status | VARCHAR(10) | NOT NULL | - | PENDING: 가입 신청 중, ACCEPTED: 가입 승인, REJECTED: 가입 거부, WITHDRAWN: 탈퇴한 회원, KICKED: 추방된 회원 | 기본값 X (하단에 자세히 설명), CHECK() 사용 예정 |
applied_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 가입 신청 시각 | |
joined_at | TIMESTAMP | NULL | - | 모임 가입 시각 | |
deleted_at | TIMESTAMP | NULL | - | 탈퇴한 시각 |
INDEX
- INDEX(group_id, status, joined_at)
- 상황 1. 특정 모임에 가입한 사용자의 리스트를 조회
- ex) B모임에 가입된 사람들의 리스트
- 상황 2. 모임 가입 신청자의 정렬
- ex) A모임의 가입 신청한 사람들의 리스트 (신청 순서 정렬은 id가 Auto Increament이므로 applied_at을 정렬로 사용하지 않아도 된다고 판단하였습니다.)
- 상황 1. 특정 모임에 가입한 사용자의 리스트를 조회
- INDEX(user_id, status, role)
- A 유저가 참여한 모임의 리스트 조회
- 내가 모임장인 모임의 리스트
고려사항
- status 기본값 X
- 일반 회원들은 모두 PENDING으로 시작하지만, 모임장은 즉시 JOINED 상태가 되기 때문에
- status로 관리
- DELETE와 INSERT보다 UPDATE를 지향하기 때문에 사용자가 모임을 탈퇴했다가 다시 재가입신청을 하여도 튜플을 추가하는 것이 아닌, 상태값만 변경하면 된다.
4️⃣ Schedule Domain
schedules 테이블
여보 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | BIGINT | PK | - | 일정 고유 ID | 하나의 모임은 여러 일정을 가질 수 있습니다. |
owner_id | INT | FK | - | 일정 생성자 ID | |
group_id | INT | FK | - | ||
title | VARCHAR(100) | NOT NULL | - | 일정 제목 | 일정 제목은 최대 100자까지 입력 가능합니다. |
description | VARCHAR(255) | NOT NULL | - | 상세 설명 | 일정 상세는 최대 255자까지 입력 가능합니다. |
address | VARCHAR(255) | NULL | - | 장소 명 | 온라인 일정일 경우 NULL일 수 있습니다. |
current_member | INT | NOT NULL | 1 | 참여중인 인원 - 일정 생성시 1명은 기본적으로 존재 (일정을 생성한 사람) | |
max_participants | INT | NULL | - | 최대 참여 인원 | |
status | VARCHAR(20) | NOT NULL | - | 일정의 상태 |
RECRUITING: 모집중 CLOSED: 모집 마감 IN_PROGRESS: 일정 진행 중 COMPLETED: 종료됨 CANCELED: 취소됨 | 상태값이 확장되거나 축소될 수 있으므로 VARCHAR로 관리합니다.
DB에선 CHECK를 사용하여 옳바른 값인지를 확인하는 과정을 거칠 예정입니다. | | start_date | DATETIME | NOT NULL | - | 일정 시작 시간 | | | created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 생성일 | | | updated_at | TIMESTAMP | NOT NULL | - | 업데이트일 | | | delete_at | DATETIME | NULL | - | 취소일 | |
INDEX
- INDEX(group_id, status, start_at)
- 특정 모임의 일정을 시작시간을 기준으로 정렬
- 상태값을 기준으로 종료된 일정, 다가오는 일정도 구분 가능)
5️⃣ Schedule Participant Domain
schedule_participants 테이블
여보 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | BIGINT | PK | - | 참여 내역 ID | |
schedule_id | BIGINT | FK | - | 일정 참조 | |
user_id | INT | FK | - | 회원 참조 | |
status | VARCHAR(20) | NOT NULL | JOINED | 응답상태 | PENDING: 대기중, ACCEPTED: 참석, REJECTED: 불참 |
created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 생성일 | |
updated_at | TIMESTAMP | NULL | - | 업데이트일 |
INDEX
- INDEX(user_id, status, joined_at)
- 사용자가 참여중인 일정을 정렬한다.
고려 사항
- SSE
- 실시간으로 일정에 참가중인 사용자의 수가 올라가는 것을 보여주기 위해
- 내가 참석한 일정은 Redis Set 사용 예정입니다.
6️⃣ 외부 Notices Domain
external_notices_tasks 테이블 (외부 알림 발송 예정)
컬럼명 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 데이터 타입 선정 이유 |
---|---|---|---|---|---|
id | BIGINT | PK | - | 메시지 고유 ID | |
user_id | INT | FK | - | 수신자 ID | |
content | TEXT | NOT NULL | - | 메시지 본문 내용 | |
type | VARCHAR(20) | NOT NULL | - | 발송 유형 | KAKAO: 카카오톡, EMAIL: 이메일 |
status | ENUM | NOT NULL | PENDING | 전송 상태 | PENDING: 전송 대기, SUCCESS: 전송 성공, FAILED: 전송 실패 |
current_retry_count | INT | NOT NULL, UNSIGNED | 0 | 현재 재전송 횟수 | |
max_retry_count | INT | NOT NULL, UNSIGNED | 3 | 최대 재전송 허용 횟수 | |
scheduled_time | DATETIME | NULL | - | 실제 발송 예정 시각 | |
created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 생성 시각 |
external_notices_logs 테이블 (외부 알림 로그)
컬럼명 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 비고 |
---|---|---|---|---|---|
id | BIGINT | PK | - | 로그 ID | |
message_id | BIGINT | FK | - | 원본 메시지 참조 | |
user_id | INT | NOT NULL | - | 수신자 ID | |
content | TEXT | NOT NULL | - | 전송된 메시지 내용 | |
channel | ENUM | NOT NULL | - | 발송 경로 | KAKAO: 카카오톡, EMAIL: 이메일 |
is_success | BOOLEAN | NOT NULL | - | 전송 성공 여부 | TRUE: 성공, FALSE: 실패 |
error_code | VARCHAR(20) | NULL | - | 오류 코드 | |
error_message | TEXT | NULL | - | 오류 메시지 | |
sent_at | DATETIME | NULL | - | 발송된 시각 | |
created_at | TIMESTAMP | NOT NULL | CURRENT_TIMESTAMP | 로그 생성 시각 |
외부 알림 종류
1. privacy_notices_queue (개인정보 알림톡 발송 예정)
개인정보처리방침 고지를 위한 큐
추가설명
-
구조는
schedule_notification_queue
와 동일하며, 발송 목적과 템플릿이 다름 -
개인정보 알림톡의 경우 서비스 가입일(해당 달)을 기준으로 1년에 한 번 전송할 예정입니다.
- 매월 25일 배치를 돌릴 예정입니다. (다음 달에 알림톡 보낼 대상을 테이블에 INSERT하는 작업)
- 실제 알림톡은 매월 1일에 발송.
- ex) 2025.4.18일 가입자는 매년 4월에 전송
알림톡 - 메세지 브로커 사용
[1] 사용자 특정 행동 발생 (ex. 모임 신청, 일정 취소) [2] Spring Boot 서버가 Kafka에 "알림 이벤트" 전송 [3] Kafka 토픽에 메시지 저장됨 (ex. topic: notification-events) [4] 알림톡 Consumer 서버가 Kafka 구독하여 메시지 수신 [5] 외부 API (카카오 알림톡 등) 호출로 알림 전송 [6] 전송 성공 여부를 DB에 저장 or 실패 시 재시도
7️⃣ 내부 Notices Domain
internal_notices 테이블
여보 | 데이터 타입 | 제약조건 | 기본값 | 설명 | 비고 |
---|---|---|---|---|---|
id | BIGINT | PK | - | 알림 ID | 자동증가 PK 사용 |
user_id | INT | FK | - | ||
title | VARCHAR(100) | NOT NULL | - | 알림 제목 | 100자 이내 |
content | TEXT | NOT NULL | - | 알림 내용 | |
is_read | BOOLEAN | NOT NULL | ‘F’ | 알림 읽음 여부 | TRUE: 읽음, FALSE: 읽지 않음 |
type | VARCHAR(20) | NOT NULL | - | 알림 분류 | |
created_at | TIMESTAMP | NOT NULL | - | 발송시간 |
INDEX
- INDEX(user_id, created_at)
- 사용자별 알림을 최신순으로 조회