소프트 삭제와 하드 삭제의 실제 트레이드오프를 배우세요 — 분석, 지원, GDPR식 삭제, 쿼리 복잡성에 미치는 영향과 안전한 복원 패턴까지.

삭제 버튼은 데이터베이스에서 두 가지 아주 다른 의미를 가질 수 있습니다.
하드 삭제는 행을 완전히 제거합니다. 이후에는 백업, 로그, 복제본이 없다면 레코드는 사라집니다. 이해하기는 쉽지만 돌이킬 수 없습니다.
소프트 삭제는 행을 유지하면서 deleted_at이나 is_deleted 같은 필드로 삭제된 상태를 표시합니다. 애플리케이션은 표시된 행을 보이지 않게 취급합니다. 관련 데이터를 보존하고 이력을 유지하며, 경우에 따라 레코드를 복원할 수 있습니다.
이 선택은 사람들이 생각하는 것보다 일상 업무에 자주 나타납니다. 예를 들어 “지난달 매출이 왜 떨어졌지?”, “내가 삭제한 프로젝트를 복구할 수 있나요?” 또는 “GDPR 삭제 요청을 받았는데 개인정보를 실제로 삭제하고 있나요?” 같은 질문에 영향을 줍니다. 또한 UI에서 ‘삭제됨’이 무엇을 의미하는지도 결정합니다. 사용자는 흔히 되돌릴 수 있을 거라 생각하다가 그럴 수 없을 때 당황합니다.
실용적인 경험칙:
예: 고객이 워크스페이스를 삭제한 뒤 그 안에 회계에 필요한 인보이스가 있었음을 깨달으면, 소프트 삭제라면 지원이 안전하게 복원할 수 있습니다. 하드 삭제라면 백업 설명과 지연, 또는 ‘불가능합니다’라는 대답을 해야 할 가능성이 큽니다.
어떤 방법이든 ‘최고’인 방법은 없습니다. 사용자 신뢰, 보고 정확성, 개인정보 준수 중 무엇을 보호할지에 따라 가장 덜 고통스러운 선택이 달라집니다.
삭제 선택은 분석에서 빠르게 드러납니다. 활성 사용자, 전환, 매출을 추적하기 시작하는 순간, “삭제됨”은 단순한 상태가 아니라 보고 규칙이 됩니다.
하드 삭제를 하면 많은 지표가 깔끔해 보입니다. 제거된 레코드는 쿼리에서 사라지기 때문입니다. 하지만 과거 구독, 팀 규모, 이전 퍼널 같은 맥락을 잃게 됩니다. 삭제된 고객 때문에 과거 차트가 재실행할 때 변하면 재무나 성장 리뷰에서 큰 문제가 됩니다.
소프트 삭제를 하면 이력을 유지할 수 있지만 숫자가 부풀려질 위험이 있습니다. 단순한 “COUNT users”가 떠난 사람까지 포함할 수 있습니다. 한 리포트에서는 deleted_at을 이탈(churn)로 처리하고 다른 리포트에서는 무시하면 이중 집계가 생깁니다. 인보이스는 남아있지만 계정이 삭제된 경우 매출 계산도 복잡해집니다.
효과적인 방법은 일관된 보고 패턴을 정하고 지키는 것입니다:
핵심은 문서화입니다. 분석가가 추측하지 않도록 ‘활성’의 의미, 소프트 삭제된 사용자를 포함하는지 여부, 계정이 나중에 삭제될 경우 매출 귀속 방식 등을 기록하세요.
구체적 예: 워크스페이스가 실수로 삭제되었다가 복원된 경우, 대시보드가 삭제 필터를 사용하지 않으면 갑작스러운 하락과 반등이 표시되어 실제 사용과 다른 결과를 보여줍니다. 스냅샷을 사용하면 과거 차트는 안정적으로 유지되면서 제품 뷰만 삭제된 워크스페이스를 숨길 수 있습니다.
삭제 관련 지원 티켓은 대개 비슷합니다: “실수로 삭제했어요” 또는 “내 레코드가 어디갔나요?” 삭제 전략에 따라 지원팀이 몇 분 만에 답할 수 있을지, 아니면 정직하게 ‘없어졌습니다’라고 말해야 할지가 결정됩니다.
소프트 삭제라면 보통 무슨 일이 일어났는지 확인하고 되돌릴 수 있습니다. 하드 삭제라면 지원은 백업에 의존해야 하고, 이는 느리거나 불완전하거나 단일 항목 복구가 불가능할 수 있습니다. 그래서 이 선택은 단순한 데이터베이스 세부사항이 아니라 제품의 ‘도움이 될 수 있는지’의 범위를 바꿉니다.
진짜 지원을 기대한다면 삭제 이벤트를 설명하는 몇 가지 필드를 추가하세요:
deleted_at (타임스탬프)deleted_by (사용자 id 또는 시스템)delete_reason (선택, 짧은 텍스트)deleted_from_ip 또는 deleted_from_device (선택)restored_at 및 restored_by (복원을 지원하면)전체 활동 로그가 없어도 이 정보로 지원은 누가, 언제 삭제했는지, 사고인지 자동 정리인지 설명할 수 있습니다.
하드 삭제는 임시 데이터에는 괜찮지만 사용자 관련 레코드에는 지원의 능력을 바꿉니다.
지원은 단일 레코드를 복원할 수 없습니다(별도의 휴지통을 구축하지 않았다면). 전체 백업 복원이 필요할 수 있고, 이는 다른 데이터에 영향을 줍니다. 또한 무슨 일이 있었는지 증명하기 어렵습니다. 복원 기능은 업무량을 바꿉니다. 사용자가 지정된 시간 내에 스스로 복원하면 티켓이 줄어듭니다. 복원이 지원의 수동 작업을 필요로 하면 티켓이 늘어날 수 있지만 처리가 빠르고 반복 가능해집니다.
‘잊힐 권리’는 보통 개인 데이터를 처리 중지하고 식별 가능한 형태로 남아 있지 않게 해야 한다는 의미입니다. 항상 모든 집계 값을 즉시 지워야 한다는 뜻은 아니지만, 더 이상 보관할 법적 근거가 없다면 식별 가능한 데이터를 ‘혹시 몰라서’ 보관해서는 안 됩니다.
이 점에서 소프트 삭제와 하드 삭제의 차이는 제품 선택을 넘어서 법적·운영적 문제입니다. 소프트 삭제(예: deleted_at 설정)는 앱에서 숨기는 것뿐입니다. 데이터베이스에 그대로 남아 있으며, 관리자나 내보내기, 검색 인덱스, 분석 테이블에서 여전히 조회될 수 있습니다. 많은 GDPR 삭제 요청에서 이것은 삭제(erase)가 아닙니다.
다음과 같은 경우 영구 삭제(또는 익명화)가 필요합니다:
팀이 자주 잊는 부분은 백업과 로그입니다. 암호화된 백업의 단일 행을 즉시 삭제할 수 없을 수도 있지만 규칙을 정할 수는 있습니다: 백업은 빨리 만료되게 하고, 복원 시에는 시스템을 다시 가동하기 전에 삭제 이벤트를 재적용하세요. 로그는 가능한 한 원시 개인 데이터를 저장하지 않게 하고 보존 한계를 명확히 하세요.
간단하고 실용적인 정책은 이중 단계 삭제입니다:
플랫폼이 소스 코드 내보내기나 데이터 내보내기를 지원하면 내보낸 파일도 데이터 저장소로 취급하세요: 어디에 저장되는지, 누가 접근 가능한지, 언제 삭제되는지 정의하세요.
소프트 삭제는 간단해 보입니다: deleted_at(또는 is_deleted) 플래그를 추가하고 행을 숨기면 됩니다. 숨겨진 비용은 이제 데이터를 읽는 모든 곳이 그 플래그를 기억해야 한다는 점입니다. 한 번만 놓쳐도 이상한 버그가 납니다: 합계에 삭제된 항목이 포함되거나 검색에 ‘유령’ 결과가 나타나거나 사용자가 사라진 걸 볼 수 있습니다.
UI와 UX 엣지 케이스가 빠르게 드러납니다. 팀이 프로젝트 "Roadmap"을 삭제한 뒤 같은 이름으로 새 프로젝트를 만들면, 이름에 고유 규칙이 있으면 생성이 실패할 수 있습니다. 목록에서는 삭제 항목을 숨기면서 전역 검색에서는 숨기지 않으면 사용자는 앱이 망가졌다고 생각할 수 있습니다.
소프트 삭제 필터는 다음과 같은 곳에서 자주 누락됩니다:
성능은 처음에는 보통 괜찮습니다. 대부분의 행이 활성 상태라면 deleted_at IS NULL 필터는 비용이 적습니다. 삭제된 행이 많아지면 적절한 인덱스 없이는 데이터베이스가 더 많은 행을 건너뛰어야 합니다. 비유하자면: 오래된 문서가 섞여 있는 서랍에서 현재 문서를 찾는 것과 비슷합니다.
별도의 “아카이브” 영역은 혼란을 줄여줍니다. 기본 뷰는 활성 레코드만 보여주고, 삭제 항목은 명확한 라벨과 시간 창이 있는 한 곳에 보관하세요. 빠르게 만든 내부 앱(예: Koder.ai로 만든 툴)에서는 이런 제품 결정이 어떤 복잡한 쿼리 트릭보다도 지원 티켓을 줄여줍니다.
소프트 삭제는 하나의 기능이 아니라 데이터 모델 선택입니다. 선택한 모델이 쿼리 규칙, 복원 동작, ‘삭제됨’이 제품에서 의미하는 바를 좌우합니다.
deleted_at + deleted_by가장 일반적인 패턴은 nullable 타임스탬프입니다. 삭제 시 deleted_at(그리고 종종 deleted_by에 사용자 id)을 설정합니다. deleted_at이 null인 레코드를 활성으로 봅니다.
이 방식은 간단한 복원에 적합합니다: 복원은 deleted_at과 deleted_by를 비우면 됩니다. 지원에도 간단한 감사 신호를 제공합니다.
타임스탬프 대신 status 필드에 active, archived, deleted 같은 명확한 상태를 둡니다. ‘아카이브’가 실제 제품 상태(예: 대부분 화면에서 숨기지만 과금에는 포함되는 경우)라면 유용합니다.
대신 규칙이 늘어납니다. 각 상태가 검색, 알림, 내보내기, 분석에서 무엇을 의미하는지 정의해야 합니다.
민감하거나 고가치 객체의 경우 삭제된 행을 별도 테이블로 옮기거나 append-only 이벤트 로그에 기록할 수 있습니다.
deleted_at, deleted_bystatus이 방식은 복원이 엄격하게 통제되어야 하거나 삭제된 데이터를 일상 쿼리와 섞지 않으려 할 때 자주 사용됩니다.
자식 레코드에도 의도된 규칙이 필요합니다. 워크스페이스가 삭제되면 프로젝트, 파일, 멤버십은 어떻게 될까요?
archived로 전환(삭제 아님)관계별로 하나의 규칙을 정하고 문서화하세요. 대부분의 ‘복원 실패’ 버그는 부모와 자식 레코드가 서로 다른 삭제 의미를 가질 때 발생합니다.
복원 버튼은 단순해 보이지만 권한, 오래된 데이터의 잘못된 부활, 사용자 혼란을 몰고 올 수 있습니다. 제품이 어떤 약속을 하는지 먼저 명확히 적으세요.
작고 엄격한 순서를 사용해 복원이 예측 가능하고 감사 가능하게 만드세요.
Koder.ai 같은 도구로 빠르게 앱을 만들면 이러한 검사들을 생성된 워크플로의 일부로 포함해 화면과 엔드포인트가 동일한 규칙을 따르도록 하세요.
소프트 삭제의 가장 큰 문제는 삭제 자체가 아니라 삭제된 상태를 잊는 모든 경로입니다. 많은 팀이 안전을 위해 소프트 삭제를 선택한 뒤, 검색 결과나 배지, 합계에 삭제된 항목이 표시되는 실수를 합니다. 사용자는 곧바로 “프로젝트 수 12인데 목록엔 11개만 보이네요” 같은 불만을 제기합니다.
다음으로 큰 문제는 접근 제어입니다. 사용자, 팀, 워크스페이스가 소프트 삭제되면 로그인, API 호출, 알림을 받아서는 안 됩니다. 로그인 검사에서 이메일로 조회해 행을 찾고 삭제 플래그를 검사하지 않으면 이 부분이 빠지기 쉽습니다.
나중에 지원 티켓을 만드는 함정을 몇 가지 나열하면:
복원 시 고유성 충돌은 특히 골칫거리입니다. 오래된 계정이 소프트 삭제된 상태에서 동일한 이메일로 새 계정을 만들면, 복원은 실패하거나 잘못된 정체성을 덮어쓸 수 있습니다. 사전에 규칙을 정하세요: 퍼지 전까지 재사용을 차단할지, 재사용 허용하되 복원 금지할지, 또는 복원 시 새 식별자로 복원할지 등.
흔한 시나리오: 지원 담당자가 소프트 삭제된 워크스페이스를 복원합니다. 워크스페이스는 복원되었지만 멤버는 삭제된 상태로 남아 있고, 통합이 이전 기록을 파트너 도구로 다시 동기화합니다. 사용자는 복원이 ‘반만 된’ 것으로 보고 새로운 문제가 생깁니다.
복원을 출시하기 전에 다음 동작을 명확히 하세요:
B2B SaaS 팀에 “워크스페이스 삭제” 버튼이 있습니다. 어느 금요일, 관리자 한 명이 정리 중 비활성처럼 보이는 40개 워크스페이스를 제거했습니다. 월요일 아침에 세 고객이 프로젝트가 사라졌다며 즉시 복원을 요청했습니다.
팀은 단순한 문제일 거라 생각했습니다. 아니었습니다.
첫 번째 문제: 완전히 삭제된 것은 복원할 수 없습니다. 워크스페이스 행이 하드 삭제되고 프로젝트, 파일, 멤버십이 연쇄 삭제되었다면 유일한 옵션은 백업입니다. 시간과 리스크가 필요하고 고객에게 난처한 설명을 해야 합니다.
두 번째 문제: 분석이 망가집니다. 대시보드는 deleted_at IS NULL로 활성 워크스페이스 수를 계산합니다. 사고성 삭제로 인해 차트가 갑자기 하락합니다. 더 나쁘게는 주간 리포트가 전주와 비교해 거짓된 이탈 급증을 기록합니다. 데이터가 사라진 건 아니지만 잘못된 곳에서 제외된 것입니다.
세 번째 문제: 영향받은 사용자 중 한 명이 개인정보 삭제 요청을 합니다. 순수한 소프트 삭제는 이를 만족하지 않습니다. 팀은 개인 필드(이름, 이메일, IP 로그)를 퍼지하면서도 청구 합계나 인보이스 번호 같은 비식별 집계는 유지하는 계획이 필요합니다.
네 번째 문제: 모두가 묻습니다. “누가 삭제 버튼을 눌렀나요?” 추적이 없다면 지원은 무슨 일이 있었는지 설명할 수 없습니다.
더 안전한 패턴은 삭제를 메타데이터가 있는 이벤트로 취급하는 것입니다:
deleted_by, deleted_at, 그리고 이유나 티켓 id를 기록이런 워크플로는 팀들이 Koder.ai 같은 플랫폼으로 빠르게 만들다가 나중에 삭제 정책도 기능만큼 설계가 필요하다는 것을 깨닫는 방식입니다.
소프트 삭제와 하드 삭제 중 선택은 선호가 아니라 레코드가 ‘사라진 이후’에 무엇을 보장해야 하는지에 관한 문제입니다. 쿼리를 작성하기 전에 다음 질문을 하세요.
결정에 대한 건전성 검증 방법은 현실적인 사고 하나를 골라 시나리오를 따라가는 것입니다. 예: 누군가 금요일 밤에 워크스페이스를 실수로 삭제한다. 월요일에 지원은 삭제 이벤트를 보고 안전하게 복원하고, 복원 시 복구되면 안 되는 관련 데이터는 되살리지 않아야 합니다. Koder.ai 같은 플랫폼에서 앱을 만든다면 이런 규칙을 초기에 정의해 생성된 백엔드와 UI가 한 정책을 따르게 하세요.
팀과 지원이 공유할 수 있는 간단한 정책을 문서화해 선택하세요. 문서화하지 않으면 정책은 흐트러지고 사용자는 일관성 없는 경험을 하게 됩니다.
먼저 다음과 같은 평범한 규칙 세트를 만드세요:
그다음 결코 섞이지 않는 두 경로를 만드세요: 실수 복구용 "관리자 복원" 경로와 실제 삭제용 "개인정보 퍼지" 경로. 복원 경로는 되돌릴 수 있고 로그가 남아야 합니다. 퍼지 경로는 최종적이어야 하며 사람을 식별할 수 있는 모든 관련 데이터를 제거하거나 익명화해야 합니다. 정책이 요구하면 백업이나 내보낸 파일도 포함하세요.
삭제된 데이터가 제품으로 다시 유출되지 않도록 가드레일을 추가하세요. 가장 쉬운 방법은 테스트에서 "삭제됨"을 일급 상태로 다루는 것입니다. 모든 새로운 쿼리, 목록 페이지, 검색, 내보내기, 분석 작업에 검토 포인트를 추가하세요. 규칙: 화면에 사용자 대상 데이터가 보이면 삭제 레코드에 대해 명시적 결정을 해야 합니다(숨김, 라벨 표기, 관리자 전용).
제품 초기 단계라면 스키마를 확정하기 전에 두 흐름을 프로토타입해 보세요. Koder.ai에서는 계획 모드에서 삭제 정책을 설계하고 기본 CRUD를 생성한 뒤 복원과 퍼지 시나리오를 빠르게 시도해 보고 스키마를 조정할 수 있습니다.