PENX

[자유토론]

즐겨찾기 기능 구현: 관계 테이블 vs 단일 테이블 방식 비교

unknownExplorer|2026년 3월 18일|조회수: 161

1. 관계 테이블 방식 (Normalized Approach)

구조

-- 기본 users 테이블
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    email TEXT,
    nickname TEXT
);

-- 별도의 favorites 관계 테이블
CREATE TABLE favorites (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER,
    board_id INTEGER,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (board_id) REFERENCES boards(id),
    UNIQUE(user_id, board_id)
);

장점

  • 데이터 정규화: 표준적인 데이터베이스 설계 원칙 준수
  • 무결성 보장: 외래키 제약조건으로 데이터 일관성 유지
  • 확장성: 추가 정보(생성일, 우선순위 등) 쉽게 추가 가능
  • 인덱싱 최적화: 각 컬럼에 인덱스 적용 용이
  • 쿼리 최적화: JOIN을 통한 효율적인 데이터 조회
  • 유지보수성: 명확한 테이블 구조로 코드 이해도 향상

단점

  • 테이블 관리: 추가 테이블 생성 및 관리 필요
  • 쿼리 복잡성: JOIN 연산으로 인한 복잡한 쿼리
  • 성능 오버헤드: 관계 테이블 조회로 인한 약간의 성능 저하
  • 개발 시간: 초기 설계 및 구현 시간 증가

2. 단일 테이블 방식 (Denormalized Approach)

구조

-- users 테이블에 favorites 컬럼 추가
CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    email TEXT,
    nickname TEXT,
    favorites JSON  -- 또는 TEXT로 comma-separated values 저장
);

-- 데이터 예시: [{"board_id": 1, "added_at": "2024-01-01"}, {"board_id": 3, "added_at": "2024-01-02"}]

장점

  • 간단한 구현: 별도 테이블 생성 불필요
  • 빠른 개발: 적은 코드량으로 빠른 구현 가능
  • 성능: 단일 테이블 조회로 인한 빠른 응답 속도
  • 직관적: 하나의 테이블에서 모든 정보 관리
  • 관리 용이: 테이블 수가 적어 관리가 쉬움

단점

  • 데이터 정규화 위반: 같은 정보가 여러 곳에 중복 저장될 수 있음
  • 쿼리 제한: 복잡한 조회 및 집계 연산 어려움
  • 확장성 부족: 구조 변경 시 전체 테이블 수정 필요
  • 데이터 무결성: 제약조건으로 관리하기 어려움
  • 인덱싱 제한: JSON/TEXT 컬럼의 인덱싱이 제한적

3. 실질적인 코드 비교

관계 테이블 방식

// 즐겨찾기 추가
const addFavorite = async (userId, boardId) => {
    const stmt = db.prepare(`
        INSERT OR IGNORE INTO favorites (user_id, board_id) 
        VALUES (?, ?)
    `);
    return stmt.run(userId, boardId);
};

// 즐겨찾기한 게시판의 글 조회
const getFavoriteBoardPosts = async (userId) => {
    const stmt = db.prepare(`
        SELECT p.*, u.nickname as author
        FROM posts p
        JOIN boards b ON p.board_id = b.id
        JOIN favorites f ON b.id = f.board_id
        JOIN users u ON p.user_id = u.id
        WHERE f.user_id = ?
        ORDER BY p.created_at DESC
    `);
    return stmt.all(userId);
};

단일 테이블 방식

// 즐겨찾기 추가
const addFavorite = async (userId, boardId) => {
    const user = db.prepare('SELECT favorites FROM users WHERE id = ?').get(userId);
    let favorites = user.favorites ? JSON.parse(user.favorites) : [];
    
    if (!favorites.find(fav => fav.board_id === boardId)) {
        favorites.push({ board_id: boardId, added_at: new Date().toISOString() });
        db.prepare('UPDATE users SET favorites = ? WHERE id = ?')
          .run(JSON.stringify(favorites), userId);
    }
};

// 즐겨찾기한 게시판의 글 조회
const getFavoriteBoardPosts = async (userId) => {
    const stmt = db.prepare(`
        SELECT p.*, u.nickname as author
        FROM posts p
        JOIN boards b ON p.board_id = b.id
        JOIN users usr ON usr.id = ?
        WHERE b.id IN (
            SELECT json_extract(value, '$.board_id') 
            FROM json_each(usr.favorites)
        )
        JOIN users u ON p.user_id = u.id
        ORDER BY p.created_at DESC
    `);
    return stmt.all(userId);
};

4. 선택 기준

관계 테이블 방식 추천 상황

  • 장기적 프로젝트 운영
  • 복잡한 기능 확장 계획
  • 팀 개발 환경
  • 데이터 무결성 중요
  • 표준적인 데이터베이스 설계 요구

단일 테이블 방식 추천 상황

  • 빠른 MVP 개발
  • 단순한 요구사항
  • 혼자 개발하는 경우
  • 성능 최적화가 중요한 경우
  • 유지보수 시간 제한

5. 결론

두 방식 모두 각자의 장점이 있으며, 프로젝트의 특성과 요구사항에 따라 선택해야 합니다.

  • 규모가 크고 장기적인 프로젝트: 관계 테이블 방식
  • 빠른 개발과 단순한 요구사항: 단일 테이블 방식

댓글 1개

  • unknownExplorer작성자2026년 3월 18일

    저는 단일테이블 방식으로 구현하려고 했는데 ai가 저렇게 알려줘서 고민이 생겼네요