✅ 소프트 딜리트는 무엇이고 왜하는걸까?
소프트 딜리트란 데이터를 진짜로 삭제하지 않고 “삭제된 것처럼 표현만 하는 방식”이다.
즉, 데이터 자체는 DB에 그대로 존재하지만 조회에서는 숨긴다.
DELETE 쿼리를 실행해 실제 데이터베이스 테이블 상에서 제거하는 ‘하드 딜리트’ 방식과 대조되는 방식이다.
DELETE FROM users WHERE id = 1;
그럼 왜할까?
삭제했지만 나중에 필요할 수도 있기 때문이다.
- 예를 들어 사용자가 실수로 삭제해서 데이터를 복구해야 할 수도 있고,
- 금융, 의료, 기업 서비스 처럼 데이터를 감사하고 추적해야 하는 서비스의 경우 삭제데이터가 매우 중요할 수도 있다.
또한 DELETE 자체가 UPDATE에 비해 생각보다 비싼 연산이기 때문에 성능상의 이점을 위해 소프트 딜리트를 쓰는 경우도 있다고 한다.
✅ 예약 정보 소프트 딜리트 도입
“삭제된 예약”은 중요한 의미를 가지는 경우가 많다.
예약은 단순 CRUD 데이터가 아니라 결제, 정산, 통계, 감사, CS, 분쟁 등에 사용될 수 있다.
물론 현재 관련된 요구사항이 있는 것은 아니지만, 소프트 딜리트를 학습하기에는 매우 적합한 도메인이라고 생각했다.
💽 Reservation Table
현재 예약 테이블은 다음과 같다.
CREATE TABLE reservation
(
id BIGINT NOT NULL AUTO_INCREMENT,
guest_name VARCHAR(255) NOT NULL,
date DATE NOT NULL,
time_id BIGINT NOT NULL,
theme_id BIGINT NOT NULL,
PRIMARY KEY (id),
UNIQUE (date, time_id, theme_id),
FOREIGN KEY (time_id) REFERENCES reservation_time (id),
FOREIGN KEY (theme_id) REFERENCES theme (id)
);
❌ 구현방법1 : deleted_at 컬럼추가
소프트 딜리트를 구현하는 가장 일반적인 방법은 삭제된 시간을 나타내는 deleted_at 컬럼을 추가하는 것이다.
CREATE TABLE reservation
(
id BIGINT NOT NULL AUTO_INCREMENT,
guest_name VARCHAR(255) NOT NULL,
date DATE NOT NULL,
time_id BIGINT NOT NULL,
theme_id BIGINT NOT NULL,
deleted_at TimeStamp -- 추가!
PRIMARY KEY (id),
UNIQUE (date, time_id, theme_id, deleted_at),
FOREIGN KEY (time_id) REFERENCES reservation_time (id),
FOREIGN KEY (theme_id) REFERENCES theme (id)
);
삭제가 되면 deleted_at을 현재 날짜로 설정한다.
UPDATE RESERVATION SET delete_at = now() where id =?;
문제점:
하지만 이 방식은 유니크 제약 조건때문에 문제가 발생한다.
UNIQUE (date, time_id, theme_id, deleted_at)
예를 들어 삭제되지 않은 예약들은 모두 deleted_at = NULL인데, 대부분의 데이터베이스에서는 유니크 제약에서 NULL 값을 서로 다른 값처럼 취급한다. 따라서 아래와 같은 중복 데이터가 들어갈 수 있다.
date time_id theme_id deleted_at
2026-05-15 1 1 NULL
2026-05-15 1 1 NULL
물론 Postgre 데이터베이스를 쓴다면, Partial Index를 사용해 deleted_at이 Null일 때만 유니크 제약조건이 동작하게 할 수 있다.
CREATE UNIQUE INDEX your_table_unique_active_idx
ON your_table_name (date, time_id, theme_id)
WHERE deleted_at IS NULL;
하지만, Postgre 데이터베이스를 쓰지 않는다면? 설령 쓰고 있더라도 데이터베이스를 바꿔야 한다면?
따라서 근본적인 해결방법은 아니라고 생각했다.
❌ 구현 방법2: is_deleted 컬럼을 추가
CREATE TABLE reservation
(
id BIGINT NOT NULL AUTO_INCREMENT,
guest_name VARCHAR(255) NOT NULL,
date DATE NOT NULL,
time_id BIGINT NOT NULL,
theme_id BIGINT NOT NULL,
deleted_at TimeStamp,
is_deleted Boolean, -- 추가!
PRIMARY KEY (id),
UNIQUE (date, time_id, theme_id, is_deleted),
FOREIGN KEY (time_id) REFERENCES reservation_time (id),
FOREIGN KEY (theme_id) REFERENCES theme (id)
);
deleted_at은 비즈니스 적으로도 사용될 여지가 있기에 그대로 유지. 대신 유니크 제약조건 해결을 위해 is_deleted를 추가하였다.
삭제가 되면 is_deleted를 true로 설정한다.
UPDATE RESERVATION SET is_deleted = true, deleted_at = now() where id =?;
문제점:
하지만 여전히 유니크 제약조건 문제는 해결되지 않는다.
이미 삭제된 데이터와 동일한 예약을 다시 만들고, 그 예약도 다시 삭제하려고 하면 둘 다 true가 되어 유니크 제약에 걸리는 문제가 있다.
date time_id theme_id is_deleted
2026-05-15 1 1 true
2026-05-15 1 1 true -- 유니크 제약 위반
🅾️ 구현방법3: delete_token 컬럼 추가
CREATE TABLE reservation
(
id BIGINT NOT NULL AUTO_INCREMENT,
guest_name VARCHAR(255) NOT NULL,
date DATE NOT NULL,
time_id BIGINT NOT NULL,
theme_id BIGINT NOT NULL,
deleted_at TimeStamp,
delete_token BIGINT NOT NULL DEFAULT 0, -- 추가!
PRIMARY KEY (id),
UNIQUE (date, time_id, theme_id, delete_token),
FOREIGN KEY (time_id) REFERENCES reservation_time (id),
FOREIGN KEY (theme_id) REFERENCES theme (id)
);
is_deleted 대신 delete_token 사용
핵심 아이디어: 삭제되지 않는 데이터는 0, 삭제된 데이터는 해당 row의 pk값으로 변경한다.
id date time_id theme_id delete_token
1 2026-05-15 1 1 0
예약이 삭제되면 delete_token을 해당 row의 pk 값으로 변경한다.
id date time_id theme_id delete_token
1 2026-05-15 1 1 1
이후 같은 날짜, 시간, 테마로 다시 예약을 생성할 수 있다.
id date time_id theme_id delete_token
1 2026-05-15 1 1 1
2 2026-05-15 1 1 0
그리고 새로 생성된 예약도 다시 삭제할 수 있다.
id date time_id theme_id delete_token
1 2026-05-15 1 1 1
2 2026-05-15 1 1 2
'우테코 8기 > 본과정 탐구 일지' 카테고리의 다른 글
| [레벨2 - 방탈출 사용자] Clock을 통해 테스트 상황에서 시간 제어하기 (0) | 2026.05.18 |
|---|---|
| [레벨 2 - 방탈출 사용자] 유니크 제약 조건, 서비스에서 검증할까? 리포지토리에서 처리할까? (0) | 2026.05.10 |
| [레벨2 - 방탈출 사용자] 삭제할 리소스가 없을 때도 204 No Content를 내려도 될까? (0) | 2026.05.10 |
| [레벨2 - 방탈출 사용자] 201 Created 응답에서 Location 헤더는 꼭 필요할까? (0) | 2026.05.10 |
| [레벨 2 - 방탈출 관리자] 테이블 구조 변경으로 인한 코드 수정을 효율적으로 하기 (0) | 2026.05.01 |