o2o-castad-backend/docs/reference/soft_delete_guide.md

4.1 KiB

소프트 삭제 (Soft Delete) 가이드

개요

소프트 삭제는 데이터를 실제로 삭제하지 않고 is_deleted 필드를 True로 설정하여 삭제된 것처럼 처리하는 방식입니다. 이를 통해 데이터 복구가 가능하고, 삭제 이력을 추적할 수 있습니다.

적용 테이블

테이블 is_deleted 인덱스 비고
User idx_user_is_deleted deleted_at 필드도 포함
Project idx_project_is_deleted
Image idx_image_is_deleted
Lyric idx_lyric_is_deleted
Song idx_song_is_deleted
SongTimestamp idx_song_timestamp_is_deleted
Video idx_video_is_deleted
SocialAccount idx_social_account_is_deleted
RefreshToken - 토큰은 is_revoked로 관리

필드 정의

is_deleted: Mapped[bool] = mapped_column(
    Boolean,
    nullable=False,
    default=False,
    comment="소프트 삭제 여부 (True: 삭제됨)",
)

API 엔드포인트

아카이브 삭제 API

DELETE /archive/videos/delete/{task_id}

task_id에 해당하는 모든 관련 데이터를 소프트 삭제합니다. 백그라운드에서 비동기로 처리됩니다.

백그라운드 태스크

soft_delete_by_task_id

위치: app/archive/worker/archive_task.py

from app.archive.worker.archive_task import soft_delete_by_task_id

# 백그라운드 태스크로 실행
background_tasks.add_task(soft_delete_by_task_id, task_id)

삭제 대상 테이블 (순서대로):

  1. Video
  2. SongTimestamp (suno_audio_id 기준)
  3. Song
  4. Lyric
  5. Image
  6. Project

사용 예시

소프트 삭제 수행

async def soft_delete_project(session: AsyncSession, project_id: int) -> None:
    """프로젝트를 소프트 삭제합니다."""
    stmt = (
        update(Project)
        .where(Project.id == project_id)
        .values(is_deleted=True)
    )
    await session.execute(stmt)
    await session.commit()

삭제되지 않은 데이터만 조회

async def get_active_projects(session: AsyncSession) -> list[Project]:
    """삭제되지 않은 프로젝트만 조회합니다."""
    stmt = select(Project).where(Project.is_deleted == False)
    result = await session.execute(stmt)
    return result.scalars().all()

삭제된 데이터 복구

async def restore_project(session: AsyncSession, project_id: int) -> None:
    """삭제된 프로젝트를 복구합니다."""
    stmt = (
        update(Project)
        .where(Project.id == project_id)
        .values(is_deleted=False)
    )
    await session.execute(stmt)
    await session.commit()

쿼리 시 주의사항

  1. 기본 조회 시 is_deleted 필터 추가

    • 모든 조회 쿼리에서 is_deleted == False 조건을 명시적으로 추가해야 합니다.
  2. 관리자 기능에서만 삭제된 데이터 포함

    • 일반 사용자 API에서는 삭제된 데이터가 노출되지 않도록 해야 합니다.
  3. CASCADE 삭제와의 관계

    • 부모 테이블 소프트 삭제 시 자식 테이블도 함께 소프트 삭제하는 로직 필요
    • 또는 부모만 소프트 삭제하고 자식은 JOIN 시 필터링

마이그레이션

기존 데이터베이스에 is_deleted 필드를 추가하려면:

# 마이그레이션 SQL 실행
mysql -u <user> -p <database> < docs/database-schema/migration_add_is_deleted.sql

또는 Alembic 마이그레이션 사용:

alembic revision --autogenerate -m "Add is_deleted field to all tables"
alembic upgrade head

인덱스 활용

is_deleted 필드에 인덱스가 생성되어 있으므로, 다음과 같은 쿼리가 효율적으로 실행됩니다:

-- 삭제되지 않은 프로젝트 조회 (인덱스 활용)
SELECT * FROM project WHERE is_deleted = FALSE;

-- 복합 조건 (task_id + is_deleted)
SELECT * FROM project WHERE task_id = 'xxx' AND is_deleted = FALSE;