CodeGym /행동 /ChatGPT Apps /Audit & lifecycle: audit logs, data retention, 요청 시 삭...

Audit & lifecycle: audit logs, data retention, 요청 시 삭제, continuity/backups

ChatGPT Apps
레벨 15 , 레슨 4
사용 가능

1. 왜 ChatGPT App에 audit & lifecycle이 필요한가

프로토타입을 만들 때는 사용자 — 본인, DB — 로컬 SQLite, ‘사고’는 git reset --hard로 해결하면 되는 세상처럼 느껴집니다.

하지만 여러분의 GiftGenius(또는 다른 ChatGPT 애플리케이션(App))가 실제 사용자, 특히 결제와 PII를 다루기 시작하면, 갑자기 다음이 등장합니다:

  • 고객 보안팀: “누가 우리 주문을 볼 수 있고, 누가 변경했나요?”
  • 법무팀: “데이터는 얼마 동안 보관하며 ‘나를 삭제해 달라’는 요청은 어떻게 처리하나요?”
  • 프로덕션 현실: “만약 개발자가 프로덕션에서 테이블을 드롭하면 어떻게 하죠?”

이 강의에서는 네 가지 핵심 블록을 다룹니다:

  1. Audit 로그 — 보안과 감사를 위한 별도의 로깅 레이어.
  2. Data retention — 데이터 유형별 보존 기간과 구현 방법.
  3. 사용자 요청 삭제 — 기술적으로 구현하는 ‘잊힐 권리’.
  4. Business continuity & backups — 체면과 데이터를 잃지 않고 장애를 넘기는 법.

예시는 가능한 한 우리 학습용 App(가상의 GiftGenius on Next.js + Apps SDK + MCP)에 맞춰 설명합니다.

2. Audit 로그: 누가, 무엇을, 언제, 결과는 무엇이었나

Audit 로그와 일반 로그의 차이

일반 application 로그는 개발자를 위한 친화적인 메시지입니다. 여기에는 stack trace, 디버그 정보, 이상한 변수 값, 디버깅용 console.log("여기서는 절대로 null이면 안 됨") 같은 것들이 들어갑니다. 수명이 짧고 엔지니어가 읽습니다.

Audit 로그는 전혀 다른 세계입니다. 주요 독자는 보안팀, 감사인, 때로는 법무입니다. 이들에게 “55번째 줄에서 NullPointer” 같은 줄은 필요 없고, “사용자 X가 조직 Y의 결제 설정을 어느 시각에 변경했고, 결과는 성공” 같은 기록이 필요합니다. Audit 기록은 보통 훨씬 오래(수년) 보관되며 조사 시 증거로 간주됩니다.

핵심 차이점:

특성 Application Logs Audit Logs
목적 디버깅, 진단 보안, 컴플라이언스, 조사
대상 독자 개발자, SRE 보안팀, 법무, 때로는 규제기관
데이터 구성 기술적 세부정보, stack trace 누가/무엇을/언제/어떤 리소스에/어떤 결과로
보존 기간 수주–한 달 수년(자주 ≥ 1년)
로그 조작 삭제/덮어쓰기 가능 가급적 append‑only, UPDATE/DELETE 금지

OWASP 등 가이드라인은 특히 강조합니다: audit 로그는 일반 애플리케이션 로그와 섞지 말고 별도 저장소나 테이블에 보관하는 것이 좋습니다.

ChatGPT 애플리케이션에서 무엇을 기록할까

특히 상거래를 포함한 ChatGPT 애플리케이션이라면, 합리적인 최소 감사 범위는 다음과 같습니다:

  • 인증 이벤트: 로그인, 로그아웃, 로그인 시도;
  • 중요 데이터 작업: 프로필/주문/결제 설정의 생성/업데이트/삭제;
  • 관리자 작업: 역할 변경, 테넌트 설정 수정;
  • 민감한 MCP/Agents 도구 호출: create_order, charge_customer, cancel_subscription 등.

좋은 가늠자: 인시던트 때 “누가 무엇을 통해 했는가?”를 묻게 될 모든 것은 감사에 남겨야 합니다.

감사 이벤트의 구조

유용한 사고모델: 각 기록은 “누가 / 어떤 행동을 / 무엇에 대해 / 어떤 컨텍스트에서 / 어떤 결과로”입니다. 보통 다음 구조로 표현합니다: who, action, resource, context, outcome.

우리 GiftGenius를 위해 TypeScript 인터페이스를 정의해 봅시다:

// lib/audit.ts
export type AuditAction =
  | "auth.login"
  | "auth.logout"
  | "order.create"
  | "order.cancel"
  | "account.delete"
  | "giftidea.generate";

export interface AuditEvent {
  eventId: string;          // uuid
  timestamp: string;        // ISO
  actor: {
    userId: string | null;  // 로그인 전에는 null일 수 있음
    tenantId?: string | null;
    ip?: string | null;
    client: "chatgpt-app" | "admin-panel" | string;
  };
  action: AuditAction;
  resource?: {
    type: string;           // "order", "user", ...
    id?: string;
  };
  context?: {
    mcpTool?: string;
    requestId?: string;
  };
  outcome: {
    status: "success" | "failure";
    reason?: string | null;
  };
}

주의: 이벤트에는 전체 이메일, 카드 번호 등 이전 강의에서 배운 바와 같이 반드시 마스킹하거나 불필요하게 로깅하지 말아야 할 PII가 포함되지 않아야 합니다.

Audit 로그를 어디에 어떻게 저장할까

저장소에 대한 최소 요구사항:

  • 일반 로그와는 별도의 테이블 또는 아예 별도 DB로 분리하여 실수로 덮어쓰지 않도록 할 것;
  • 가능하면 append‑only 모드: 기술적으로는 “이 테이블에는 UPDATE/DELETE를 절대 하지 않는다”는 정책과, INSERTSELECT만 권한을 가진 전용 DB 역할을 두는 방식;
  • 접근 제한: 모든 엔지니어가 전체 audit를 읽을 권한을 가질 필요는 없습니다.

PostgreSQL을 Prisma/Drizzle로 사용한다면, 모델은 다음과 같을 수 있습니다(단순화 예시):

CREATE TABLE audit_events (
  event_id   uuid PRIMARY KEY,
  created_at timestamptz NOT NULL DEFAULT now(),
  actor_user_id text,
  actor_tenant_id text,
  actor_ip      inet,
  action        text NOT NULL,
  resource_type text,
  resource_id   text,
  context_mcp_tool text,
  context_request_id text,
  outcome_status text NOT NULL,
  outcome_reason text
);

스키마는 필요에 맞게 조정하면 되지만, 가장 중요한 것은 구조화입니다. JSON 쓰레기를 한 줄로 때려 넣으면 나중에 반드시 후회합니다.

우리 App에서 감사 구현

Next.js 애플리케이션의 작은 helper를 만들어 봅시다(Node 환경, 예: MCP 서버 또는 API 라우트):

// lib/audit.ts
import { randomUUID } from "crypto";
import { db } from "./db"; // DB 클라이언트

export async function logAudit(event: Omit<AuditEvent, "eventId" | "timestamp">) {
  const full: AuditEvent = {
    ...event,
    eventId: randomUUID(),
    timestamp: new Date().toISOString(),
  };

  // 실서비스에서는 큐/백그라운드로; 여기서는 단순 insert
  await db.insertInto("audit_events").values({
    event_id: full.eventId,
    created_at: full.timestamp,
    actor_user_id: full.actor.userId,
    action: full.action,
    outcome_status: full.outcome.status,
    outcome_reason: full.outcome.reason ?? null,
    // ...나머지 필드
  });
}

이제 주문을 생성하는 핸들러(MCP 도구 또는 서버 엔드포인트라고 가정)에 호출을 추가합니다:

// app/api/orders/route.ts
export async function POST(req: Request) {
  const user = await requireUser(req); // 인증 모듈에서
  const body = await req.json();
  const order = await createOrderInDb(user, body);

  await logAudit({
    actor: { userId: user.id, client: "chatgpt-app" },
    action: "order.create",
    resource: { type: "order", id: order.id },
    context: { mcpTool: "create_order_tool" },
    outcome: { status: "success" },
  });

  return Response.json(order);
}

취소, 결제 정보 변경, 계정 삭제처럼 위험한 작업에도 동일하게 적용할 수 있습니다.

별도의 구조화된 감사 레이어가 생겼습니다 — 훌륭합니다. 다음 자연스러운 질문은: 얼마나 오래 이러한 이벤트(및 기타 사용자 데이터)를 보관해야 하며 기한이 지나면 어떻게 처리할까요?

3. Data retention: 데이터는 얼마나 오래 살아야 하나

왜 영구 보관하면 안 되는가

“언젠가 쓸지도”라는 엔지니어 본능은 사용자 데이터 맥락에서는 매우 위험합니다.

첫째, 데이터를 오래 많이 쌓을수록 유출 시 피해가 커집니다. 기름통이 두꺼울수록 불이 더 무섭습니다. 많은 데이터 보호 가이드가 데이터를 ‘독성 자산’으로 부릅니다: 유용하게 보관하되, 규모와 기간을 최소화하라.

둘째, GDPR/CCPA 등 법률은 “처리 목적에 필요한 기간을 넘지 않는다”는 원칙을 요구합니다. 즉, 개인 데이터를 “혹시 몰라서” 무한정 보관할 수 없습니다. 데이터 유형마다 명확한 보존 기간과 삭제 또는 익명화 절차가 있어야 합니다.

셋째, 클라우드 스토리지는 비용이 듭니다. 로그 테이블과 대화 히스토리는 빠르게 커지고, 1년쯤 지나면 클라우드 요금의 절반이 “어제의 쓰레기”가 됩니다.

데이터마다 다른 보존 기간

기업 경험과 공개 가이드라인을 종합하면 대략 다음과 같습니다:

데이터 유형 일반적 보존 기간
Debug 로그, 기술 메트릭 1~12개월
Audit 로그 ≥ 12개월, 경우에 따라 2–5년
주문, 결제, 청구 3–7년(회계/세무 요구)
세션, 임시 토큰 수시간–수일
원시 채팅/요청 수주~수개월, 혹은 아예 보관하지 않음
익명 집계(애널리틱스) 더 길게 — PII가 없으므로

중요: 이는 법률 자문이 아닌 엔지니어링 가이드입니다. 실제 제품에서는 법무와 기간을 합의하되, 기술적으로는 이미 유형별 TTL을 구현할 준비가 되어 있어야 합니다.

코드로 retention 구현하기

가장 흔한 패턴: 테이블에 created_at 또는 expires_at이 있고, 주기적으로 오래된 레코드를 삭제하거나 익명화하는 프로세스를 둡니다.

예: 90일보다 오래된 일반 로그 정리.

// scripts/cleanup-logs.ts
import { db } from "../lib/db";

async function cleanup() {
  await db
    .deleteFrom("app_logs")
    .where("created_at", "<", new Date(Date.now() - 90 * 24 * 60 * 60 * 1000));
  console.log("Old logs removed");
}

cleanup().catch(console.error);

이런 스크립트는 cron, GitHub Actions 스케줄, 또는 클라우드 스케줄러로 실행할 수 있습니다.

PII의 경우 삭제 대신 익명화를 자주 합니다. 예를 들어, N년이 지난 주문은 특정 사용자와의 연결을 끊습니다:

UPDATE orders
SET user_id = NULL
WHERE created_at < now() - interval '3 years';

이때 금액, 상품 등 ‘회계 데이터’는 남지만, 특정 개인과의 연결은 사라집니다.

백업에도 자체 보존 기간이 있어야 함을 잊지 마세요. 백업의 주기와 보관 기간은 백업 파트에서 별도로 다루지만, 아이디어는 같습니다: 보관 기간 없이 백업을 무한정 쌓아두면 ‘잊힐 권리’는 현실에서 작동하지 않습니다.

4. 사용자 요청 삭제: ‘잊힐 권리’를 코드로

요구 사항의 출처

유럽의 GDPR(및 유사 법률)은 이른바 ‘잊힐 권리’를 도입합니다: 사용자가 자신의 개인 데이터 삭제를 요구하면, 회사는 부당한 지체 없이 이를 수행해야 합니다.

개발자 관점에서는: 언젠가는 “내 데이터를 모두 삭제해 주세요”라는 요청이 오거나 (혹은 “Delete my data” 버튼을 직접 만들거나) users 테이블의 레코드만 지우는 게 아니라, 전체 흔적 — 주문, 세션, 토큰, 활동 로그, CRM, 결제 등 — 을 따라가 처리해야 함을 의미합니다.

다만, 재무 트랜잭션처럼 법적으로 일정 데이터를 보관해야 하는 경우도 있습니다. 즉, 기술 문제뿐 아니라 법적 문제도 함께 고려해야 합니다.

무엇을 지워야 하는가

우리 GiftGenius 기준 최소 항목:

  • 사용자 프로필(이름, 이메일, 설정);
  • 세션, refresh 토큰, OAuth 제공자 연동;
  • 개인화가 불필요한 주문(또는 익명화 가능한 경우 익명화);
  • PII가 포함된 로그 및 audit 기록(예: 원문 이메일 등).

이와 함께 보고에 중요한 데이터는 남기되, 비식별화합니다 — 주문 금액, 트랜잭션 수, 국가별 집계 등.

삭제 알고리즘 예

시나리오 개요:

  1. 사용자(인증됨)가 “내 계정 삭제”를 클릭한다.
  2. 서버로 해당 사용자의 userId가 전송된다.
  3. 서버는 다음을 수행한다:
    • 종속 레코드(주문, 세션, 통합 등)를 삭제/익명화;
    • 프로필의 PII 정리;
    • “데이터 삭제 요청 처리”를 audit 로그에 기록.

단순화를 위해 두어 개 테이블만 예로 들겠습니다. 실제 제품에서는 이 핵심을 바탕으로 (통합, 외부 서비스 등) 더 많은 엔티티를 추가합니다.

Next.js의 서비스 코드(단순화 예):

// app/api/delete-me/route.ts
import { db } from "@/lib/db";
import { logAudit } from "@/lib/audit";

export async function POST(req: Request) {
  const user = await requireUser(req);

  await db.transaction(async (tx) => {
    await tx.deleteFrom("sessions").where("user_id", "=", user.id);
    await tx.deleteFrom("orders").where("user_id", "=", user.id);

    await tx.updateTable("users")
      .set({
        is_deleted: true,
        name: null,
        email: null,
      })
      .where("id", "=", user.id);

    await logAudit({
      actor: { userId: user.id, client: "chatgpt-app" },
      action: "account.delete",
      outcome: { status: "success" },
    });
  });

  return new Response(null, { status: 204 });
}

실제 환경에서는 외부 API 호출(예: Stripe에서 customer 분리)도 추가하고, 트랜잭션도 더 견고하게 구성합니다. 하지만 원칙은 동일합니다: 한 곳에 모아 처리하고, audit 기록을 남깁니다.

백업과의 상호작용

“그럼 백업은?”이라는 까다로운 부분에 흥미로운 쟁점이 많습니다. 운영 DB에서 사용자를 삭제하더라도 야간 스냅샷에 데이터가 남아 있을 수 있습니다. 사실상 “우리는 아무도 삭제하지 않는다”가 되지 않으려면 두 가지 접근이 있습니다:

  1. 백업 자체에 보존 기간(예: 30–90일)을 두고, 기간 경과 시 데이터와 함께 사라지게 합니다. Retention이 지나면 운영 DB와 아카이브 어디에도 사용자가 남지 않습니다.
  2. 백업에서 시스템을 복구할 일이 생기면, ‘삭제된’ ID 레지스트리를 가지고 복구 후 삭제/익명화 스크립트를 재적용합니다.

대기업에서는 때로 crypto‑shredding을 사용합니다: 사용자의 PII를 별도 키로 암호화해 두고, 삭제 요청 시 키만 폐기합니다. 로그나 백업 어딘가에 암호화된 사본이 남아 있더라도 키 없이는 무의미한 데이터가 됩니다. 훌륭하지만 스타트업에겐 다소 ‘로켓 과학’일 수 있습니다.

중요한 UX 포인트

삭제는 SQL만의 문제가 아닙니다. 사용자는 다음을 기대합니다:

  • 요청을 보낼 명확한 방법(버튼, 폼, 이메일);
  • 합리적인 처리 기간(실무상 최대 30일 정도);
  • 성공 알림 또는 정당한 거절(예: 일부 데이터는 법적으로 보관해야 하는 경우).

기술적으로는 이미 준비가 끝났습니다: 정리를 수행하고, 작업을 로깅하며, 백업에 불필요하게 남기지 않는 법을 아는 것입니다.

5. Business continuity & 백업

지금까지가 완벽히 작동한다고 해봅시다… 그러다 치명적인 DROP TABLE orders, 클라우드 장애, 리전 다운이 발생합니다. 합리적인 시간 내 서비스 복구와 핵심 데이터 보전을 위한 메커니즘이 필요합니다.

RTO와 RPO — 복구의 두 축

Disaster Recovery의 두 기본 지표:

  • RTO (Recovery Time Objective) — 허용 가능한 서비스 불가 시간. 예를 들어 RTO = 1시간이면, 심각한 장애 후 최대 1시간 내 시스템을 복구해야 합니다.
  • RPO (Recovery Point Objective) — 시간 기준으로 허용 가능한 데이터 손실. RPO = 10분이면, 복구 시 마지막 10분 이내의 데이터만 손실 허용입니다.

제품이 중요할수록(뱅킹, 트레이딩 시스템) 두 값은 0에 가까워집니다. 학습용 GiftGenius는 RTO 수시간, RPO 15–60분 정도도 괜찮겠지만, 이것도 구현은 필요합니다.

여러분의 스택에서 무엇이 문제될 수 있나

Vercel + 클라우드 DB + 외부 API에 기반한 ChatGPT 애플리케이션 문맥에서 흔한 이슈는 다음과 같습니다:

  • OpenAI API가 다운: App이 tool‑call에 에러로 응답.
  • Vercel(또는 다른 곳) 장애: 위젯이 백엔드에 연결 불가.
  • 데이터베이스 손상 또는 실수로 삭제(예: DROP TABLE).
  • 인프라를 제어하는 계정이 파손되거나 탈취.

이에 대한 대응은 백업, 복제, 그리고 장애 시 합리적인 애플리케이션 동작의 조합입니다.

백업 전략

현대 클라우드 Postgres/기타 DB는 보통 최소 세 가지 옵션을 제공합니다:

  1. 전체 백업 + 증분.
    하루 한 번 전체 스냅샷을 만들고, 그 사이 변경분을 증분 보관. 복구는 특정 스냅샷 시점으로 롤백 후 변경 로그를 재생.
  2. Point‑in‑Time Recovery (PITR).
    DB가 트랜잭션 로그(WAL)를 작성하여 임의 시점으로 복구 가능(예: “테이블을 드롭하기 전 14:03:00 상태”).
  3. 다른 리전으로의 복제.
    다른 리전/클라우드에 수동 또는 능동 복제본 유지. 기본 리전을 잃으면 복제본으로 스위치하고, 아직 전파되지 않은 일부 데이터만 손실.

우리 규모에서는 보통 DB 제공자의 PITR를 켜고 주기적인 오프사이트 백업이면 충분합니다.

간단 예시: 로컬/dev DB의 일일 덤프

프로덕션은 관리형 DB에 의존하더라도, staging/dev에서는 간단한 스크립트가 유용할 때가 있습니다:

# scripts/backup.sh
#!/usr/bin/env bash
set -e
DATE=$(date +%F)
pg_dump "$DATABASE_URL" > "backups/backup-$DATE.sql"
echo "Backup created: backups/backup-$DATE.sql"

cron이나 GitHub Actions로 실행할 수 있습니다. 중요한 점 — 백업도 보존 기간에 맞춰 삭제해야 한다는 것입니다.

외부 서비스 장애 시 App 동작

백업과 PITR는 “완전히 망가졌거나 데이터가 손상된” 경우의 이야기입니다. 하지만 비즈니스 현실에서는 부분적 장애 — 외부 API 다운, 네트워크 단절, 결제 서비스 지연 — 이 더 자주 발생합니다.

OpenAI API나 결제 서비스가 다운될 때 최악의 전략은 의미 없는 500과 stack trace를 그대로 내보내는 것입니다. 이상적인 동작은:

  • 백엔드가 { error: "upstream_unavailable" }처럼 구조화된 에러를 반환;
  • 위젯이 사용자에게 이해 가능한 메시지를 보여줌: “서비스가 일시적으로 중단되었습니다. 나중에 다시 시도해 주세요.”;
  • 죽은 API를 무한 재시도하지 않음(서킷 브레이커 등 패턴은 안정성 모듈에서 자세히 다룹니다).

외부 오류를 고려한 MCP 도구 핸들러 예:

// mcp/tools/createGiftIdea.ts
export async function createGiftIdea(args: Input): Promise<Output> {
  try {
    return await callOpenAiModel(args);
  } catch (err) {
    await logAudit({
      actor: { userId: args.userId ?? null, client: "chatgpt-app" },
      action: "giftidea.generate",
      outcome: { status: "failure", reason: "openai_unavailable" },
    });
    throw new Error("UPSTREAM_UNAVAILABLE");
  }
}

이후 MCP와 위젯 사이의 프록시 층에서 이 에러를 UI에 적절히 표시하도록 처리하면 됩니다.

복구 검증: restore 없는 백업은 그냥 파일

고전적인 안티 패턴: 매일 백업을 만들어 모두가 안심하지만… 막상 복구하려니 불가능(포맷 변경, 키 분실, 용량 부족)한 경우입니다.

최소 계획:

  • 주기적으로(예: 월 1회) 백업에서 staging 환경을 부팅;
  • 기본 시나리오 수행: 로그인, 주문 생성, App 동작;
  • 복구 시간과 데이터 손실이 여러분의 RTO/RPO 내에 들어오는지 검증.

‘DevOps 교리’ 강좌 대신, 이 강의에서 기억할 점은 이것입니다: 백업 프로세스는 App 아키텍처의 일부이지, “클라우드 어딘가에서 누가 해 줄 것”이 아닙니다.

6. 시각화: 데이터와 이벤트의 라이프사이클

말뿐이 아니라, 두 가지 간단한 다이어그램을 그려 봅시다.

사용자 데이터의 라이프사이클

flowchart TD
  A["데이터 생성<br/>(가입, 주문)"] --> B["저장 및 사용<br/>(prod DB)"]
  B --> C["아카이브/집계<br/>(익명 메트릭)"]
  B --> D[삭제 요청]
  D --> E[삭제/익명화<br/>prod DB에서]
  E --> F["백업 보존 기간 만료<br/>(retention)"]
    

핵심 아이디어: 데이터 라이프사이클은 운영 DB에서 끝나지 않고, 백업에서도 계속됩니다.

위험한 작업에 대한 감사 플로우

sequenceDiagram
  participant User as 사용자
  participant ChatGPT as ChatGPT
  participant App as 백엔드/MCP
  participant DB as 데이터베이스
  participant Audit as 감사 저장소

  User->>ChatGPT: "주문 #123을 취소해 줘"
  ChatGPT->>App: callTool cancel_order
  App->>DB: UPDATE orders SET status='canceled'
  App->>Audit: INSERT audit_event {actor, action, resource, outcome}
  App-->>ChatGPT: 작업 결과
  ChatGPT-->>User: 결과 메시지
    

7. Audit & lifecycle에서 흔한 실수

오류 №1: Audit 로그와 일반 로그를 뒤섞음.
모든 메시지를 하나의 logs 인덱스로 몰아넣으면, 반년 뒤에는 “사용자가 관리자 역할을 변경”한 것과 “또 null reference 났다”를 구분하기 어렵습니다. Audit에는 비즈니스 수준의 구조화된 이벤트(감사 이벤트 구조 섹션 참고)와 제한된 접근의 별도 저장소가 필요합니다.

오류 №2: Audit/Debug 로그에 PII를 기록.
전체 이메일, 전화번호, 배송지, 카드 끝 4자리 등은 종종 실수로 로그에 들어갑니다. 이는 유출 위험을 높이고 프라이버시 권고와도 배치됩니다. 대신 식별자와 마스킹된 값을 기록하세요.

오류 №3: Retention 정책 부재 — “항상 다 보관”.
MVP 단계에서는 “괜찮겠지” 싶지만, 1년 뒤 테이블이 괴물처럼 커져 분석 쿼리 하나가 DB에 대한 DDoS가 됩니다. 또한 최신 데이터 법의 최소화 원칙을 어깁니다. 데이터 유형별 최소 TTL을 설계하고, 정리를 자동화해야 합니다.

오류 №4: ‘요청 시 삭제’ == DELETE FROM users.
사용자 행만 삭제하고 주문, 세션, 로그에 PII를 남겨두면 실제로는 아무도 삭제되지 않은 것입니다. 올바른 접근은 모든 연관 엔티티를 트랜잭션으로 훑고, 삭제가 불가한 곳은 익명화하는 것입니다. 그리고 삭제 사실 자체를 audit 이벤트로 기록하는 것을 잊지 마세요.

오류 №5: 삭제 시 백업을 무시.
운영에서 사용자를 삭제해도, 1년 동안 옛 스냅샷에 데이터가 남아 있을 수 있습니다. 그 스냅샷으로 복구하면 모든 것이 ‘부활’하고, 프라이버시 정책과 사용자 약속을 다시 어기게 됩니다. 백업 보존 기간을 제한하거나, 복구 후 삭제 재적용 절차를 가져야 합니다.

오류 №6: “백업은 켜놨으니 문제없음” — 그러나 복구 테스트를 안 함.
복구를 한 번도 해보지 않은 백업은 그저 비싼 파일일 뿐입니다. 복구 테스트 없이는 실제 RTO/RPO도, DR 플랜이 작동하는지도 알 수 없습니다. 최소한 — 체크리스트에 따라 주기적으로 백업에서 staging을 올려 보세요.

오류 №7: 문서와 현실의 불일치.
프라이버시 정책에 “로그 30일 보관, 요청 시 데이터 삭제”라 쓰고, 코드에서는 영구 보관한다면, ChatGPT 스토어, 엔터프라이즈 고객, 감사인은 “retention 테이블을 보여달라”, “특정 사용자 삭제를 시연하라” 같은 질문으로 쉽게 드러냅니다. 먼저 구현하고, 그다음에 문서로 반영하세요.

1
설문조사/퀴즈
보안, 레벨 15, 레슨 4
사용 불가능
보안
보안 및 컴플라이언스
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION