# 소프트 삭제의 어려움

> Clean Markdown view of GeekNews topic #26046. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=26046](https://news.hada.io/topic?id=26046)
- GeekNews Markdown: [https://news.hada.io/topic/26046.md](https://news.hada.io/topic/26046.md)
- Type: GN+
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2026-01-23T10:21:04+09:00
- Updated: 2026-01-23T10:21:04+09:00
- Original source: [atlas9.dev](https://atlas9.dev/blog/soft-delete.html)
- Points: 23
- Comments: 4

## Summary

소프트 삭제를 위해 흔히 사용하는 **`archived_at` 열 기반 방식**은 시간이 지날수록 쿼리와 인덱스, 마이그레이션을 복잡하게 만들고, 다시 읽히지 않는 데이터가 쌓여 데이터베이스 효율을 떨어뜨립니다. 이에 대한 대안으로 **트리거 기반 보관**이 주목받으며, 삭제 시 데이터를 별도 테이블에 자동 복사해 라이브 테이블을 단순하게 유지하고 백업·복원 부담을 줄입니다. 새로운 프로젝트에서는 이 접근이 **단순성과 유지보수성의 균형**을 가장 잘 맞추는 선택이라고 주장하는 글입니다. 물론 이건 시스템 성격에 따라 다를 수 있으니 참고만 하셔도 됩니다.

## Topic Body

- 데이터 복구와 규제 준수를 위해 **`archived_at` 열 기반의 소프트 삭제**가 자주 사용되지만, 시간이 지나면 **복잡성과 비효율**이 커짐  
- 이 방식은 **쿼리, 인덱스, 마이그레이션, 복원 로직**을 복잡하게 만들고, 대부분의 보관 데이터가 다시 읽히지 않아 **데이터베이스에 불필요한 부하**를 초래함  
- 대안으로 **애플리케이션 이벤트 기반 보관**, **트리거 기반 보관**, **WAL(Change Data Capture) 기반 보관**이 제시됨  
- 각 방식은 **운영 복잡도, 인프라 요구, 복원 용이성**에서 차이를 보이며, 특히 WAL 기반은 **Kafka 등 외부 시스템과의 통합**이 필요함  
- 새로운 프로젝트라면 **트리거 기반 접근법**이 **단순성과 유지보수성 측면에서 가장 균형 잡힌 선택**임  
  
---  
  
### 소프트 삭제의 문제점  
- 일반적으로 `deleted` 불리언이나 `archived_at` 타임스탬프 열을 사용해 데이터를 논리적으로 삭제함  
  - 고객이 데이터를 실수로 삭제했을 때 복구 가능  
  - 규제나 감사 목적상 보관이 필요한 경우도 있음  
- 그러나 `archived_at` 열은 **쿼리, 운영, 애플리케이션 코드** 전반에 복잡성을 유발  
  - 대부분의 보관 데이터는 다시 읽히지 않음  
  - API 동작 문제나 자동화 도구(Terraform 등)로 인해 **수백만 개의 불필요한 행**이 누적될 수 있음  
- 보관 데이터 정리 작업이 설정되지 않으면 **데이터베이스 백업 및 복원 시 성능 저하** 발생  
- 쿼리와 인덱스에서 보관 데이터를 필터링해야 하며, **데이터 누출 위험** 존재  
- 마이그레이션 시 오래된 데이터 처리나 기본값 수정이 어려움  
- 복원 로직이 복잡해지고, **외부 시스템 호출**이 필요한 경우 버그 발생 가능  
- 결과적으로 `archived_at` 방식은 단순해 보이지만 **장기적으로 유지보수 비용이 높음**  
  
### 애플리케이션 레벨 보관  
- 삭제 시 **이벤트를 발행**하고, 이를 SQS로 전송해 다른 서비스가 S3에 보관  
- 장점  
  - 주요 데이터베이스와 애플리케이션 코드 단순화  
  - 외부 리소스 정리를 **비동기 처리**하여 성능과 안정성 향상  
  - JSON 형태로 직렬화해 **애플리케이션 친화적 구조**로 보관 가능  
- 단점  
  - 애플리케이션 코드 버그로 인해 **보관 데이터 손실** 가능  
  - 메시지 큐 등 **운영 인프라 복잡성 증가**  
  - S3의 보관 데이터는 **검색 및 복원 도구 필요**  
  
### 트리거 기반 보관  
- 삭제 전 트리거가 행을 **별도의 archive 테이블**에 JSON 형태로 복사  
  - 예시 테이블: `archive(id, table_name, record_id, data, archived_at, caused_by_table, caused_by_id)`  
- 외래 키 삭제(cascade) 시 **삭제 원인 추적**을 위해 세션 변수(`archive.cause_table`, `archive.cause_id`) 사용  
  - 어떤 상위 레코드가 하위 데이터를 삭제했는지 조회 가능  
- 장점  
  - **라이브 테이블이 깨끗하게 유지**, `archived_at` 열 불필요  
  - 보관 테이블 정리(`WHERE archived_at < NOW() - INTERVAL '90 days'`)가 간단  
  - 쿼리와 인덱스 효율 유지, 마이그레이션 단순화  
  - 백업 크기 감소  
- 보관 테이블은 **별도 테이블스페이스**나 **시간 파티셔닝**으로 관리 가능  
  
### WAL(Change Data Capture) 기반 보관  
- PostgreSQL의 **WAL 로그를 읽어 삭제 이벤트를 외부 시스템으로 스트리밍**  
  - 대표 도구: **Debezium** (Kafka와 연동)  
  - 경로 예시: `PostgreSQL → Debezium → Kafka → Consumer → Archive Storage`  
- 경량 대안  
  - **pgstream**: WAL을 웹훅이나 메시지 큐로 직접 전송  
  - **wal2json**: WAL을 JSON으로 출력  
  - **pg_recvlogical**: PostgreSQL 내장 논리 복제 도구  
- 운영 복잡성  
  - Kafka 기반은 **모니터링·장애 대응·튜닝** 필요  
  - 소비자(consumer)가 지연되면 **WAL 파일 누적 → 디스크 공간 부족 위험**  
  - PostgreSQL 13+의 `max_slot_wal_keep_size` 설정으로 제한 가능  
  - 복제 슬롯 지연 모니터링 및 알림 필수  
- 장점  
  - 애플리케이션 코드 수정 없이 **모든 변경 캡처 가능**  
  - **다양한 목적지(S3, 데이터 웨어하우스, 검색 인덱스)** 로 스트리밍 가능  
  - 기본 데이터베이스에 추가 부하 없음  
- 단점  
  - **운영 복잡도와 인프라 비용** 높음  
  - 소비자 지연 시 **데이터 손실 또는 재동기화 필요**  
  - 스키마 변경 시 **소스-소비자 간 조율 필요**  
  
### 삭제를 처리하지 않는 복제본 아이디어  
- DELETE 쿼리를 무시하는 **PostgreSQL 복제본**을 유지하는 아이디어 제시  
  - 삭제되지 않은 모든 데이터를 누적 보관 가능  
  - 보관 데이터 **직접 쿼리 가능**  
- 잠재적 문제  
  - 삭제 정보 구분 불가 가능성  
  - 마이그레이션 적용 시 충돌 위험  
  - **저장 공간 및 운영 비용 증가**  
  
### 결론  
- 새로운 프로젝트에서는 **트리거 기반 보관 방식**이 가장 실용적 선택  
  - 설정이 간단하고, 라이브 테이블을 깨끗하게 유지  
  - 별도 인프라 없이도 보관 데이터 조회 및 관리 용이  
- 복잡한 인프라가 이미 존재하거나 다중 목적지 스트리밍이 필요한 경우에는 **WAL 기반 접근**이 적합

## Comments



### Comment 49771

- Author: love7peace
- Created: 2026-01-23T17:19:54+09:00
- Points: 1

트리거 기반이면 db에 부하 생기는 걸로 배웠는데...? 트리거를 추천한다라

### Comment 49832

- Author: nemorize
- Created: 2026-01-24T07:15:58+09:00
- Points: 1
- Parent comment: 49771
- Depth: 1

저정도 트리거에서 생기는 부하가 문제가 되면 트리거가 없어도 이미 문제 투성이인 상황

### Comment 49749

- Author: cherrycoder
- Created: 2026-01-23T12:16:31+09:00
- Points: 1

언제나, 규제는 비용이군요. 뭐 어차피 소비자들이 내야할 몫이지만.

### Comment 49733

- Author: neo
- Created: 2026-01-23T10:21:04+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=46698061) 
- 내가 일하는 **은행 도메인**에서는 오히려 soft delete가 유리하다고 느꼈음  
  `deleted_at` 컬럼이 있으면 쿼리 작성이 명확하고, 분석용 쿼리나 관리자 페이지에서도 동일한 데이터셋을 다룰 수 있음  
  삭제는 대부분의 경우 드물고, soft delete된 행이 **성능 문제**를 일으킨 적도 거의 없었음  
  또한 관계가 그대로 유지되므로 **복구(undo)** 도 간단함  
  나는 더 나아가 행을 완전히 **불변(immutable)** 하게 만들어 업데이트 시 새 행을 추가하는 방식을 선호함  
  로그를 남기려면 DB 트리거를 이용해 INSERT/UPDATE/DELETE 시 복제 테이블에 기록을 남기는 접근이 좋다고 생각함
  - 네 말이 맞지만, 삭제가 드문 경우에만 해당된다고 봄  
    내가 본 테이블 중 50~70%가 soft delete된 경우에는 성능 저하가 확실히 있었음  
    결국 soft delete는 상황에 따라 다르고, **사전 분석**이 필요함
  - Postgres에서는 soft delete되지 않은 데이터만 **인덱싱**하도록 설정할 수도 있음  
    대부분의 경우 필요 없지만, **RAM 절약**에는 도움이 될 수 있음
  - 은행에서 soft delete는 **감사 추적성(auditability)** 부족을 임시로 가리는 해결책에 불과함  
    진짜 해법은 **Event Sourcing**으로, 모든 변경을 이벤트로 기록해야 함  
    성능은 떨어지지만, **스냅샷과 동기화(sync)** 로 보완 가능함
  - DB를 불변 구조로 운영하려면 **Datomic** 같은 시스템을 고려할 만함  
    시간여행(time travel) 기능으로 과거 상태를 완전하게 조회할 수 있음
  - 예전 보험사에서 근무할 때도 각 테이블을 **append-only 로그**로 운영했음  
    최신 상태는 가장 큰 타임스탬프의 행에 있고, 과거 상태는 필터로 조회 가능했음  
    이 방식은 **강력한 이력 관리**를 가능하게 함

- soft delete의 가장 큰 함정은 **쿼리 복잡도**임  
  처음엔 `WHERE deleted_at IS NULL`만 추가하면 된다고 생각하지만, 몇 달 지나면 필터 누락으로 **유령 데이터**가 보고서에 등장함  
  View로 해결할 수 있지만, 결국 **병렬 접근 패턴**을 유지해야 하고, 삭제된 데이터 조회 시엔 추상화를 우회해야 함  
  Event sourcing이 더 깔끔하지만 **운영 부담**이 크기 때문에, 대부분은 하이브리드 접근을 택함
  - View는 충분히 강력한 도구임  
    문제는 많은 **SWE와 BI 엔지니어**가 SQL과 스키마 설계에 익숙하지 않다는 점임  
    soft delete보다 더 흔한 문제는 **Type 2 Slowly Changing Dimension** 처리임  
    대부분 불필요하게 **audit table**을 만들어 비효율적인 UPDATE/INSERT를 반복함  
    사실 DB는 정말 아름다운 시스템인데, 그만큼 **존중받지 못함**이 아쉬움

- soft delete가 **DB 내장 기능**으로 제공되면 좋겠다고 생각함  
  테이블 단위로 활성화하고, 삭제 처리 전략을 선택할 수 있다면 이상적일 것 같음  
  - 실제로 **Iceberg, Delta Lake, BigQuery** 같은 시스템은 **time travel** 기능을 제공함  
    하지만 많은 팀이 커스텀 요구사항 때문에 결국 **SCD(Slowly Changing Dimension)** 방식으로 구현함

- 내 경험상 **트리거 기반 접근**이 가장 안정적이었음  
  아카이브 테이블은 **append-only**로 유지하고, 복구는 애플리케이션 레이어에서 처리해야 함  
  업데이트는 soft delete로 간주하고, 트리거가 이전 상태를 캡처하도록 함  
  트리거는 반드시 **BEFORE** 시점에 실행되어야 하며, 로직은 단순해야 함  
  파티션은 월 단위가 일반적이고, 쓰기 부하가 많으면 일 단위로 나누는 게 좋음

- 나는 DB가 **stateful → stateless**로 진화하길 바람  
  모든 변경을 append-only 이벤트로 기록하고, 필요한 데이터는 **view**로 표현하는 구조를 선호함  
  DB가 자동으로 **materialized index**를 관리해주면 이상적임  
  일부 최신 DB는 이런 기능을 제공하지만, 아직은 **OLTP 중심의 발전**이 부족함
  - 이건 결국 **Event Sourcing** 개념임  
    [Martin Fowler의 설명](https://martinfowler.com/eaaDev/EventSourcing.html)을 참고할 만함

- 예전에 다닌 회사에서는 모든 시스템에 soft delete를 적용했었음  
  교수님도 “**비즈니스 세계에서는 데이터가 절대 삭제되지 않는다**”고 하셨던 게 기억남
  - 완전 삭제는 미래의 **데이터 분석 능력**을 스스로 제한하는 행위임  
    저장 공간은 싸니까, 데이터를 절대 지우지 말아야 함
  - 하지만 수정에 대해서는 교수님이 아무 말도 안 하셨던 게 흥미로움

- 데이터베이스는 **사실(fact)** 을 저장하는 곳임  
  레코드 생성은 새로운 사실, 삭제는 또 다른 사실임  
  하지만 행을 물리적으로 지우면 **사실이 사라짐**  
  대부분의 경우 이런 삭제는 바람직하지 않음  
  - 하지만 데이터가 **유출 위험**을 가진 자산이라면, 오히려 대량 삭제가 필요할 수도 있음  
    유지 비용과 보안 리스크를 고려해야 함
  - DB가 불변이 아니라면, 수정 자체가 이미 **사라진 사실**을 만드는 셈임  
    데이터를 영구 보존하는 결정은 신중해야 함
  - 개인적으로는 데이터 저장소가 **조회와 삽입** 두 가지 연산만 지원해야 한다고 생각함  
    이를 위해 데이터의 **수명 주기**를 이해하는 것이 중요함

- Firezone에서는 처음에 soft delete를 **감사 로그용**으로 썼지만, 마이그레이션 문제로 포기했음  
  대신 **Postgres CDC(Change Data Capture)** 를 사용해 별도의 쓰기 최적화 테이블로 이벤트를 내보내는 방식으로 전환함  
  soft delete는 **사용자 복구 기능**에는 유용하지만, **감사나 규정 준수** 용도로는 부적절하다고 생각함
  - 단순한 프로젝트에서는 DB 변경 대신 **API 호출 자체를 감사**하는 게 더 효율적임

- soft delete 필드를 가진 테이블 위에 **View**를 만들어, 삭제된 행을 숨기는 방식이 깔끔함  
  이렇게 하면 애플리케이션은 삭제 여부를 신경 쓸 필요가 없음
  - Postgres의 **RLS(Row Level Security)** 를 이용하면 soft delete된 행을 자동으로 숨길 수 있음  
    애플리케이션은 여전히 동일한 테이블에 읽기/쓰기/삭제를 수행함

- **스키마 드리프트(schema drift)** 는 어떻게 처리하냐는 질문이 있음  
  삭제 당시의 스키마로 직렬화된 데이터를 나중에 복원하려면, 스키마 변경이 문제됨
  - 내 경험상 아카이브된 객체는 거의 접근되지 않음  
    삭제 후 며칠 내 복원하는 경우가 대부분이라 스키마 변경 영향이 적음  
    오래된 아카이브를 새 모델로 마이그레이션하는 건 **복잡하고 오류 가능성이 높은 작업**이었음  
    결국 시스템의 사용 방식에 따라 접근 전략이 달라짐
