프로젝트 · 트러블 슈팅

[플젝기록] 논리 삭제

외계나무 2025. 5. 13. 21:40

물리 삭제(Hard Delete) / 논리 삭제(Soft Delete)

물리 삭제와 논리 삭제는 데이터베이스에서 데이터를 삭제하는 두 방법이다.

물리 삭제는 DELETE 명령어를 통해 실제로 데이터를 삭제하지만, 논리 삭제는 UPDATE 명령을 통해 특정 컬럼을 수정함으로써 삭제 여부를 나타내는 방식을 사용한다.

# 물리 삭제 처리
DELETE FROM `member` WHERE id=1;

# 논리 삭제 처리와 조회
UPDATE `member` SET deleted_at=curdate() WHERE id=1;
SELECT * FROM `member` WHERE deleted_at IS NULL;

즉, 논리 삭제는 데이터가 삭제되었음을 표시만 하는 것인데...

사실 이전 플젝에서는 회원 탈퇴의 경우에만 논리 삭제를 진행하고 (일정 기간 동안 같은 SNS나 Id를 통한 재가입을 방지하기 위해) 이외의 경우에는 써 본 적이 없어서 굳이 논리 삭제를 쓸 일이 있나 싶었다.

그런데 최근, 현직에 있던 사람과 사이드 프로젝트를 하게 되면서, 당시 다뤘던 데이터는 대부분 논리 삭제 했다는 말을 듣고 찾아보게 되었다. 솔직히 처음 들었을 때는 기업이라 DB가 남아도나 ㅋㅋㅋㅋ 였지만, 나름 장단점이 있더라. 

다음은 장단점... 보다는 특징 정리.

물리 삭제

  • 실제로 데이터를 삭제하기 때문에 저장 공간을 새로 확보할 수 있음.
  • 테이블의 크기 줄어들기 때문에 검색 속도 향상을 기대할 수 있음.
  • 데이터를 다시 복구하기 어려움.
  • 데이터가 실제로 삭제되기 때문에 비즈니스 의사결정에 사용하기 어려움.

논리 삭제

  • 데이터를 실제로 삭제하지 않기 때문에 쉽게 복구할 수 있음.
  • 삭제된 데이터도 비즈니스 의사결정에 사용할 수 있음.
  • 테이블에 데이터가 많아져 성능에 악영향을 줄 수 있음.
  • 논리 삭제된 데이터를 같이 조회하는 실수가 발생할 수 있어서 주의 필요.

복구 관련은 어차피 삭제할 때 검증 과정을 거치면 되는데, 비즈니스 의사결정은 가벼운 사이드 프로젝트에서는 생각해 본 적 없는 부분이라 깨달음의 순간이 있었다. 생각해보면 회원이 탈퇴한다고 회원의 모든 종적을 다 삭제해버리면 분기별 통계 낼 때 데이터가 들쭉날쭉할 수 있을 것 같다. 성능만 생각했는데, 그걸 감수하고서라도 통계에 반영되어야 하는 데이터가 있는 법이지... 싶었다.

여하튼, 최근 프로젝트에서는 조건이 2개를 넘어가는 조회 작업의 경우에는 대부분 동적 쿼리를 생성해 조회하고 있어서, 논리 삭제된 데이터를 같이 조회하는 실수를 방지하는 문제는 어렵지 않게 해결할 수 있었다.

최근 프로젝트에서는 Qclass 활용을 위해 JPAQueryFactory를 쓰고 있어서, 다음과 같이 쓰고 있다.

builder.and(qMyEntity.status.notIn(StatusType.DELETED.getValue()));

BooleanBuilder에 데이터 상태 컬럼에 대한 조건을 추가하는 방식이다.

논리 삭제 방법

방법이야 여러가지가 있겠지만, 내가 써 본 방식을 소개하려고 한다.

1. 문자열로 이루어진 특정 컬럼에 _deleted 붙이기

: 탈퇴한 사용자 Id 앞이나 뒤에 붙여서 startsWith / endsWith 체크한다든가 하는 방식으로 쓸 수 있다. 기존 컬럼 값이 나중에 또 쓰일 수 있다면 _deleted 뒤에 날짜를 같이 붙여 쓸 수도 있다.

2. DeleteDate 컬럼 사용하기

: 기본은 null로 두고 논리 삭제 시에 LocalDate 값을 넣은 다음 해당 컬럼이 null이 아니면 삭제된 데이터 취급하는 것이다. 복구할 일 있으면 null로 돌리면 돼서 편하다고 생각하지만, 삭제 날짜가 불필요하다면 아래 방법이 더 나을 수도 있다.

3. 플래그 컬럼 사용하기

: flag, 말 그대로 깃발용의 컬럼을 하나 두는 거다. DeleteDate 컬럼이랑 비슷하지만 보통 boolean(byte)이나 small_int 같은 작은 데이터를 쓰기 때문에 날짜 저장이 불필요하다면 좀 더 효율적인 편. MySQL 같은 RDBMS은 한 row에 들어갈 수 있는 byte 수가 한계가 있다보니 해당 데이터의 다른 컬럼 값이 크다면 1바이트로 삭제 유무를 표시할 수 있어서 유용하다.


새단장 이후의 첫 포스팅인데, 이걸로 쓰게 될 줄은 몰랐지만... 노션 훑어보다 갑자기 눈에 띄어서 그만.

CS 카테고리는 따로 있지만 가끔 플젝에 사용해본 개념이 있으면 이쪽에 올리게 될 것 같다.

마지막으로,

참고한 레퍼런스: 매일메일 (해당 페이지는 찾기 귀찮아서... 아마 지난 질문 목록에서 찾을 수 있을 듯)