18P by GN⁺ 2일전 | ★ favorite | 댓글 4개
  • 데이터 복구와 규제 준수를 위해 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 기반 접근이 적합

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

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

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

Hacker News 의견들
  • 내가 일하는 은행 도메인에서는 오히려 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 중심의 발전이 부족함

  • 예전에 다닌 회사에서는 모든 시스템에 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) 는 어떻게 처리하냐는 질문이 있음
    삭제 당시의 스키마로 직렬화된 데이터를 나중에 복원하려면, 스키마 변경이 문제됨

    • 내 경험상 아카이브된 객체는 거의 접근되지 않음
      삭제 후 며칠 내 복원하는 경우가 대부분이라 스키마 변경 영향이 적음
      오래된 아카이브를 새 모델로 마이그레이션하는 건 복잡하고 오류 가능성이 높은 작업이었음
      결국 시스템의 사용 방식에 따라 접근 전략이 달라짐