TIL - 20261003

juni·3일 전

TIL

목록 보기
469/472

1003 운영 자동화/AI 워크플로우 심화 (27/N): Data Retention, Archiving, PII Lifecycle과 안전한 Cleanup


✅ 1. 데이터는 계속 쌓이기만 하면 언젠가 운영 문제가 된다

서비스 초기에는 보통:

주문 1,000건

Audit Log 5,000건

Webhook 10,000건

Outbox 20,000건

정도라 관리가 어렵지 않다.

하지만 몇 년 지나면:

주문 수십만 건

Audit Log 수백만 건

Event 수천만 건

Export 파일 수천 개

AI Report / Artifact 누적

처럼 쌓일 수 있다.

문제는 단순히 저장 공간만 아니다.


✅ 2. 오래된 데이터는 여러 비용을 만든다

예:

DB Size 증가

Backup 시간 증가

Restore 시간 증가

Index 크기 증가

Query 성능 저하

운영 화면 검색 부담

개인정보 보유 범위 증가

유출 사고 시 피해 범위 증가

즉 데이터 보관 정책은:

DB 최적화

만의 문제가 아니라:

운영
+
보안
+
복구
+
개인정보

전체 문제다.


✅ 3. Retention이란?

Retention은:

데이터를 얼마 동안 보관할 것인가

를 정하는 정책이다.

예:

Application Log
30일

Outbox Event
90일

완료 Export File
30일

Audit Log
1년

주문 데이터
업무/법적 요구에 따라 별도

처럼 데이터 종류마다 다르게 정한다.


✅ 4. 모든 데이터를 같은 기간 보관하면 안 된다

예를 들어:

Debug Log

와:

주문 이력

은 중요도가 완전히 다르다.

또:

Notification Provider Raw Response

와:

주문 상태 Audit

도 다르다.

따라서 먼저 데이터를 분류해야 한다.


✅ 5. 데이터 분류 예시

현재 프로젝트 기준으로는 대략 다음처럼 나눌 수 있다.

1. Business Data

2. Operational Data

3. Audit Data

4. Event / Queue Data

5. Temporary Data

6. Artifact / File

7. Personal Data

✅ 6. Business Data

예:

주문

상담

상품

신청 상태

고객 신청 내역

서비스의 핵심 업무 데이터다.

이 데이터는 무작정 자동 삭제하면 안 된다.


✅ 7. Operational Data

예:

Job 상태

WorkflowRun

ScheduleRun

Worker 상태

Deployment 기록

Reconciliation 기록

운영 추적을 위해 필요하지만 영구 보관할 필요는 없을 수 있다.


✅ 8. Audit Data

예:

누가 주문 상태를 변경했는가

누가 설정을 변경했는가

누가 Feature Flag를 켰는가

누가 수동 Retry를 했는가

운영 책임성과 사고 추적에 중요하다.

일반 로그보다 더 오래 보관할 수 있다.


✅ 9. Event / Queue Data

예:

OutboxEvent

InboxEvent

WebhookEvent

Dead Letter

전달 안정성을 위한 데이터다.

영구 Event Store가 아니라면 일정 기간 후 정리할 수 있다.


✅ 10. Temporary Data

예:

임시 Export 파일

Preview 파일

중간 생성 Artifact

Cache Data

임시 업로드

대부분 짧은 Retention이 적합하다.


✅ 11. Artifact / File

예:

Excel Export

Markdown Report

첨부 파일

S3 Object

AI Output

DB Row와 실제 파일 수명주기를 같이 관리해야 한다.


✅ 12. Personal Data

예:

이름

전화번호

주소

생년월일

IP

상담 내용

주문 관련 개인정보

이 데이터는 단순히:

DB Size가 크니까 삭제

관점으로 보면 안 된다.

보관 필요성과 개인정보 최소화 원칙을 같이 봐야 한다.


✅ 13. PII란?

PII는 일반적으로 개인을 식별하거나 식별 가능하게 만드는 정보다.

예:

이름

휴대폰 번호

주소

이메일

IP Address

고객 ID와 결합된 행동 정보

등이 포함될 수 있다.


✅ 14. 중요한 것은 “PII인지 아닌지”만 나누는 것이 아니다

실무에서는:

왜 수집했는가?

어디에 사용되는가?

얼마나 오래 필요한가?

삭제 가능한가?

익명화 가능한가?

를 같이 본다.


✅ 15. Data Lifecycle

데이터도 하나의 생명주기를 가진다.

Collect
↓
Use
↓
Store
↓
Archive
↓
Anonymize / Delete

형태다.


✅ 16. Collect 단계

처음 수집할 때부터:

이 데이터가 정말 필요한가?

를 묻는다.

필요하지 않은 개인정보는 아예 받지 않는 것이 가장 안전하다.


✅ 17. Data Minimization

예:

분석 목적에:

고객 전화번호 전체

가 필요하지 않다면:

customerId

hash

통계 단위

정도로 대체할 수 있다.


✅ 18. 수집하지 않은 데이터는 삭제할 필요도 없다

보안에서 가장 강력한 전략 중 하나다.

수집 최소화

자체가 Retention 전략이다.


✅ 19. Store 단계에서도 위치를 줄인다

같은 전화번호가:

orders

audit_logs

outbox_events

application_logs

error_logs

analytics

에 모두 복제돼 있으면 위험하다.


✅ 20. 개인정보 중복 저장을 줄인다

예:

Outbox에는:

{
  "orderId": "order_123"
}

만 저장하고,

전화번호가 필요하면 Worker가 필요한 시점에 Order를 조회한다.


✅ 21. 이 방식의 장점

전화번호가 바뀌거나 삭제 대상이 됐을 때:

여러 Event Payload까지 전부 찾아 삭제

할 필요가 줄어든다.


✅ 22. Archive와 Delete는 다르다

Archive

주 사용 DB에서 제거

하지만 별도 보관

Delete

데이터 자체 제거

다.


✅ 23. Archive가 필요한 이유

예:

2년 전 주문

을 관리자 목록에서 매일 검색할 필요는 없지만:

과거 문의

운영 감사

분쟁 확인

때문에 일정 기간 보관이 필요할 수 있다.

이 경우 Active DB와 Archive를 분리할 수 있다.


✅ 24. 하지만 작은 프로젝트에서 Archive DB부터 만들 필요는 없다

처음에는:

같은 PostgreSQL

+
Archived 상태

+
기간 필터

만으로도 충분할 수 있다.


✅ 25. Soft Delete와 Archive도 다르다

Soft Delete:

삭제됐지만 DB에 Row 유지

Archive:

정상 데이터지만 일상 조회 대상에서 제외

이다.


✅ 26. 상태 예시

ACTIVE

ARCHIVED

SOFT_DELETED

를 구분할 수 있다.


✅ 27. Soft Delete를 영구 보관하면 진짜 삭제가 아니다

Soft Delete는:

실수 복구

운영 안전성

에는 좋다.

하지만:

삭제 후 수년간 그대로 보관

하면 실제 Retention 목적을 달성하지 못한다.


✅ 28. Soft Delete 후 Purge 정책

예:

ACTIVE
↓
SOFT_DELETED
↓
30일
↓
HARD_DELETE

형태를 둘 수 있다.


✅ 29. 단 Business Data는 Hard Delete가 항상 맞는 것은 아니다

예:

주문 자체를 완전히 삭제

하면 과거 매출/운영 기록이 깨질 수 있다.

이런 경우 개인정보만 제거하고 업무 기록은 유지하는 방식이 더 적합할 수 있다.


✅ 30. Anonymization

예:

기존:

이름
홍길동

전화번호
010-1234-5678

주소
서울시 ...

익명화 후:

이름
삭제

전화번호
삭제

주소
삭제

orderId
유지

상품
유지

주문일
유지

할 수 있다.


✅ 31. 업무 통계는 유지하면서 개인 식별정보만 제거

이렇게 하면:

월별 판매량

기종별 주문 수

통신사별 통계

는 유지할 수 있다.


✅ 32. 익명화와 가명처리는 다르다

예:

customer_123

처럼 다른 식별자로 대체했지만 별도 Mapping을 통해 다시 원래 고객을 찾을 수 있다면 완전한 익명화와는 다르다.


✅ 33. Hash도 무조건 익명화는 아니다

전화번호를 단순 Hash하면:

가능한 전화번호 범위가 제한적

이므로 공격자가 사전 계산으로 추정할 수도 있다.

따라서:

Hash = Anonymous

라고 단정하면 안 된다.


✅ 34. 분석용 식별자가 필요하다면 목적을 명확히 한다

예:

동일 고객 중복 여부

만 보고 싶다면:

salted hash

같은 방식이 유용할 수 있다.

하지만 이것 역시 보호해야 할 데이터로 보는 편이 안전하다.


✅ 35. Retention Policy 모델

예:

interface RetentionPolicy {
  dataType: string;

  retainForDays?: number;

  action:
    | 'ARCHIVE'
    | 'ANONYMIZE'
    | 'DELETE';

  gracePeriodDays?: number;
}

✅ 36. 하지만 정책을 코드 숫자로 여기저기 박지 않는다

나쁜 예:

if (createdAt < nowMinus90Days) {
  delete();
}

여러 곳에서 90일이 반복되면 나중에 정책 변경이 어렵다.


✅ 37. Retention Definition을 중앙화한다

예:

OUTBOX_EVENT
90일

EXPORT_FILE
30일

WORKFLOW_STEP_DETAIL
90일

처럼 한 곳에서 관리한다.


✅ 38. Retention Policy도 Configuration이다

0928과 연결된다.

정책 변경:

90일 → 30일

은 엄청난 영향을 줄 수 있다.

잘못 바꾸면 수많은 데이터가 삭제될 수 있다.


✅ 39. 따라서 Retention 변경도 Audit 대상

예:

Actor
admin

Policy
EXPORT_FILE

Before
90 days

After
30 days

Reason
Storage optimization

을 기록한다.


✅ 40. Retention Job을 단순 Cron Delete로 만들면 위험하다

나쁜 예:

DELETE FROM outbox_events
WHERE created_at < now() - interval '90 days';

한 번 실행으로 수백만 Row를 삭제할 수 있다.


✅ 41. Cleanup Job도 Workflow처럼 관리한다

예:

PLAN

↓

SCAN

↓

VALIDATE

↓

DELETE_BATCH

↓

VERIFY

↓

COMPLETE

형태로 만들 수 있다.


✅ 42. Dry Run이 먼저다

실제 삭제 전에:

대상 건수

가장 오래된 Row

가장 최근 대상 Row

예상 삭제량

을 확인한다.


✅ 43. Dry Run 결과 예시

Policy
OUTBOX_EVENT_90D

Candidates
12,451

Oldest
2026-01-01

Newest
2026-07-05

Estimated Size
340MB

정도다.


✅ 44. Safety Threshold

평소:

하루 삭제 1,000건

인데 갑자기:

250,000건

이 조회된다면 이상할 수 있다.


✅ 45. Max Delete Count

예:

expected
< 10,000

actual
250,000

이면:

MANUAL_REQUIRED

로 중단한다.


✅ 46. Percentage Threshold

전체 Row의:

50%

를 한 번에 삭제하는 것도 이상할 수 있다.

예:

deleteRatio > 10%

이면 수동 확인하도록 할 수 있다.


✅ 47. Absolute + Relative Threshold를 같이 쓸 수 있다

예:

deleteCount > 50,000
OR
deleteRatio > 20%

이면 중단한다.


✅ 48. Cleanup 대상 Query를 먼저 검증한다

예:

status = PUBLISHED

publishedAt < cutoff

처럼 삭제 대상 조건이 명확해야 한다.

단순:

createdAt < cutoff

만 보면 아직 처리되지 않은 Event를 삭제할 수 있다.


✅ 49. Outbox Cleanup 예시

삭제 가능:

PUBLISHED

90일 초과

삭제 금지:

PENDING

RETRY_PENDING

DEAD_LETTER

일 수 있다.


✅ 50. Inbox도 마찬가지다

삭제 전에:

PROCESSED

상태인지 확인한다.

처리 중/실패 상태는 보존한다.


✅ 51. Dead Letter는 더 오래 보관할 수 있다

DLQ는 장애 분석에 중요하다.

예:

일반 Event
90일

DLQ
180일

처럼 정책을 다르게 할 수 있다.


✅ 52. Workflow Retention도 상태별로 나눈다

예:

SUCCESS
90일

FAILED
180일

MANUAL_REQUIRED
삭제 금지

처럼 할 수 있다.


✅ 53. Running/Waiting Workflow는 Retention 대상이 아니다

RUNNING

WAITING

COMPENSATING

같은 Active 상태를 날짜만 보고 삭제하면 안 된다.


✅ 54. ScheduleRun도 Terminal State만 정리한다

예:

SUCCESS

FAILED

SKIPPED

같은 완료 상태만 대상으로 한다.


✅ 55. Cleanup Query는 Business State를 알아야 한다

이게 중요하다.

Retention은 단순 날짜 SQL이 아니라:

이 데이터가 정말 업무적으로 끝난 상태인가?

를 확인해야 한다.


✅ 56. Artifact Cleanup은 DB와 파일을 같이 봐야 한다

예:

Export DB Record

+

S3 Excel File

이 있다.

DB Row만 지우면 S3 파일이 남는 Orphan이 된다.


✅ 57. 반대로 S3부터 지우면?

파일 삭제 성공 후 DB Update 전에 Worker가 죽을 수 있다.

DB에는:

AVAILABLE

인데 실제 파일은 없다.


✅ 58. Artifact Cleanup도 Workflow로 처리한다

예:

EXPIRED

↓

DELETE_FILE

↓

MARK_DELETED

처럼 상태를 나눈다.


✅ 59. 삭제 결과가 불명확할 수 있다

S3 Delete 요청 후 Timeout.

실제로 파일이:

삭제됐는지

남아 있는지

모를 수 있다.


✅ 60. Reconciliation

이 경우:

Object 존재 확인

후:

없음
→ DELETED

있음
→ Retry

로 복구한다.


✅ 61. Artifact 상태 예시

AVAILABLE

EXPIRED

DELETE_PENDING

DELETING

DELETED

DELETE_FAILED

정도로 관리할 수 있다.


✅ 62. Export Download URL 만료와 실제 파일 삭제는 다르다

Presigned URL이 만료됐다고 S3 Object가 삭제되는 것은 아니다.

실제 Storage Cleanup 정책이 필요하다.


✅ 63. Lifecycle Rule을 활용할 수도 있다

S3 같은 Storage는 일정 기간 후 자동 삭제 정책을 제공할 수 있다.

이런 경우 Application Cron보다 Storage Lifecycle이 더 단순할 수 있다.


✅ 64. 하지만 DB Record와 상태 동기화 문제는 남는다

Storage에서는 파일이 삭제됐는데 DB에는:

AVAILABLE

일 수 있다.

따라서 DB 상태 Reconciliation이 필요할 수 있다.


✅ 65. Cloud Lifecycle + Application State

예:

S3 Lifecycle
30일 후 삭제

↓

주기적 Reconciler

↓

DB Artifact
DELETED

형태가 가능하다.


✅ 66. Application이 직접 삭제할지 Infrastructure Lifecycle을 쓸지 선택한다

단순 파일 Retention은 Lifecycle이 편할 수 있다.

하지만:

특정 고객 삭제 요청

즉시 삭제

복잡한 조건

이면 Application Workflow가 필요할 수 있다.


✅ 67. PII 삭제는 여러 저장소를 찾아야 할 수 있다

예:

PostgreSQL

S3

Search Index

Backup

Log

Analytics

에 데이터가 있을 수 있다.


✅ 68. 그래서 Data Inventory가 중요하다

개인정보 종류별로:

어디에 저장되는가?

왜 저장되는가?

얼마나 보관하는가?

삭제 방법은 무엇인가?

를 알아야 한다.


✅ 69. Data Inventory 예시

데이터위치목적보관
이름orders주문 처리정책에 따름
전화번호orders고객 연락정책에 따름
전화번호logs없어야 함즉시 개선
IPlead유입 분석/중복별도 정책
ExportS3관리자 다운로드30일

이런 문서가 있으면 좋다.


✅ 70. 로그는 개인정보가 새어들기 쉬운 곳이다

예:

logger.info(req.body);

를 해두면 주문 Form 전체가 로그에 들어갈 수 있다.


✅ 71. 그래서 Retention보다 먼저 Log Minimization

로그에 처음부터:

전화번호

주소

이름

토큰

을 넣지 않는 것이 중요하다.


✅ 72. 이미 로그에 개인정보가 있다면?

Retention을 짧게 하고 앞으로 Logging 정책을 수정한다.

단순히:

30일 후 없어지니까 괜찮다

가 아니라 생성 자체를 줄인다.


✅ 73. Structured Log에는 ID 중심

예:

{
  "event": "order.created",
  "orderId": "order_123",
  "correlationId": "cor_1"
}

정도로 남긴다.


✅ 74. 고객 이름 대신 orderId로 추적

필요할 때 관리자 권한으로 실제 Order를 조회한다.

운영 로그가 개인정보 DB 역할을 하지 않게 한다.


✅ 75. IP Address도 무조건 로그에 오래 두지 않는다

현재 유입/중복 분석에 IP를 사용하더라도:

분석 목적

보관 기간

접근 권한

을 따로 정의하는 것이 좋다.


✅ 76. PII Lifecycle 상태

예:

ACTIVE

RETENTION_HOLD

ANONYMIZE_PENDING

ANONYMIZED

DELETE_PENDING

DELETED

같은 상태를 사용할 수도 있다.


✅ 77. Retention Hold

일반 정책상 삭제 시점이 됐더라도 특정 사유로 일시 보존해야 하는 상황이 있을 수 있다.

이때:

retentionHold = true

인 Record는 Cleanup에서 제외한다.


✅ 78. Hold를 너무 쉽게 만들면 안 된다

모든 Record가:

혹시 필요할 수도 있으니까 Hold

가 되면 Retention 정책이 의미 없어진다.


✅ 79. Hold에는 이유와 만료 시점을 둔다

예:

reason

createdBy

createdAt

expiresAt

을 기록한다.


✅ 80. Hold 자체도 Audit한다

누가:

삭제 예정 데이터를 왜 보존했는가?

추적할 수 있어야 한다.


✅ 81. Deletion Request와 Retention Policy 충돌

예:

고객 삭제 요청

이 들어와도 업무/법적 보관 의무 때문에 모든 Record를 즉시 완전히 지울 수 없는 경우가 있을 수 있다.


✅ 82. 그래서 삭제 요청 = DB Row 전부 DELETE로 단순화하면 안 된다

업무 요구에 따라:

식별 정보 제거

업무 기록 유지

특정 필드 마스킹

일부 데이터 삭제

형태가 될 수 있다.


✅ 83. 여기서는 법률 판단을 코드에 하드코딩하지 않는다

법적 보존 기간이나 삭제 요구는 실제 회사 정책/법무 기준으로 결정해야 한다.

개발자는:

정책을 구현할 수 있는 구조

를 만든다.


✅ 84. Policy-driven Deletion

예:

OrderRetentionPolicy

가:

ANONYMIZE

라고 결정하면 코드가 그 Action을 수행한다.


✅ 85. 즉 Retention Engine이 법적 결정을 스스로 하지 않는다

Input:

policy

를 실행하는 역할만 한다.


✅ 86. Anonymization Workflow

예:

1. 대상 조회

2. Hold 여부 확인

3. 관련 Active 업무 확인

4. 개인정보 제거

5. Secondary Store 정리

6. Verify

7. Audit

형태다.


✅ 87. 익명화 전에 Active Workflow를 확인한다

예:

주문 상담 진행 중

인데 전화번호를 먼저 삭제하면 업무가 깨질 수 있다.


✅ 88. Deletion Precondition

예:

Order final status

No active workflow

No retention hold

같은 조건을 둔다.


✅ 89. Final Status

예:

COMPLETED

CANCELLED

REJECTED

처럼 업무가 끝난 상태인지 확인한다.

정확한 상태는 프로젝트 정책에 맞춘다.


✅ 90. 익명화도 Transaction이 필요할 수 있다

같은 DB 안에서:

customerName = null

phone = null

address = null

anonymizedAt = now

를 하나의 Transaction으로 처리한다.


✅ 91. 일부만 익명화되면 안 된다

예:

이름 삭제 성공

전화번호 업데이트 실패

같은 중간 상태를 막는다.


✅ 92. 관련 Audit Log의 개인정보

Audit Log에:

before.phone

after.phone

전체 값을 넣어뒀다면 주문 Row만 익명화해도 개인정보가 남는다.


✅ 93. Audit 설계부터 PII를 최소화해야 한다

예:

field
phone

before
[REDACTED]

after
[REDACTED]

또는:

changed=true

정도만 남기는 방법이 있다.


✅ 94. Audit와 PII Retention의 균형

Audit 목적 때문에 모든 개인정보 원문을 저장할 필요는 없다.

무엇이 바뀌었는가

와:

실제 값 전체

는 다르다.


✅ 95. Webhook Payload도 위험하다

외부 Provider Webhook 전체 JSON을 장기간 저장하면 개인정보가 들어 있을 수 있다.


✅ 96. Raw Payload Retention을 짧게

필요하다면:

Raw Payload
7일

Normalized Event
90일

처럼 구분할 수 있다.


✅ 97. Debugging이 끝난 뒤 Raw Data를 계속 보관할 필요는 없다

운영 편의를 위해 영구 보관하지 않는다.


✅ 98. Event Payload Normalization

원본 Webhook:

{
  "customerName": "...",
  "phone": "...",
  "messageId": "..."
}

에서 내부 Event에는:

{
  "providerMessageId": "...",
  "status": "DELIVERED"
}

정도만 저장한다.


✅ 99. Raw → Normalized 분리

External Raw Event

↓

Validation

↓

Normalized Internal Event

로 변환한다.

내부 시스템 전체에 외부 Raw Payload를 퍼뜨리지 않는다.


✅ 100. Backup Retention

Backup도 데이터 복사본이다.

Production DB에서 삭제했다고 Backup까지 즉시 사라지는 것은 아니다.


✅ 101. Backup 정책도 Data Lifecycle의 일부

예:

Daily Backup
30일

Monthly Backup
장기 보관

등 별도 정책이 있을 수 있다.

정확한 기간은 회사 정책에 맞춘다.


✅ 102. 삭제 요청과 Backup은 별도 고려가 필요하다

Backup은 즉시 Row 단위 삭제가 어려울 수 있다.

따라서:

백업 보관 기간

Restore 후 데이터 처리 절차

를 정책으로 갖는 것이 중요하다.


✅ 103. Restore 후 삭제된 개인정보가 다시 나타날 수 있다

예:

10/01 개인정보 삭제

10/03 장애

09/30 Backup 복구

하면 삭제 전 데이터가 다시 들어올 수 있다.


✅ 104. Restore Reconciliation

Restore 후:

삭제/익명화 이력

을 다시 적용할 수 있는 구조를 고려할 수 있다.


✅ 105. Tombstone / Deletion Ledger

예:

customerId

deletedAt

deletionType

같은 최소 삭제 이력을 별도 보관하는 방식이다.


✅ 106. Tombstone은 원본 개인정보를 다시 저장하면 안 된다

목적은:

이 대상은 다시 활성화되면 안 된다

를 표시하는 것이다.


✅ 107. Restore 후 Tombstone Reapply

Backup Restore

↓

Deletion Ledger 확인

↓

해당 데이터 재익명화

같은 흐름을 만들 수 있다.


✅ 108. 하지만 현재 규모에서는 너무 복잡하게 시작하지 않는다

우선:

Backup Retention 정책 문서화

삭제 이력 Audit

Restore Runbook

정도부터 시작해도 충분하다.


✅ 109. Cleanup Job은 반드시 Idempotent해야 한다

예:

already deleted

된 Artifact에 다시 Delete 요청이 와도:

성공 취급

할 수 있다.


✅ 110. 익명화도 재실행 안전하게

예:

name = null

phone = null

상태에서 다시 Anonymize해도 문제가 없어야 한다.


✅ 111. Cleanup Run 상태

예:

PLANNED

SCANNING

READY

RUNNING

VERIFYING

SUCCESS

FAILED

MANUAL_REQUIRED

정도로 둘 수 있다.


✅ 112. 대상 목록을 Snapshot으로 잡을 수 있다

예:

CleanupRun
run_123

Candidate IDs

를 기록한다.

다만 대량 ID를 JSON에 다 넣지 않는다.


✅ 113. Candidate Table

필요하면:

cleanup_run_items

를 두고:

targetType

targetId

status

를 관리할 수 있다.


✅ 114. 너무 작은 Cleanup에는 별도 Item Table이 과할 수 있다

예:

매일 Outbox 2,000건 삭제

정도라면 Cursor 기반 Batch로 충분할 수 있다.


✅ 115. Cursor 기반 Cleanup

예:

id > lastId
AND createdAt < cutoff

를 사용해 Batch를 순서대로 처리한다.


✅ 116. Offset Pagination은 대량 삭제에서 불안정할 수 있다

삭제하면서 Offset이 계속 바뀌기 때문이다.

Keyset/Cursor 방식이 더 안정적이다.


✅ 117. Batch Size

예:

500

1,000

5,000

중 DB 부하를 보며 결정한다.

처음에는 작게 시작한다.


✅ 118. Batch 사이 Delay

Production DB 부하가 민감하다면:

batch 처리
↓
200ms sleep
↓
다음 batch

같은 방식으로 속도를 조절할 수 있다.


✅ 119. Cleanup Rate Limit

예:

maxRowsPerMinute

를 둘 수 있다.


✅ 120. DB 상태에 따라 Pause

예:

DB CPU 상승

Latency 증가

Connection Pool 포화

가 발생하면 Cleanup을 중단한다.

초기에는 자동보다 운영자 수동 Pause도 충분하다.


✅ 121. Cleanup Job Priority

LOW

로 두는 것이 일반적이다.

고객 요청 처리보다 우선해서는 안 된다.


✅ 122. Cleanup 시간대

트래픽이 상대적으로 낮은 시간에 실행할 수 있다.

단 모든 Batch Job이 같은 새벽 3시에 몰리지 않도록 0929의 Scheduler 원칙을 적용한다.


✅ 123. Cleanup Schedule 예시

02:10
Outbox Cleanup

02:30
Workflow Cleanup

03:00
Artifact Cleanup

처럼 분산한다.


✅ 124. Cleanup Retry

DB Timeout 같은 일시 오류는 Retry 가능하다.

하지만:

삭제 대상 500,000건
Safety Threshold 초과

는 Retry할 문제가 아니다.


✅ 125. Error Classification

Retryable:

DB_TIMEOUT

S3_TEMPORARY_ERROR

NETWORK_ERROR

Non-Retryable / Manual:

SAFETY_THRESHOLD_EXCEEDED

POLICY_MISSING

RETENTION_HOLD

UNKNOWN_DATA_STATE

등이다.


✅ 126. Cleanup DLQ는 일반 Queue DLQ와 조금 다를 수 있다

삭제 실패 자체가 고객 기능 장애는 아니지만:

계속 실패하면 오래된 데이터가 누적

된다.

운영자에게 정기적으로 노출한다.


✅ 127. Cleanup Drift

예:

정책상 30일 보관

인데 실제로:

90일 된 파일이 5,000개 남아 있음

이면 Drift다.


✅ 128. Retention Compliance Metric

예:

expired_records

expired_artifacts

oldest_expired_age

를 볼 수 있다.


✅ 129. Cleanup SLO

예:

만료 데이터의 99%가
만료 후 24시간 이내 정리

같은 내부 운영 목표를 둘 수 있다.


✅ 130. 반드시 즉시 삭제가 필요한 것은 아니다

예:

Retention 30일

Cleanup Daily

이면 실제 삭제는 최대 31일 시점일 수 있다.

이런 Grace를 정책에 반영한다.


✅ 131. Grace Period

예:

retainFor
30일

grace
24시간

처럼 관리할 수 있다.


✅ 132. 즉시 삭제 Workflow는 별도로

자동 Retention Cleanup과:

특정 데이터 즉시 삭제 요청

은 다른 Use Case다.


✅ 133. Deletion Request 모델

예:

interface DeletionRequest {
  id: string;

  subjectId: string;

  status:
    | 'PENDING'
    | 'REVIEWING'
    | 'APPROVED'
    | 'RUNNING'
    | 'COMPLETED'
    | 'REJECTED';

  reason: string;
}

✅ 134. 하지만 실제 사용자 삭제 요청 기능이 필요할 때만 구현한다

현재 요구가 없다면 구조만 이해하고 과도하게 만들지 않는다.


✅ 135. Deletion Workflow에서 먼저 Inventory를 조회

예:

Orders

Consults

Files

Lead Data

Webhook Raw

Logs

어디에 대상 데이터가 있는지 확인한다.


✅ 136. 삭제 대상과 보존 대상을 구분

예:

삭제
전화번호
주소

보존
orderId
상품
가격
상태 이력

처럼 Policy가 정할 수 있다.


✅ 137. Anonymization Marker

예:

anonymizedAt

을 남기면 이미 처리된 Row를 다시 판단하기 쉽다.


✅ 138. 원래 이름을 Audit에 남기면 안 된다

예:

anonymized customer 홍길동

처럼 로그에 원본 PII를 다시 기록하면 삭제 의미가 줄어든다.


✅ 139. Audit에는 대상 ID만

예:

customerId

orderId

fieldsRedactedCount

policyVersion

정도로 남긴다.


✅ 140. Policy Version도 기록한다

삭제 시점에 어떤 Retention Policy를 적용했는지 알아야 한다.

예:

policyVersion
v7

이다.


✅ 141. Retention Rule이 나중에 바뀔 수 있기 때문이다

예:

과거 정책
90일

현재 정책
30일

일 수 있다.


✅ 142. 이미 90일 보관한 데이터를 정책 변경 후 즉시 다 지울지?

이것도 업무 결정이다.

코드는:

새 정책이 적용되는 기준 시점

을 지원할 수 있어야 한다.


✅ 143. Policy Effective Date

예:

effectiveFrom
2026-10-01

을 둘 수 있다.


✅ 144. Policy Simulation

설정 변경 전에:

새 Retention 30일을 적용하면
삭제 대상 128,421건

같이 미리 계산하면 좋다.


✅ 145. 이 기능이 실수 방지에 매우 유용하다

정책 변경 저장 버튼을 누르기 전에:

예상 영향

을 보여준다.


✅ 146. Retention Policy Change Gate

예:

삭제 대상 < 10,000
→ Normal

삭제 대상 10,000~100,000
→ Warning

100,000 이상
→ Approval

처럼 운영할 수 있다.

정확한 숫자는 실제 규모에 맞춘다.


✅ 147. 데이터 Cleanup도 Release와 비슷하다

Plan

Validate

Apply

Verify

Monitor

한다.


✅ 148. Verification

Cleanup 후:

만료 Row가 남았는가?

Active Row가 실수로 삭제됐는가?

File Orphan이 있는가?

DB Record와 Storage가 일치하는가?

를 확인한다.


✅ 149. Negative Verification도 필요하다

지워야 할 것이 지워졌다

만 보는 게 아니라:

지우면 안 되는 것이 살아 있다

도 확인한다.


✅ 150. Canary Cleanup

대량 Cleanup은 처음부터 전체 적용하지 않는다.

예:

100건

↓

1,000건

↓

10,000건

으로 확대한다.


✅ 151. 특정 기간/Partition부터 테스트

예:

가장 오래된 1일치

만 먼저 정리한다.

결과가 정상인지 확인한다.


✅ 152. Delete Preview Sample

실제 삭제 전:

랜덤 20건

가장 최신 대상 20건

가장 오래된 대상 20건

을 Sample로 확인할 수도 있다.


✅ 153. 하지만 운영자가 개인정보 원문을 Preview할 필요는 없다

ID와 Metadata 위주로 보여준다.


✅ 154. Partitioning을 고려할 시점

Event/Log 데이터가 매우 커지면 날짜별 Partition이 유용할 수 있다.

예:

outbox_2026_09

outbox_2026_10

처럼 생각할 수 있다.


✅ 155. Partition Retention의 장점

오래된 데이터를:

DELETE millions rows

하는 대신:

old partition drop

방식으로 빠르게 정리할 수 있다.


✅ 156. 하지만 현재 규모에서는 서둘러 Partitioning할 필요 없다

먼저:

Index

Retention

Batch Cleanup

으로 충분한지 본다.


✅ 157. Table Size Monitoring

예:

orders

audit_logs

outbox_events

workflow_step_runs

Table Row 수와 Disk Size를 주기적으로 본다.


✅ 158. Growth Rate

단순 현재 Size보다:

하루에 얼마나 증가하는가?

가 중요하다.

예:

Outbox
+100k rows/day

이면 Retention 없이는 빠르게 커진다.


✅ 159. Capacity Forecast

예:

현재
10GB

월 증가
3GB

6개월 후
28GB

처럼 대략 예측할 수 있다.


✅ 160. 모든 데이터를 삭제하는 것만이 최적화는 아니다

Query에서:

최근 3개월

만 기본 조회하도록 하는 것도 중요하다.


✅ 161. 관리자 검색 기본 기간

예:

기본
최근 30일

필요 시
기간 확대

로 하면 오래된 데이터가 있어도 일반 Query 부담이 줄어든다.


✅ 162. Archive 조회를 별도 UI로 분리할 수 있다

예:

주문 관리

과거 주문 조회

를 분리한다.


✅ 163. 오래된 데이터를 Cold Path로 보낸다

자주 조회하는 데이터:

Hot

드물게 조회:

Cold

로 볼 수 있다.


✅ 164. Hot / Cold Storage

현재 규모에서는 굳이 별도 DB가 아니라:

Active Table

Archive Table

수준도 가능하다.


✅ 165. Archive Table Migration의 단점

FK 관리

Query 분기

복구

데이터 이동

복잡도가 늘어난다.

실제 성능 문제가 있을 때 도입한다.


✅ 166. 우선은 Index + 기간 Filter + Cleanup

가장 현실적인 순서다.


✅ 167. Audit Log는 Archive 후보지만 삭제에는 신중

Audit은 사고 조사와 운영 추적에 중요하다.

로그와 같은 짧은 Retention을 적용하지 않는다.


✅ 168. Audit에 필요한 최소 정보만 저장하면 오래 보관 부담이 줄어든다

예:

actorId

action

targetType

targetId

timestamp

changedFields

정도다.


✅ 169. before/after 전체 Object 저장은 피한다

특히 Order 전체 JSON을 Audit에 저장하면:

PII 복제

Storage 증가

Schema 변화

문제가 생긴다.


✅ 170. Field Diff 중심 Audit

예:

{
  "changedFields": [
    "status",
    "planId"
  ]
}

또는 민감하지 않은 값만 저장한다.


✅ 171. AI Workflow Artifact Retention

AI 자동화에서는:

Prompt

Context

LLM Output

Tool Log

Report

가 쌓일 수 있다.


✅ 172. 모든 Prompt를 영구 저장할 필요는 없다

특히 Prompt에 코드, 업무정보, 개인정보가 섞일 수 있다.


✅ 173. AI Run Metadata와 Artifact를 구분

Metadata:

runId

model

promptVersion

status

duration

는 오래 보관 가능하다.

Raw Prompt/Context:

짧은 Retention

을 고려할 수 있다.


✅ 174. AI Tool Log도 최소화

예:

명령어

Exit Code

Duration

Artifact ID

정도는 남기되:

.env 전체

Secret Output

전체 DB Row

는 기록하지 않는다.


✅ 175. Local LLM Work Report의 경우

최종 Markdown은 성과 기록이라 오래 보관할 가치가 있다.

반면:

중간 Prompt

임시 Context

Raw Model Response

는 필요 없으면 짧게 보관하거나 저장하지 않아도 된다.


✅ 176. Workflow Step Output Cleanup

Run 전체는 1년 보관하더라도 Step의 큰 Output은 30일 뒤 삭제할 수 있다.


✅ 177. Metadata Retention과 Payload Retention을 분리

예:

WorkflowRun Metadata
365일

Step Raw Output
30일

같이 운영할 수 있다.


✅ 178. 이렇게 하면 추적성은 남기면서 Storage를 줄일 수 있다

Run이 있었다

언제 성공했다

어떤 Error가 났다

는 남는다.

큰 Payload만 사라진다.


✅ 179. Error Stack도 영구 보관 필요 없음

오래된 Stack Trace는 코드가 이미 바뀌어 활용 가치가 낮을 수 있다.


✅ 180. Incident 관련 데이터는 더 오래 보존할 수 있다

예:

Incident와 연결된 로그/Run

은 일반 Retention보다 길게 유지할 수 있다.


✅ 181. Legal/Incident Hold 개념

중요 Incident 조사 중에는 관련 데이터를 Cleanup에서 제외한다.


✅ 182. Hold 대상

예:

correlationId

workflowRunId

incidentId

orderId

를 기준으로 연결된 데이터를 보존할 수 있다.


✅ 183. 하지만 관련 데이터 전체를 영구 보존하지 않는다

Incident 종료 후 Hold 해제 시점을 정한다.


✅ 184. Cleanup Job도 Audit Log가 필요하다

예:

Cleanup Run
cleanup_123

Policy
OUTBOX_90D

Candidates
12,451

Deleted
12,449

Failed
2

정도를 기록한다.


✅ 185. 개별 Row마다 Audit를 만들 필요는 없다

수십만 건 Cleanup에서 Row마다 Audit를 쓰면:

삭제한 만큼 Audit가 다시 생김

이라는 문제가 생긴다.


✅ 186. Aggregate Summary Audit

Cleanup Run 단위로 요약 기록하는 편이 낫다.


✅ 187. PII 삭제는 개별 처리 기록이 필요할 수 있다

개인정보 관련 Workflow는 대상별 상태를 남길 가치가 있다.

단 원문 PII는 남기지 않는다.


✅ 188. Cleanup 실패 항목만 상세 기록

정상 삭제 10만 건은 Summary.

실패 5건만:

targetId

errorCode

를 저장한다.


✅ 189. Cleanup Job Error Code

예:

RETENTION_POLICY_MISSING

RETENTION_HOLD_ACTIVE

DELETE_THRESHOLD_EXCEEDED

ARTIFACT_DELETE_FAILED

ANONYMIZATION_FAILED

CLEANUP_VERIFICATION_FAILED

등이다.


✅ 190. Data Retention Dashboard

예:

Expired Outbox
0

Expired Workflow
120

Expired Artifacts
42

Deletion Failures
2

Oldest Expired
3 days

정도로 볼 수 있다.


✅ 191. Storage Dashboard

예:

PostgreSQL
32GB

S3 Export
4GB

Logs
8GB

Artifacts
2GB

등을 확인한다.


✅ 192. Growth Trend가 중요하다

이번 달 +20%

처럼 급증하면 새 기능이 데이터를 과도하게 만들고 있을 수 있다.


✅ 193. Retention 변경 전 Storage 절감량도 계산 가능

예:

Workflow Step
180일 → 90일

Estimated Reduction
3.4GB

같은 Preview를 만들 수 있다.


✅ 194. 단 Storage 비용 때문에 개인정보를 오래 두면 안 된다

보관 여부의 우선 기준은:

업무/정책상 필요성

이다.

Storage 최적화는 그다음이다.


✅ 195. 반대로 Storage가 싸다고 영구 보관하는 것도 좋지 않다

S3 싸니까 전부 영구 저장

은 보안과 관리 측면에서 좋지 않다.


✅ 196. “나중에 쓸 수도 있음”은 보관 목적이 약하다

가능하면 데이터마다 명확한 목적을 둔다.


✅ 197. Data Owner

데이터 종류마다 담당 영역을 정할 수 있다.

예:

Orders
Business

Outbox
Backend

Logs
Infra

AI Artifacts
Automation

1인 개발이어도 목적이 명확해진다.


✅ 198. Retention Registry

예:

interface RetentionDefinition {
  dataType: string;
  owner: string;
  retainDays?: number;
  action: string;
  criticality: string;
}

형태로 중앙 관리할 수 있다.


✅ 199. Policy Registry 예시

OUTBOX_PUBLISHED
90d
DELETE

WORKFLOW_SUCCESS
180d
ARCHIVE

EXPORT_FILE
30d
DELETE

AI_RAW_CONTEXT
7d
DELETE

정도다.

실제 기간은 업무 기준으로 정한다.


✅ 200. 정책 없는 데이터도 감지한다

새 Table을 만들었는데:

Retention Policy 없음

이면 Review 대상으로 띄울 수 있다.


✅ 201. Schema Review Checklist에 추가

새 Persistent Table 생성 시:

이 데이터는 언제 삭제되는가?

를 묻는다.


✅ 202. 이것이 중요하다

많은 시스템이:

데이터 생성 기능

은 열심히 만들지만:

데이터 종료 전략

은 만들지 않는다.


✅ 203. Create 기능을 만들 때 Delete/Archive까지 생각한다

예:

ExportJob 생성

기능을 만들 때 동시에:

Export Artifact Retention

을 정의한다.


✅ 204. Event를 만들 때도 Retention을 정의

Outbox Event

가 영구 Event Store가 아니라면:

PUBLISHED 이후 얼마나 유지?

를 정한다.


✅ 205. AI Run을 만들 때도 마찬가지다

Prompt/Output을 언제 정리할까?

를 처음부터 생각한다.


✅ 206. Cleanup Failure Injection

0925와 연결해서 테스트할 수 있다.

예:

삭제 중 Worker Crash

S3 Timeout

DB Batch 실패

Safety Threshold 초과

이다.


✅ 207. 기대 결과

이미 완료한 Batch 재삭제 안전

Resume 가능

Active Data 삭제 없음

Failure 기록

이어야 한다.


✅ 208. 삭제 중 Worker Crash

예:

Batch 1
SUCCESS

Batch 2
절반 처리 후 Crash

다시 실행해도 이미 삭제된 Row 때문에 실패하지 않아야 한다.


✅ 209. Cursor 계산에 주의

삭제된 Row 기준 Cursor가 꼬이지 않게:

고정 cutoff

Primary Key cursor

를 사용한다.


✅ 210. Cleanup Run 시작 시 Cutoff를 고정한다

예:

cutoffAt
2026-07-01T00:00:00Z

를 Run 시작 시 저장한다.


✅ 211. 실행 중 시간이 지나도 Cutoff를 바꾸지 않는다

그렇지 않으면 Job 실행 중 대상 범위가 계속 늘어난다.


✅ 212. Deterministic Cleanup Run

Run 입력:

policyVersion

cutoffAt

targetType

을 고정한다.

같은 Run을 Resume해도 대상 기준이 동일하다.


✅ 213. New Run에서만 새 Cutoff 계산

다음날 Cleanup Run이 새 범위를 처리한다.


✅ 214. Archive Workflow

삭제 대신 Archive라면:

Candidate Scan

↓

Copy / Move

↓

Verify Archive

↓

Remove Active

↓

Complete

형태가 된다.


✅ 215. Copy 완료 전에 Active를 삭제하지 않는다

먼저:

Archive 존재 확인

후 원본을 정리한다.


✅ 216. Move도 Dual Write 문제와 비슷하다

Archive Insert 성공

Active Delete 실패

하면 중복 데이터가 생길 수 있다.


✅ 217. Archive ID / Source ID를 이용해 중복 방지

예:

archive.sourceId UNIQUE

를 둔다.


✅ 218. Archive Reconciliation

Desired:

Archived record exists
Active record absent

Actual:

둘 다 존재

이면 Cleanup을 이어간다.


✅ 219. Archive Query 성능

Archive Data를 일상 조회에서 제외하면 Active Index Size가 줄어들 수 있다.

하지만 실제 필요성이 생긴 뒤 도입한다.


✅ 220. 테이블 분리 없이 ArchivedAt만으로 시작 가능

예:

archivedAt IS NULL

을 기본 Query에 넣는다.


✅ 221. 다만 Row 자체가 너무 많으면 결국 분리 필요할 수 있다

이 시점에 Partition/Archive Table을 검토한다.


✅ 222. 현재 프로젝트에서 먼저 적용할 데이터

우선순위는 다음이 현실적이다.

1. Export 파일

2. Outbox/Inbox 완료 기록

3. Workflow/Job 상세 로그

4. Raw Webhook Payload

5. AI 임시 Artifact

이다.


✅ 223. 주문 원본 데이터는 가장 신중하게

핵심 Business Data이므로 회사의 실제 보관 정책과 운영 요구를 먼저 확인해야 한다.


✅ 224. 개인정보 Fields부터 Inventory한다

예:

orders.customerName

orders.phone

orders.birthDate

orders.address

leads.ip

consults.message

등을 정리한다.


✅ 225. “어디에 PII가 있는지 모르겠다”가 가장 위험한 상태다

삭제 요청이 와도 완전하게 처리할 수 없기 때문이다.


✅ 226. PII Scan을 AI에게 도울 수 있다

AI가 코드/Schema를 분석해:

phone

email

address

birth

name

ip

관련 Field 후보를 찾아줄 수 있다.


✅ 227. 단 AI 결과를 그대로 개인정보 목록으로 확정하지 않는다

Field 이름만 보고는 의미가 애매할 수 있다.

사람이 검토한다.


✅ 228. AI Data Inventory Report

예:

Field
orders.customerPhone

Usage
Order processing

References
5 files

Logs
No direct usage detected

Retention
Not defined

형태로 만들 수 있다.


✅ 229. AI가 도움될 수 있는 영역

Retention 없는 Table 찾기

PII 후보 Field 탐색

Log에 PII 출력 후보 찾기

Orphan Artifact 가능성 찾기

Cleanup 대상 Query 검토

Destructive SQL Risk Review

등이다.


✅ 230. AI에게 삭제 실행 권한은 주지 않는다

특히 Production 데이터 삭제는:

Review

Dry Run

Approval

Execute

과정을 거친다.


✅ 231. Destructive Action Policy

예:

READ
자동 가능

DRY_RUN
자동 가능

DELETE_PREVIEW
자동 가능

DELETE_EXECUTE
Approval Required

정도로 나눈다.


✅ 232. AI Cleanup Assistant

예:

정책
Export 30일

↓

AI
대상/위험/예상 용량 정리

↓

System Dry Run

↓

Human Approval

↓

Cleanup Worker

형태가 안전하다.


✅ 233. Cleanup Job과 Feature Flag

새 Cleanup 정책을:

cleanup_export_v2_enabled

로 일부 범위에만 적용할 수도 있다.


✅ 234. 하지만 Retention 기능에 Flag를 너무 많이 만들지 않는다

정책이 정착하면 Legacy Flag를 정리한다.


✅ 235. Cleanup Kill Switch

문제가 생기면:

cleanup_enabled=false

로 모든 자동 Cleanup을 즉시 중지할 수 있으면 좋다.


✅ 236. 개별 Policy Pause도 유용

예:

Outbox Cleanup
ON

Export Cleanup
OFF

처럼 특정 Cleanup만 멈춘다.


✅ 237. Cleanup 상태와 Alert

예:

CLEANUP_FAILED
3회 연속

Warning.

Expired PII
정책 기준 초과

Criticality를 높일 수 있다.


✅ 238. 단 모든 Cleanup 실패를 새벽에 Critical Alert로 보낼 필요는 없다

업무 중요도에 따라 다르게 한다.


✅ 239. Cleanup Run History

예:

Policy대상삭제실패결과
Outbox 90d12,45112,4510성공
Export 30d1201182부분 성공
Workflow 180d5,21000Threshold 중단

이런 화면이 있으면 좋다.


✅ 240. Partial Success

Cleanup에서도 일부 File 삭제만 실패할 수 있다.

PARTIAL_SUCCESS

를 지원할 수 있다.


✅ 241. 실패 항목만 Retry

이미 삭제한 118개 파일을 다시 처음부터 처리할 필요 없다.


✅ 242. Cleanup Reconciliation

예:

DB:

Artifact DELETED

Storage:

Object Exists

이면 불일치다.

반대로:

DB:

AVAILABLE

Storage:

Missing

도 불일치다.


✅ 243. Desired vs Actual

Expired Artifact

Desired
DB DELETED
Storage missing

형태다.


✅ 244. Orphan Detection

Storage에 파일이 있는데 DB Record가 없다면 Orphan이다.


✅ 245. Orphan Cleanup은 특히 신중

DB Record가 실수로 삭제됐을 수도 있다.

바로 Storage 삭제하지 말고:

Orphan 발견

↓

Grace Period

↓

재확인

↓

삭제

가 안전하다.


✅ 246. Orphan Grace Period

예:

7일

등으로 두고 일시적 Race Condition을 피한다.

정확한 기간은 시스템 특성에 맞춘다.


✅ 247. Database Orphan도 검사 가능

예:

NotificationJob
orderId 존재

Order 없음

같은 참조 불일치다.

FK가 있으면 많은 문제를 예방할 수 있다.


✅ 248. FK를 사용할 수 있는 곳은 활용한다

Application Cleanup으로 Referential Integrity를 대신하지 않는다.


✅ 249. 삭제 순서

FK가 있다면:

Child

↓

Parent

순서가 필요할 수 있다.


✅ 250. Cascade Delete는 신중하게

Order Delete

↓

Audit

Notification

Workflow

모두 자동 Delete

되면 예상보다 훨씬 큰 데이터가 사라질 수 있다.


✅ 251. 핵심 데이터에는 무분별한 Cascade를 피한다

특히 Audit나 운영 기록이 같이 지워지지 않도록 한다.


✅ 252. Cleanup Preview에서 Cascade 영향도 표시

예:

Order 100건 삭제

Related Rows
Notification 230
Workflow 400
Audit 1,200

처럼 보여주면 위험을 알 수 있다.


✅ 253. 실제 삭제 전에 Query Plan도 볼 수 있다

대량 Cleanup Query가 Full Scan으로 DB를 압박할 수 있다.

Index를 점검한다.


✅ 254. Retention용 Index

예:

status + createdAt

status + completedAt

deletedAt

등 Cleanup Query 패턴에 맞춘다.


✅ 255. 하지만 Cleanup 때문에 Index를 너무 많이 만들지 않는다

쓰기 비용도 증가한다.

실제 Slow Query를 보고 판단한다.


✅ 256. Vacuum / Table Bloat도 고려할 수 있다

PostgreSQL에서 대량 DELETE 후 디스크 공간이 즉시 파일 시스템에 반환되지 않을 수 있다.

즉:

Row 삭제
=
Disk 즉시 감소

는 아니다.


✅ 257. 그래서 Retention 효과는 DB 내부 구조까지 봐야 한다

하지만 현재 단계에서는 Autovacuum과 일반 운영 정책을 우선하고 과도한 수동 Vacuum은 피한다.


✅ 258. 대량 삭제보다 Partition Drop이 유리할 수 있는 이유도 여기 있다

정말 규모가 커졌을 때 고려한다.


✅ 259. Data Lifecycle Runbook

예:

1. Policy 확인

2. 대상 Preview

3. Hold 확인

4. Safety Threshold 확인

5. Dry Run

6. Approval

7. Batch 실행

8. Verify

9. Failure Retry

10. Audit Summary

이다.


✅ 260. 개인정보 Anonymization Runbook

1. 대상 ID 확인

2. Active 업무 여부 확인

3. Retention Hold 확인

4. 적용 Policy 확인

5. DB PII 익명화

6. Secondary Store 확인

7. Artifact 처리

8. Verification

9. Audit

10. Monitoring

✅ 261. Backup Restore Runbook에도 Retention을 포함

Restore 완료

↓

Deletion / Anonymization 상태 확인

↓

필요 시 재적용

↓

서비스 오픈

같은 체크를 둘 수 있다.


✅ 262. 현재 프로젝트 우선 적용 1단계

먼저 Inventory만 만든다.

어떤 Table / File이 있는가?

PII가 어디 있는가?

지금 Cleanup은 있는가?

현재 보관 기간은 무엇인가?

를 정리한다.


✅ 263. 2단계

낮은 위험 데이터부터 자동 Cleanup한다.

예:

완료 Export File

Temporary File

완료 Outbox

이다.


✅ 264. 3단계

Workflow/Job History Retention을 정한다.


✅ 265. 4단계

Raw Webhook / Log의 PII와 Retention을 정리한다.


✅ 266. 5단계

회사 정책이 확정된 뒤 주문/고객 개인정보 Lifecycle을 구현한다.


✅ 267. 핵심 주문 데이터부터 자동 삭제하지 않는다

가장 위험한 접근이다.

먼저:

정책

운영 필요

보존 의무

복구 전략

을 확정한다.


✅ 268. 현재 가장 ROI 높은 작업

1. PII Inventory

2. Export Artifact 30일 Cleanup 같은 저위험 정책

3. Outbox/Inbox Retention

4. Raw Payload/Log PII 제거

5. Cleanup Dry Run + Safety Threshold

정도다.


✅ 269. 당장 과한 것

현재 규모에서는:

별도 Data Lake

복잡한 Data Governance SaaS

자동 법률 Policy Engine

대규모 Archive Cluster

모든 Table Partitioning

까지 갈 필요 없다.


✅ 270. Codex 구현 프롬프트

현재 NestJS + Prisma + PostgreSQL + AWS 기반 프로젝트에
Data Retention / Cleanup 구조를 최소 범위로 추가해줘.

목표는 모든 데이터를 무조건 삭제하는 것이 아니라,
데이터 종류별 보관 정책을 명확히 하고
오래된 Operational / Temporary Data를
안전하게 정리할 수 있도록 만드는 것이다.

주문/고객 개인정보처럼 법적·업무상 정책 확인이 필요한 데이터는
임의의 보관 기간을 가정해서 자동 삭제하지 않는다.

먼저 현재 Schema, S3/File 사용, Job/Workflow/Event 구조를 분석한다.

1. Persistent Data Inventory를 작성한다.

최소 대상:
- orders
- consults
- audit logs
- application/event logs
- outbox/inbox/webhook
- jobs
- workflow runs / step runs
- schedule runs
- export files
- S3 artifacts
- AI report artifacts

각 항목에 대해:
- 목적
- 민감도
- PII 여부 후보
- 현재 Cleanup 여부
- 추천 Cleanup 방식
을 정리한다.

2. PII 후보 Field를 찾는다.

예:
- name
- phone
- birth
- address
- email
- ip

단 Field 이름만 보고 확정하지 말고
실제 사용 코드를 확인해서 후보로 정리한다.

3. RetentionPolicy 구조를 설계한다.

예:
- dataType
- retentionDays
- gracePeriod
- action
- enabled
- riskLevel
- version

action:
- ARCHIVE
- ANONYMIZE
- DELETE

4. 주문/고객 Core Business Data에는
실제 정책이 없는 상태에서 기본 DELETE Policy를 만들지 않는다.

5. 첫 자동 Cleanup 적용 대상은
저위험 데이터로 제한한다.

우선순위:
- 만료 Export Artifact
- 완료된 Outbox/Inbox
- 오래된 완료 Workflow Step Detail
- Temporary Artifact

6. CleanupRun 모델을 만든다.

필드 예:
- id
- policyType
- policyVersion
- cutoffAt
- status
- candidateCount
- processedCount
- deletedCount
- failedCount
- startedAt
- completedAt
- errorCode

7. CleanupRun 상태:
- PLANNED
- SCANNING
- READY
- RUNNING
- VERIFYING
- SUCCESS
- PARTIAL_SUCCESS
- FAILED
- MANUAL_REQUIRED

8. Cleanup 시작 시 cutoffAt을 고정한다.

Run 중간에 현재 시간을 다시 기준으로 삼아서
대상 범위가 계속 늘어나지 않게 한다.

9. 실제 삭제 전에 Dry Run을 지원한다.

Dry Run 결과:
- candidateCount
- oldestCandidate
- newestCandidate
- estimated size가 가능하면 해당 정보
- 삭제 대상 조건

10. Safety Threshold를 적용할 수 있게 한다.

예:
- maxDeleteCount
- maxDeleteRatio

Threshold를 초과하면
자동 삭제하지 않고 MANUAL_REQUIRED로 전환한다.

11. Cleanup 대상은 상태 조건을 포함한다.

예:
OutboxEvent는 단순 createdAt만 보지 말고
PUBLISHED 등 안전한 Terminal 상태인지 확인한다.

PENDING / RETRY_PENDING / DEAD_LETTER를
무조건 삭제하지 않는다.

12. Workflow / Schedule / Job 역시
Active 상태는 Cleanup 대상에서 제외한다.

예:
- RUNNING
- WAITING
- COMPENSATING
등.

13. Cleanup은 Batch 방식으로 처리한다.

Offset 기반보다는
Primary Key / Keyset Cursor 방식을 우선 고려한다.

14. Batch Size와
필요한 경우 batch delay를 설정할 수 있게 한다.

15. Cleanup Job은 Idempotent하게 만든다.

이미 삭제된 Resource를 다시 처리해도
안전해야 한다.

16. Artifact 삭제는 DB Row 삭제와 분리한다.

예:
- EXPIRED
- DELETE_PENDING
- DELETING
- DELETED
- DELETE_FAILED

상태를 고려한다.

17. S3/File 삭제 요청 결과가 불확실한 경우
Object 존재 여부를 재확인하는
Reconciliation 흐름을 고려한다.

18. DB Record와 Artifact 간 Orphan을
탐지할 수 있는 구조를 만든다.

Orphan을 발견했다고 즉시 삭제하지 않고
Grace Period 후 재확인할 수 있게 한다.

19. 개인정보가 Log / Audit / Event Payload에
과도하게 복제되고 있는지 분석한다.

특히 다음 패턴을 찾는다.
- logger.info(req.body)
- 전체 order/customer 객체 로그
- Outbox payload에 전화번호/주소 저장
- Audit before/after 전체 JSON

20. Logger에 이미 Redaction 구조가 있다면 재사용한다.

없다면 민감 Field 중앙 Redaction 방식을 제안한다.

21. Audit Log에서 개인정보 원문 보관을 최소화한다.

가능하면:
- changed field names
- targetId
- [REDACTED]
형태를 우선한다.

22. Anonymization 구조를 설계할 때
다음 Precondition을 고려한다.

- Active Workflow 없음
- Retention Hold 없음
- Business Final State

단 실제 주문 데이터 익명화는
정책 확정 전 자동 실행하지 않는다.

23. Retention Hold 구조를 고려한다.

필드 예:
- targetType
- targetId
- reason
- createdBy
- createdAt
- expiresAt

24. Hold 상태 데이터는
일반 Cleanup에서 제외한다.

25. Policy 변경은 Audit Log에 기록한다.

- before
- after
- actor
- reason
- version

26. Retention Policy 변경 시
실제 적용 전에 영향도를 계산할 수 있게 한다.

예:
90일 → 30일 변경 시
예상 Candidate Count 출력

27. Cleanup 자체는
Kill Switch 또는 enabled 상태로 중지할 수 있게 한다.

28. Cleanup 관련 Metric을 수집할 수 있게 한다.

- expired records
- cleanup success
- cleanup failure
- deleted count
- oldest expired age
- cleanup duration

29. 다음 Reliability Test를 작성한다.

- Dry Run 후 실제 데이터 변경 없음
- Threshold 초과 시 실행 중단
- Terminal 상태 데이터만 대상
- Active Workflow 삭제 방지
- Batch 삭제
- 중간 Worker Crash 후 Resume
- 동일 Cleanup 재실행 Idempotency
- Artifact delete timeout 후 Reconciliation
- Retention Hold 대상 제외
- 잘못된 Policy Version 차단

30. 실제 주문/고객 PII Hard Delete는
이번 구현 범위에서 자동화하지 않는다.

먼저 Inventory + Policy 적용 구조 + 저위험 Cleanup부터 구현한다.

현재 코드베이스를 분석한 후,
필요 이상의 Data Governance Platform을 만들지 말고
1인 개발자가 운영 가능한 수준으로 설계해줘.

✅ 271. PII Inventory용 Codex 프롬프트

현재 NestJS + Prisma 코드베이스에서
개인정보 또는 민감정보가 저장/전달/로그될 가능성이 있는 위치를 찾아
PII Inventory Report를 작성해줘.

코드나 데이터를 수정하지 않고 분석만 한다.

검토 대상:

1. Prisma Schema
2. DTO
3. API Request / Response
4. Logger
5. Audit Log
6. Outbox / Inbox / Webhook Payload
7. Queue Payload
8. S3 / File Artifact
9. Export Excel
10. AI Prompt / Context / Report
11. Analytics / Lead Tracking

찾을 후보:

- 이름
- 전화번호
- 주소
- 생년월일
- 이메일
- IP
- Token / Credential
- 상담 메시지
- 주문 개인정보

각 후보에 대해 다음을 정리한다.

## 데이터

## 저장 위치

## 생성 위치

## 사용 위치

## Log 포함 여부

## Event/Queue 복제 여부

## Export 포함 여부

## 삭제/익명화 경로 존재 여부

## Retention 정의 여부

## 위험도

HIGH
민감 데이터가 여러 저장소/로그로 복제됨

MEDIUM
필요한 저장이지만 Retention이 불명확함

LOW
ID 참조만 존재하거나 이미 Redaction됨

실제 코드에서 확인할 수 없는 보관 기간이나
법적 보관 의무는 추정하지 않는다.

마지막에 다음 세 가지를 별도로 정리한다.

1. 지금 바로 줄일 수 있는 불필요한 PII 복제
2. Retention 정의가 필요한 데이터
3. 삭제/익명화 구현 전에 정책 확인이 필요한 데이터

✅ 272. Cleanup Dry Run 프롬프트

현재 DB Cleanup 후보를 분석하되
실제 DELETE / UPDATE를 실행하지 말고
Dry Run Report만 작성해줘.

대상:
- PUBLISHED Outbox
- PROCESSED Inbox
- 완료 Workflow Step
- 만료 Export Artifact

각 대상마다:

1. Cleanup 조건
2. Candidate Count를 구할 Query
3. 삭제하면 안 되는 상태
4. 필요한 Index
5. Batch 기준
6. Safety Threshold
7. Retry / Resume 전략
8. Verification Query
9. 예상 위험

을 정리한다.

특히 다음은 자동 삭제 대상에서 제외한다.

- PENDING
- PROCESSING
- RETRY_PENDING
- WAITING
- MANUAL_REQUIRED
- DEAD_LETTER
- Retention Hold

실제 Production 데이터는 수정하지 않는다.

✅ 273. 실무 체크리스트

Data Inventory

  • 어떤 Persistent Data가 있는지 알고 있는가?
  • PII가 어디에 저장되는지 알고 있는가?
  • 같은 개인정보가 여러 저장소에 복제되지 않는가?
  • 각 데이터의 목적이 명확한가?

Retention

  • 데이터별 Retention Policy가 있는가?
  • 모든 데이터를 같은 기간 보관하지 않는가?
  • Retention 없는 새 Table을 발견할 수 있는가?
  • Policy 변경 영향도를 미리 볼 수 있는가?

Cleanup

  • Dry Run이 있는가?
  • Candidate Count를 확인하는가?
  • Safety Threshold가 있는가?
  • Batch 삭제를 사용하는가?
  • Active 상태 데이터를 제외하는가?
  • Cleanup이 Idempotent한가?

PII

  • 로그에 PII가 직접 남지 않는가?
  • Event/Queue Payload에 PII를 최소화했는가?
  • Audit에 개인정보 전체 Snapshot을 저장하지 않는가?
  • 익명화/삭제 전 Active 업무를 확인하는가?
  • 정책 확인 없이 Core Business Data를 자동 삭제하지 않는가?

Artifact

  • DB Record와 실제 File Lifecycle이 연결되는가?
  • S3/File 삭제 실패를 감지할 수 있는가?
  • Orphan File을 찾을 수 있는가?
  • Orphan을 즉시 삭제하지 않고 재확인하는가?

Workflow

  • Cleanup Run 상태를 기록하는가?
  • cutoffAt이 Run 동안 고정되는가?
  • Worker Crash 후 Resume 가능한가?
  • 실패 항목만 재처리하는가?

Audit / Hold

  • Cleanup 실행 자체가 기록되는가?
  • Retention Hold가 있는가?
  • Hold Reason / Expiry를 기록하는가?
  • Incident 관련 데이터가 Cleanup에서 보호되는가?

AI

  • AI는 Inventory / Risk Review 위주로 사용하는가?
  • AI가 Production DELETE를 직접 실행하지 않는가?
  • Raw Prompt/Context Retention을 고려했는가?
  • Tool Log에 Secret/PII가 남지 않는가?

📌 요약

1002에서는:

새 구조를 어떻게 안전하게 추가하고

구버전과 신버전을 공존시키고

마지막에 Legacy를 제거할 것인가

를 다뤘다.

1003에서는 시스템 운영의 반대편 질문을 다뤘다.

이 데이터는
언제까지 살아 있어야 하는가?

이다.

데이터 Lifecycle은:

Collect
↓
Use
↓
Store
↓
Archive
↓
Anonymize / Delete

로 볼 수 있다.

가장 먼저 기억할 것은:

모든 데이터는 영구 보관 대상이 아니다.

하지만 반대로:

오래됐다는 이유만으로
모든 데이터를 삭제해서도 안 된다.

는 것이다.

따라서:

Business Data

Audit Data

Operational Data

Event Data

Temporary Data

PII

를 구분하고 서로 다른 Retention Policy를 적용해야 한다.

특히 개인정보는:

DB Row 삭제

하나로 끝나는 문제가 아니다.

Log

Audit

Event

Queue

Export

File

Backup

등 다른 위치에도 복제돼 있을 수 있기 때문이다.

그래서 가장 먼저 필요한 것은:

Data Inventory

다.

즉:

어떤 개인정보가 어디에 있고 왜 존재하는지 모르는 상태에서는 안전한 삭제도 할 수 없다.

또 Cleanup 자체도 위험한 Production 작업이다.

따라서:

Cron
→ DELETE

로 만들기보다:

Policy
↓
Dry Run
↓
Candidate Count
↓
Safety Threshold
↓
Approval
↓
Batch Cleanup
↓
Verification

순서로 처리하는 것이 좋다.

특히:

평소 1,000건 삭제

오늘 250,000건 대상

처럼 예상 범위를 크게 벗어나면 자동 실행을 멈춰야 한다.

Cleanup도 지금까지 다룬 운영 원칙을 그대로 사용한다.

Idempotency

Batch

Retry

Resume

Reconciliation

Audit

Observability

가 필요하다.

Artifact도 마찬가지다.

DB Record 삭제

와:

S3/File 삭제

는 서로 다른 시스템이므로 불일치가 생길 수 있다.

따라서:

Expired
↓
Delete Pending
↓
Delete
↓
Verify
↓
Deleted

같은 상태를 두고 Reconciliation할 수 있다.

그리고 개인정보 관련 핵심 원칙은:

필요 없는 데이터는
나중에 잘 삭제하는 것보다
처음부터 저장하지 않는 것이 더 좋다.

는 것이다.

따라서:

Log PII 제거

Event Payload 최소화

Audit Snapshot 최소화

ID Reference 중심 설계

가 Cleanup보다 우선될 수 있다.

현재 프로젝트에서는 주문/고객 Core Data를 바로 자동 삭제하기보다 먼저:

1. PII Inventory

2. Export Artifact Cleanup

3. Outbox / Inbox Retention

4. Workflow History Retention

5. Raw Webhook / Log PII 정리

순서로 적용하는 것이 현실적이다.

전체 시리즈 흐름에 Data Lifecycle을 연결하면:

Create
↓
Process
↓
Observe
↓
Persist
↓
Retain
↓
Archive / Anonymize
↓
Cleanup
↓
Verify
↓
Audit

가 된다.

결국 Data Retention의 핵심은

“데이터를 얼마나 많이 보관할 수 있는가”가 아니라, 각 데이터가 왜 존재하는지 설명할 수 있고, 더 이상 필요하지 않을 때 안전하고 검증 가능한 방식으로 수명을 끝낼 수 있는가

이다.

0개의 댓글