
실무에서 Soft Delete(isDeleted)를 선택하는 이유
그리고 BaseTimeEntity에 삭제 개념을 포함시킨 설계 의도
실무에서 데이터 삭제는 단순한 CRUD 중 하나로 보기 어렵습니다.
특히 운영 환경에서는 데이터가 단순한 값이 아니라 비즈니스 기록이자 자산이기 때문에
“삭제”라는 행위는 항상 신중하게 다뤄져야 합니다.
이 글에서는 실무에서 Hard Delete 대신 Soft Delete(isDeleted)를 선택하는 이유와,
BaseTimeEntity에 삭제 개념을 함께 포함시킨 설계 의도에 대해 구체적으로 설명해 보겠습니다.
1. Hard Delete가 실무에서 가지는 한계
개발 초기에는 다음과 같이 생각하기 쉽습니다.
“삭제 기능이니까 DELETE 쿼리를 사용하면 되지 않을까?”
하지만 실제 운영 환경에서는 Hard Delete가 여러 문제를 야기할 수 있습니다.
1️⃣ 삭제된 데이터는 복구가 어렵습니다
운영 중에는 다음과 같은 요청이 자주 발생합니다.
- “탈퇴 처리한 사용자를 다시 복구할 수 있을까요?”
- “관리자가 실수로 삭제한 데이터인데 되돌릴 수 있나요?”
Hard Delete를 사용했다면, 해당 데이터는 이미 물리적으로 제거되었기 때문에
백업 복구 외에는 방법이 없는 상황이 됩니다.
그리고 대부분의 경우, 이는 현실적인 해결책이 되기 어렵습니다.
2️⃣ 참조 무결성(Foreign Key) 문제
실무 시스템에서는 하나의 엔티티가 여러 테이블에서 참조되는 구조가 일반적입니다.
User
├─ Order
├─ Payment
├─ Review
이런 상황에서 User를 Hard Delete 할 경우,
- FK 제약으로 인해 삭제가 실패하거나
- CASCADE 설정으로 인해 연관 데이터가 연쇄 삭제되거나
- 의도하지 않은 데이터 유실이 발생할 수 있습니다
이는 곧 서비스 장애로 이어질 가능성을 내포하고 있습니다.
3️⃣ 삭제 역시 중요한 이력입니다
삭제는 단순히 데이터를 없애는 행위가 아니라,
- 누가
- 언제
- 어떤 데이터를
- 어떤 맥락에서
삭제했는지에 대한 중요한 감사(Audit) 정보입니다.
Hard Delete는 이러한 정보를 완전히 잃게 만든다는 한계를 가집니다.
2. Soft Delete의 기본 개념
Soft Delete는 데이터를 실제로 삭제하지 않고,
“삭제된 상태”임을 명시적으로 표현하는 방식입니다.
실무에서는 보통 다음 두 가지 컬럼을 함께 사용합니다.
- isDeleted : 삭제 여부
- deletedAt : 삭제 시점
이 방식은 데이터의 복구 가능성과 이력 관리 측면에서 많은 장점을 제공합니다.
3. BaseTimeEntity에 Soft Delete를 포함시킨 이유
아래는 실제로 사용한 BaseTimeEntity 코드입니다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {
@CreatedDate
@Column(updatable = false, nullable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(nullable = false)
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
@Column(nullable = false)
private Boolean isDeleted = false;
// Soft Delete 메서드
public void delete() {
this.isDeleted = true;
this.deletedAt = LocalDateTime.now();
}
}
이 설계는 단순히 “편해서” 선택한 것이 아니라,
엔티티의 생명 주기를 명확히 관리하기 위한 의도적인 선택입니다.
4. 왜 삭제 개념을 BaseTimeEntity에 포함했을까?
1️⃣ 삭제도 엔티티 생명 주기의 일부이기 때문입니다
엔티티는 일반적으로 다음과 같은 흐름을 가집니다.
생성 → 수정 → (논리적) 삭제
이미 생성 시점(createdAt)과 수정 시점(updatedAt)을 관리하고 있다면,
삭제 시점(deletedAt) 역시 동일한 맥락에서 관리하는 것이 자연스럽습니다.
이렇게 구성하면 데이터의 전체 생애 주기를 한눈에 파악할 수 있습니다.
2️⃣ 운영 이슈 대응이 훨씬 수월해집니다
운영 중에는 다음과 같은 질문을 자주 받게 됩니다.
- “이 데이터는 언제 삭제되었나요?”
- “삭제 직전에 마지막으로 수정된 시점은 언제인가요?”
BaseTimeEntity에 삭제 정보가 포함되어 있으면,
로그를 추적하지 않아도 DB 조회만으로 빠르게 원인을 파악할 수 있습니다.
3️⃣ 모든 엔티티에 동일한 삭제 정책을 적용할 수 있습니다
Soft Delete를 각 엔티티마다 개별적으로 구현할 경우,
- 삭제 컬럼이 누락되거나
- 삭제 로직이 서로 달라지거나
- 실수로 Hard Delete를 사용하는 상황이 발생할 수 있습니다
공통 Base Entity에 삭제 정책을 포함시키면
일관된 규칙을 강제할 수 있고, 유지보수 측면에서도 큰 이점이 있습니다.
5. delete() 메서드를 엔티티에 둔 이유
public void delete() {
this.isDeleted = true;
this.deletedAt = LocalDateTime.now();
}
이 메서드는 단순하지만 중요한 의미를 가집니다.
- 실제 삭제가 아니라
- “삭제 상태로 전환한다”는 도메인 행위를 명확히 표현합니다
즉, 삭제를 기술적인 동작이 아니라
비즈니스 의미를 가진 행위로 다루기 위한 설계입니다.
6. 실무에서의 사용 방식
1️⃣ 조회 시 기본 조건
모든 조회 쿼리는 기본적으로 다음 조건을 전제로 합니다.
WHERE is_deleted = false
이를 통해 삭제된 데이터가 일반 사용자 조회에 노출되는 것을 방지합니다.
2️⃣ 삭제 시 repository.delete()를 사용하지 않습니다
실무에서는 보통 다음과 같은 규칙을 둡니다.
- repository.delete() 사용 금지
- Soft Delete 메서드만 허용
이를 통해 의도치 않은 Hard Delete를 사전에 방지할 수 있습니다.
3️⃣ Hard Delete는 제한적으로 사용합니다
Hard Delete는 다음과 같은 경우에만 사용합니다.
- 배치 작업을 통한 데이터 정리
- 법적 보존 기간이 지난 데이터
- 명확한 관리 목적이 있는 관리자 기능
7. 정리하며
Soft Delete는 단순히
“DELETE 쿼리를 사용하지 않는 방법”이 아닙니다.
데이터를 자산으로 인식하고,
운영 환경을 고려한 설계인지에 대한 판단이라고 생각합니다.
isDeleted와 deletedAt, 그리고 이를 포함한 BaseTimeEntity 설계는
운영 안정성과 유지보수성을 모두 고려한 선택이라고 볼 수 있습니다.
'Daily' 카테고리의 다른 글
| [회고] HiSchool 프로젝트 회고 (4) | 2025.06.12 |
|---|---|
| 실무자가 추천한 VS Code 확장 프로그램 모음 (0) | 2025.03.12 |
| [회고] 25년 상반기 인턴 1개월차의 회고 (0) | 2025.03.11 |
| IT 기술 소식 사이트 추천 (0) | 2025.03.11 |
| PowerShell 실행 정책(Execution Policy) 문제 (1) | 2025.03.08 |