Files
Obsidian/ZELLYY/zellyy note/02_기술_문서/데이터_모델_설계.md
2025-03-26 18:16:46 +09:00

8.9 KiB

Zellyy 데이터 모델 설계

이 문서는 Zellyy 프로젝트의 데이터베이스 모델 설계에 대한 상세 내용을 제공합니다.

스키마 구조

Zellyy 프로젝트는 Supabase의 PostgreSQL 데이터베이스를 사용하며, 모든 테이블은 zellyy 스키마 아래에 생성됩니다. 이는 기존 프로젝트와의 분리를 위한 것입니다.

핵심 테이블

1. zellyy.users

사용자 정보를 저장하는 테이블입니다. Supabase Auth와 연동됩니다.

CREATE TABLE zellyy.users (
    id UUID REFERENCES auth.users(id) PRIMARY KEY,
    email TEXT UNIQUE NOT NULL,
    username TEXT UNIQUE,
    display_name TEXT,
    avatar_url TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    last_login TIMESTAMP WITH TIME ZONE,
    is_premium BOOLEAN DEFAULT FALSE,
    premium_until TIMESTAMP WITH TIME ZONE
);

2. zellyy.cards

사용자가 작성한 카드 정보를 저장하는 테이블입니다.

CREATE TABLE zellyy.cards (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID REFERENCES zellyy.users(id) NOT NULL,
    content TEXT NOT NULL,
    background_color TEXT DEFAULT '#FFFFFF',
    text_color TEXT DEFAULT '#000000',
    font_family TEXT DEFAULT 'system',
    font_size INTEGER DEFAULT 16,
    text_align TEXT DEFAULT 'center',
    is_public BOOLEAN DEFAULT FALSE,
    is_synced BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    deleted_at TIMESTAMP WITH TIME ZONE
);

3. zellyy.card_tags

카드에 적용된 태그 정보를 저장하는 테이블입니다.

CREATE TABLE zellyy.card_tags (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    card_id UUID REFERENCES zellyy.cards(id) NOT NULL,
    tag_name TEXT NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(card_id, tag_name)
);

4. zellyy.social_shares

카드의 소셜 미디어 공유 기록을 저장하는 테이블입니다.

CREATE TABLE zellyy.social_shares (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    card_id UUID REFERENCES zellyy.cards(id) NOT NULL,
    user_id UUID REFERENCES zellyy.users(id) NOT NULL,
    platform TEXT NOT NULL, -- 'facebook', 'instagram', 'twitter', etc.
    share_url TEXT,
    shared_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    status TEXT DEFAULT 'pending', -- 'pending', 'success', 'failed'
    response_data JSONB
);

5. zellyy.social_accounts

사용자의 소셜 미디어 계정 연동 정보를 저장하는 테이블입니다.

CREATE TABLE zellyy.social_accounts (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID REFERENCES zellyy.users(id) NOT NULL,
    platform TEXT NOT NULL, -- 'facebook', 'instagram', 'twitter', etc.
    platform_user_id TEXT,
    access_token TEXT,
    refresh_token TEXT,
    token_expires_at TIMESTAMP WITH TIME ZONE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(user_id, platform)
);

6. zellyy.subscriptions

사용자의 구독 정보를 저장하는 테이블입니다.

CREATE TABLE zellyy.subscriptions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id UUID REFERENCES zellyy.users(id) NOT NULL,
    plan_type TEXT NOT NULL, -- 'monthly', 'yearly', etc.
    status TEXT NOT NULL, -- 'active', 'canceled', 'expired'
    start_date TIMESTAMP WITH TIME ZONE NOT NULL,
    end_date TIMESTAMP WITH TIME ZONE NOT NULL,
    payment_provider TEXT, -- 'apple', 'google', 'stripe', etc.
    payment_id TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

보안 정책 (RLS)

Supabase의 Row Level Security(RLS)를 사용하여 데이터 접근을 제한합니다.

zellyy.users 테이블 정책

-- 사용자는 자신의 정보만 읽을 수 있음
CREATE POLICY "사용자는 자신의 정보만 읽을 수 있음" ON zellyy.users
    FOR SELECT USING (auth.uid() = id);

-- 사용자는 자신의 정보만 업데이트할 수 있음
CREATE POLICY "사용자는 자신의 정보만 업데이트할 수 있음" ON zellyy.users
    FOR UPDATE USING (auth.uid() = id);

zellyy.cards 테이블 정책

-- 사용자는 자신의 카드만 읽을 수 있음 (또는 공개 카드)
CREATE POLICY "사용자는 자신의 카드만 읽을 수 있음" ON zellyy.cards
    FOR SELECT USING (auth.uid() = user_id OR is_public = TRUE);

-- 사용자는 자신의 카드만 생성할 수 있음
CREATE POLICY "사용자는 자신의 카드만 생성할 수 있음" ON zellyy.cards
    FOR INSERT WITH CHECK (auth.uid() = user_id);

-- 사용자는 자신의 카드만 업데이트할 수 있음
CREATE POLICY "사용자는 자신의 카드만 업데이트할 수 있음" ON zellyy.cards
    FOR UPDATE USING (auth.uid() = user_id);

-- 사용자는 자신의 카드만 삭제할 수 있음
CREATE POLICY "사용자는 자신의 카드만 삭제할 수 있음" ON zellyy.cards
    FOR DELETE USING (auth.uid() = user_id);

인덱스

성능 최적화를 위한 인덱스를 생성합니다.

-- 카드 검색 최적화
CREATE INDEX idx_cards_user_id ON zellyy.cards(user_id);
CREATE INDEX idx_cards_created_at ON zellyy.cards(created_at);
CREATE INDEX idx_cards_is_public ON zellyy.cards(is_public);

-- 태그 검색 최적화
CREATE INDEX idx_card_tags_card_id ON zellyy.card_tags(card_id);
CREATE INDEX idx_card_tags_tag_name ON zellyy.card_tags(tag_name);

-- 소셜 공유 검색 최적화
CREATE INDEX idx_social_shares_user_id ON zellyy.social_shares(user_id);
CREATE INDEX idx_social_shares_card_id ON zellyy.social_shares(card_id);

트리거 및 함수

자동화된 작업을 위한 트리거와 함수를 정의합니다.

업데이트 타임스탬프 트리거

-- 업데이트 타임스탬프 함수
CREATE OR REPLACE FUNCTION zellyy.update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 사용자 테이블에 트리거 적용
CREATE TRIGGER update_users_timestamp
BEFORE UPDATE ON zellyy.users
FOR EACH ROW EXECUTE FUNCTION zellyy.update_timestamp();

-- 카드 테이블에 트리거 적용
CREATE TRIGGER update_cards_timestamp
BEFORE UPDATE ON zellyy.cards
FOR EACH ROW EXECUTE FUNCTION zellyy.update_timestamp();

프리미엄 상태 업데이트 함수

-- 구독 상태에 따라 사용자의 프리미엄 상태 업데이트
CREATE OR REPLACE FUNCTION zellyy.update_premium_status()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.status = 'active' THEN
        UPDATE zellyy.users
        SET is_premium = TRUE, premium_until = NEW.end_date
        WHERE id = NEW.user_id;
    ELSIF NEW.status IN ('canceled', 'expired') AND NEW.end_date <= NOW() THEN
        UPDATE zellyy.users
        SET is_premium = FALSE, premium_until = NULL
        WHERE id = NEW.user_id;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 구독 테이블에 트리거 적용
CREATE TRIGGER update_premium_status_trigger
AFTER INSERT OR UPDATE ON zellyy.subscriptions
FOR EACH ROW EXECUTE FUNCTION zellyy.update_premium_status();

데이터 마이그레이션 및 초기 설정

프로젝트 초기 설정을 위한 SQL 스크립트입니다.

-- 스키마 생성
CREATE SCHEMA IF NOT EXISTS zellyy;

-- UUID 확장 활성화
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- 테이블 생성 (위에서 정의한 모든 테이블)

-- 기본 관리자 사용자 생성
INSERT INTO zellyy.users (id, email, username, display_name, is_premium)
VALUES ('00000000-0000-0000-0000-000000000000', 'admin@zellyy.com', 'admin', 'Zellyy Admin', TRUE);

확장성 고려사항

  1. 샤딩: 사용자 수가 크게 증가할 경우, 사용자 ID를 기준으로 데이터를 샤딩하는 전략을 고려할 수 있습니다.

  2. 아카이빙: 오래된 카드 데이터는 별도의 아카이브 테이블로 이동하여 주 테이블의 성능을 유지할 수 있습니다.

  3. 캐싱: 자주 접근하는 데이터(예: 공개 카드, 인기 태그)는 Redis와 같은 캐시 시스템을 도입하여 성능을 향상시킬 수 있습니다.

  4. 읽기 복제본: 쿼리 부하가 높아질 경우, 읽기 전용 복제본을 설정하여 부하를 분산할 수 있습니다.

데이터 백업 전략

  1. 정기 백업: 매일 전체 데이터베이스 백업을 수행합니다.
  2. 증분 백업: 시간별 WAL(Write-Ahead Log) 백업을 통해 세부적인 복구 지점을 제공합니다.
  3. 지리적 복제: 재해 복구를 위해 다른 지역에 백업을 저장합니다.

결론

이 데이터 모델은 Zellyy 프로젝트의 초기 요구사항을 충족하도록 설계되었습니다. 사용자 피드백과 실제 사용 패턴에 따라 모델을 지속적으로 개선하고 확장할 계획입니다.