4.1 KiB
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)
삭제 대상 테이블 (순서대로):
- Video
- SongTimestamp (suno_audio_id 기준)
- Song
- Lyric
- Image
- 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()
쿼리 시 주의사항
-
기본 조회 시 is_deleted 필터 추가
- 모든 조회 쿼리에서
is_deleted == False조건을 명시적으로 추가해야 합니다.
- 모든 조회 쿼리에서
-
관리자 기능에서만 삭제된 데이터 포함
- 일반 사용자 API에서는 삭제된 데이터가 노출되지 않도록 해야 합니다.
-
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;