0817 데이터베이스 실무 심화 (5/N): 상담/주문 상태 변경 이력과 Audit Log 설계
✅ 1. 상태 변경 이력이란 무엇인가?
- 상태 변경 이력은 데이터의 상태가 언제, 누구에 의해, 어떤 값에서 어떤 값으로 바뀌었는지 기록하는 테이블입니다.
- 상담, 주문, 개통, 배송, 알림 발송, 엑셀 ExportJob처럼 상태가 변하는 데이터에는 이력이 중요합니다.
- 현재 상태만 저장하면 운영 중 문제가 생겼을 때 과거 흐름을 추적하기 어렵습니다.
현재 상태만 저장:
consult.status = CALLED
상태 이력 저장:
NEW → CALLING → CALLED
각 변경 시간과 관리자 기록
➕ 1-1. 상태 변경 이력이 필요한 이유
누가 상태를 바꿨는지 확인
언제 상태가 바뀌었는지 추적
잘못된 상태 변경 원인 확인
상담 처리 속도 분석
관리자 업무 이력 확인
고객 문의/분쟁 대응
장애 원인 추적
- 상태 이력은 단순 로그가 아닙니다.
- 운영자가 매일 처리하는 업무의 증거이자, 데이터 정합성을 지키는 장치입니다.
✅ 2. 현재 상태와 상태 이력의 차이
- 현재 상태는 현재 row의 최종 상태입니다.
- 상태 이력은 그 상태가 되기까지의 변경 과정입니다.
consults.status:
현재 상태
consult_status_histories:
상태 변경 과정
➕ 2-1. 예시
consults
- id: 10
- status: CONVERTED
consult_status_histories
- NEW → CALLING
- CALLING → CALLED
- CALLED → CONVERTED
➕ 2-2. 둘 다 필요한 이유
현재 상태:
목록 조회/필터/정렬에 필요
상태 이력:
추적/감사/분석/분쟁 대응에 필요
- 관리자 목록에서 매번 이력 테이블을 조회해 현재 상태를 계산하면 비효율적입니다.
- 그래서 현재 상태는 원본 테이블에 두고, 변경 과정은 이력 테이블에 남기는 방식이 실무적으로 좋습니다.
✅ 3. 상태 이력을 남기지 않으면 생기는 문제
상담이 언제 처리됐는지 모름
누가 상태를 바꿨는지 모름
잘못 변경된 상태를 추적할 수 없음
관리자별 처리량을 알 수 없음
상담 지연 원인을 찾기 어려움
고객 문의 시 설명 근거 부족
배포 후 상태 변경 버그 추적 어려움
➕ 3-1. 실제 상황 예시
상황:
고객이 분명히 연락받지 못했다고 문의
DB:
consults.status = CALLED
문제:
누가 언제 CALLED로 바꿨는지 기록 없음
결과:
운영자 확인 불가
고객 응대 근거 부족
➕ 3-2. 이력이 있다면
확인 가능:
2026-08-17 14:22
관리자 A
CALLING → CALLED
변경 사유: 부재중 2회 후 문자 안내
- 이력은 운영 신뢰도를 높입니다.
- 특히 1인 개발자가 만든 관리자 시스템에서는 이런 기록이 있느냐 없느냐가 완성도를 크게 가릅니다.
✅ 4. 상담 상태 이력 테이블 기본 구조
consult_status_histories
- id
- consultId
- fromStatus
- toStatus
- changedByAdminId
- reason
- memo
- createdAt
➕ 4-1. 컬럼 의미
| 컬럼 | 의미 |
|---|
id | 이력 고유 ID |
consultId | 상담 ID |
fromStatus | 변경 전 상태 |
toStatus | 변경 후 상태 |
changedByAdminId | 변경한 관리자 |
reason | 변경 사유 코드 |
memo | 변경 메모 |
createdAt | 변경 시각 |
➕ 4-2. 예시 데이터
| consultId | fromStatus | toStatus | changedByAdminId | reason | createdAt |
|---|
| 10 | NEW | CALLING | 1 | FIRST_CALL | 2026-08-17 10:20 |
| 10 | CALLING | CALLED | 1 | CALL_DONE | 2026-08-17 10:35 |
| 10 | CALLED | CONVERTED | 2 | OPENED | 2026-08-17 15:10 |
fromStatus와 toStatus가 있어야 상태 변경 흐름을 명확히 볼 수 있습니다.
changedByAdminId가 있어야 관리자 작업 추적이 가능합니다.
✅ 5. Prisma Schema 예시
model Consult {
id Int @id @default(autoincrement())
name String
phone String
status ConsultStatus @default(NEW)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
statusHistories ConsultStatusHistory[]
}
model ConsultStatusHistory {
id Int @id @default(autoincrement())
consultId Int
fromStatus ConsultStatus
toStatus ConsultStatus
changedByAdminId Int?
reason String?
memo String?
createdAt DateTime @default(now())
consult Consult @relation(fields: [consultId], references: [id])
changedByAdmin AdminUser? @relation(fields: [changedByAdminId], references: [id])
@@index([consultId, createdAt])
@@index([changedByAdminId, createdAt])
}
enum ConsultStatus {
NEW
CALLING
CALLED
PENDING
CONVERTED
CANCELED
DUPLICATED
}
➕ 5-1. 설계 포인트
상태 enum 사용
consultId + createdAt 인덱스
관리자 기준 조회를 위한 changedByAdminId 인덱스
시스템 자동 변경을 고려해 adminId nullable 가능
- 모든 상태 변경이 관리자에 의해 발생하는 것은 아닐 수 있습니다.
- 중복 자동 처리, 시스템 만료 처리, 외부 webhook 반영 같은 경우
changedByAdminId가 없을 수 있습니다.
✅ 6. 상태 변경은 Transaction으로 묶기
- 상태 변경에서는 현재 상태 업데이트와 이력 생성이 반드시 함께 일어나야 합니다.
- 둘 중 하나만 성공하면 데이터 정합성이 깨집니다.
1. 현재 상담 조회
2. 상태 전이 가능 여부 확인
3. consults.status 업데이트
4. consult_status_histories 생성
5. audit_logs 생성
6. commit
➕ 6-1. Prisma 예시
await prisma.$transaction(async (tx) => {
const consult = await tx.consult.findUniqueOrThrow({
where: { id: consultId },
});
validateStatusTransition(consult.status, nextStatus);
await tx.consult.update({
where: { id: consultId },
data: {
status: nextStatus,
},
});
await tx.consultStatusHistory.create({
data: {
consultId,
fromStatus: consult.status,
toStatus: nextStatus,
changedByAdminId: adminId,
reason,
memo,
},
});
});
➕ 6-2. 주의할 점
상태 업데이트와 이력 생성을 따로 실행하지 않기
이력 생성 실패 시 상태 변경도 rollback
상태 변경 전 현재 상태 확인
허용되지 않는 상태 전이 차단
- 상태 변경은 단순 update가 아닙니다.
- 운영 데이터에서는 “상태가 바뀐 이유와 경로”가 중요합니다.
✅ 7. 상태 전이 규칙
- 상태는 아무 방향으로나 바뀌면 안 됩니다.
- 운영 정책에 맞게 가능한 전이와 불가능한 전이를 정해야 합니다.
NEW
↓
CALLING
↓
CALLED
↓
CONVERTED
NEW → CANCELED
CALLING → PENDING
PENDING → CALLING
CALLED → CANCELED
➕ 7-1. 상태 전이 규칙 예시
const allowedTransitions: Record<ConsultStatus, ConsultStatus[]> = {
NEW: ['CALLING', 'CANCELED', 'DUPLICATED'],
CALLING: ['CALLED', 'PENDING', 'CANCELED'],
CALLED: ['CONVERTED', 'PENDING', 'CANCELED'],
PENDING: ['CALLING', 'CANCELED'],
CONVERTED: [],
CANCELED: [],
DUPLICATED: [],
};
➕ 7-2. 검증 함수 예시
function validateStatusTransition(
currentStatus: ConsultStatus,
nextStatus: ConsultStatus,
) {
const allowed = allowedTransitions[currentStatus];
if (!allowed.includes(nextStatus)) {
throw new BadRequestException(
`${currentStatus} 상태에서 ${nextStatus} 상태로 변경할 수 없습니다.`,
);
}
}
➕ 7-3. 실무 기준
최종 상태는 되돌릴 수 있는지 정책 필요
취소/중복 상태에서 다시 진행 가능한지 결정
관리자 권한별 허용 상태 변경 구분 가능
자동 상태 변경과 수동 상태 변경 구분
- 상태 전이 규칙은 개발자가 임의로 정하는 게 아니라 운영 방식과 맞아야 합니다.
- 운영자가 실제로 되돌리기 작업을 자주 한다면 예외 상태도 설계해야 합니다.
✅ 8. 상태 변경 사유 코드
- 상태 변경 이력에는 단순 메모보다 사유 코드를 함께 두면 분석이 쉬워집니다.
➕ 8-1. 상담 상태 변경 사유 예시
FIRST_CALL:
첫 연락 시도
CALL_DONE:
통화 완료
NO_ANSWER:
부재중
CUSTOMER_DELAY:
고객 보류 요청
PRICE_MISMATCH:
가격/혜택 불일치
DUPLICATED_REQUEST:
중복 신청
CUSTOMER_CANCEL:
고객 취소
OPENED:
개통 완료
➕ 8-2. 사유 코드가 필요한 이유
부재중 비율 분석
고객 취소 사유 분석
상담 지연 원인 분석
관리자 업무 기준 통일
메모 검색 의존도 감소
➕ 8-3. 설계 방식
간단하게 시작:
reason String
규칙이 많아지면:
enum 또는 status_change_reasons 테이블
운영자가 관리해야 하면:
관리자에서 사유 코드 관리
- 처음부터 너무 복잡하게 만들 필요는 없습니다.
- 다만 사유를 완전 자유 텍스트로만 두면 나중에 통계가 어렵습니다.
✅ 9. Audit Log란 무엇인가?
- Audit Log는 관리자 또는 시스템이 중요한 데이터를 변경한 기록입니다.
- 상태 변경 이력은 특정 도메인의 변경 이력이고, Audit Log는 전체 시스템의 주요 작업 기록입니다.
상태 변경 이력:
상담 상태가 어떻게 바뀌었는지
Audit Log:
관리자가 시스템에서 어떤 중요한 작업을 했는지
➕ 9-1. Audit Log가 필요한 작업
상담 상태 변경
상담 메모 수정
상품 등록/수정/삭제
지원금/가격 변경
배너 노출 변경
관리자 계정 생성/비활성화
권한 변경
엑셀 다운로드
알림톡 재발송
운영 환경 설정 변경
- Audit Log는 “운영자가 한 중요한 작업”을 추적하는 데 목적이 있습니다.
- 실수나 문제 발생 시 책임 추궁보다 원인 확인과 복구를 위한 기록입니다.
✅ 10. 상태 이력과 Audit Log의 차이
| 구분 | 상태 이력 | Audit Log |
|---|
| 목적 | 상태 흐름 추적 | 관리자 작업 추적 |
| 대상 | 상담, 주문, 작업 상태 | 시스템 주요 변경 |
| 예시 | NEW → CALLED | 상품 가격 수정 |
| 조회 위치 | 상담 상세 이력 | 관리자 활동 로그 |
| 구조 | 도메인별 테이블 | 공통 로그 테이블 |
➕ 10-1. 둘 다 남기는 경우
관리자가 상담 상태 변경
상태 이력:
consult_status_histories에 NEW → CALLED 기록
Audit Log:
audit_logs에 CONSULT_STATUS_UPDATE 기록
➕ 10-2. 왜 중복처럼 보여도 둘 다 필요할까?
상태 이력:
상담 상세에서 상태 흐름을 보기 좋음
Audit Log:
관리자별 전체 작업을 보기 좋음
- 목적이 다르면 같은 사건도 다른 형태로 기록할 수 있습니다.
- 다만 너무 많은 중복을 만들지 않도록 기준을 정해야 합니다.
✅ 11. Audit Log 기본 테이블 구조
audit_logs
- id
- actorType
- actorId
- action
- targetType
- targetId
- beforeValue
- afterValue
- ipAddress
- userAgent
- requestId
- createdAt
➕ 11-1. 컬럼 의미
| 컬럼 | 의미 |
|---|
actorType | 작업 주체 유형 |
actorId | 작업 주체 ID |
action | 수행한 작업 |
targetType | 대상 리소스 유형 |
targetId | 대상 리소스 ID |
beforeValue | 변경 전 값 |
afterValue | 변경 후 값 |
ipAddress | 요청 IP |
userAgent | 브라우저/클라이언트 정보 |
requestId | 요청 추적 ID |
createdAt | 발생 시각 |
➕ 11-2. actorType 예시
ADMIN:
관리자
SYSTEM:
시스템 자동 처리
USER:
고객 또는 일반 사용자
WORKER:
백그라운드 작업자
➕ 11-3. targetType 예시
CONSULT
PRODUCT
PRODUCT_OPTION
ADMIN_USER
BANNER
EXPORT_JOB
ALIMTALK
✅ 12. Prisma Audit Log 예시
model AuditLog {
id Int @id @default(autoincrement())
actorType ActorType
actorId Int?
action String
targetType String
targetId String?
beforeValue Json?
afterValue Json?
ipAddress String?
userAgent String?
requestId String?
createdAt DateTime @default(now())
@@index([actorType, actorId, createdAt])
@@index([targetType, targetId, createdAt])
@@index([action, createdAt])
@@index([requestId])
}
enum ActorType {
ADMIN
SYSTEM
USER
WORKER
}
➕ 12-1. 설계 포인트
여러 도메인을 기록하므로 targetType은 유연하게
beforeValue/afterValue는 Json으로 저장 가능
actorId는 시스템 작업 때문에 nullable 가능
검색을 위해 action, target, actor 기준 인덱스 필요
- Audit Log는 공통 테이블로 설계하는 경우가 많습니다.
- 단, 개인정보가 들어가지 않게 before/after 값을 선별해야 합니다.
✅ 13. Audit Log의 action 설계
action은 나중에 검색과 필터에 쓰이므로 명확해야 합니다.
- 단순히
UPDATE, DELETE만 넣으면 어떤 업무인지 알기 어렵습니다.
➕ 13-1. 좋은 action 예시
CONSULT_STATUS_UPDATE
CONSULT_MEMO_UPDATE
PRODUCT_CREATE
PRODUCT_UPDATE
PRODUCT_DELETE
PRODUCT_OPTION_PRICE_UPDATE
BANNER_VISIBILITY_UPDATE
ADMIN_USER_CREATE
ADMIN_USER_DISABLE
ADMIN_PERMISSION_UPDATE
EXPORT_DOWNLOAD_REQUEST
ALIMTALK_RESEND
➕ 13-2. 나쁜 action 예시
UPDATE
CHANGE
DONE
CLICK
OK
수정
처리
기타
➕ 13-3. 기준
도메인 + 작업 형태로 작성
검색하기 쉽게 대문자 snake case 사용
비즈니스 의미가 드러나야 함
너무 세분화하지 않되 모호하지 않게
- action 이름은 미래의 검색 키워드입니다.
- 나중에 “상품 가격 변경 누가 했어?”를 찾을 수 있어야 합니다.
✅ 14. beforeValue / afterValue 설계
- Audit Log에는 변경 전과 변경 후 값을 남길 수 있습니다.
- 다만 전체 row를 무조건 저장하면 개인정보와 불필요한 데이터가 많이 쌓입니다.
➕ 14-1. 좋은 예시
{
"beforeValue": {
"status": "CALLING"
},
"afterValue": {
"status": "CALLED"
}
}
상품 가격 변경:
{
"beforeValue": {
"price": 1200000,
"subsidy": 450000
},
"afterValue": {
"price": 1180000,
"subsidy": 500000
}
}
➕ 14-2. 피해야 할 예시
{
"beforeValue": {
"customerName": "김고객",
"phone": "01012345678",
"memo": "고객 개인정보가 포함된 긴 상담 메모",
"accessToken": "..."
}
}
➕ 14-3. 기준
변경된 필드만 저장
개인정보는 마스킹 또는 제외
토큰/Secret은 절대 저장 금지
긴 memo 전체 저장 주의
필요한 경우 memo 변경 여부만 기록
- Audit Log는 추적용이지 개인정보 백업 테이블이 아닙니다.
- 무엇을 남길지 선별하는 것이 중요합니다.
✅ 15. 개인정보와 Audit Log
- Audit Log는 강력하지만 개인정보를 잘못 저장하면 보안 위험이 커집니다.
- 상담 서비스에서는 이름, 전화번호, 상담 메모, IP, userAgent 등을 조심해야 합니다.
➕ 15-1. 남기면 위험한 값
전화번호 원본
고객 이름 원본
상담 메모 전체
신분증/주소/계좌번호
토큰/쿠키
Authorization header
DATABASE_URL
외부 API Secret
➕ 15-2. 마스킹 기준
전화번호:
010****1234
이름:
김*객 또는 저장하지 않음
IP:
필요 시 일부 마스킹 또는 접근권한 제한
memo:
전체 저장보다 변경 여부/요약만 저장
➕ 15-3. 실무 기준
운영 추적에 필요한 최소값만 저장
개인정보 원본은 원본 테이블에서 권한 통제
Audit Log에는 변경 사실 중심으로 기록
로그 접근 권한 제한
- Audit Log는 오래 보관될 수 있습니다.
- 그래서 원본 데이터보다 더 보수적으로 개인정보를 다뤄야 합니다.
✅ 16. 상태 이력과 Audit Log를 함께 저장하는 Transaction
- 중요한 변경 작업에서는 원본 변경, 상태 이력, Audit Log를 하나의 transaction으로 묶을 수 있습니다.
Transaction 시작
↓
consults.status 업데이트
↓
consult_status_histories 생성
↓
audit_logs 생성
↓
Commit
➕ 16-1. Prisma 예시
await prisma.$transaction(async (tx) => {
const consult = await tx.consult.findUniqueOrThrow({
where: { id: consultId },
});
validateStatusTransition(consult.status, nextStatus);
const updatedConsult = await tx.consult.update({
where: { id: consultId },
data: {
status: nextStatus,
},
});
await tx.consultStatusHistory.create({
data: {
consultId,
fromStatus: consult.status,
toStatus: nextStatus,
changedByAdminId: adminId,
reason,
memo,
},
});
await tx.auditLog.create({
data: {
actorType: 'ADMIN',
actorId: adminId,
action: 'CONSULT_STATUS_UPDATE',
targetType: 'CONSULT',
targetId: String(consultId),
beforeValue: {
status: consult.status,
},
afterValue: {
status: updatedConsult.status,
},
ipAddress,
userAgent,
requestId,
},
});
});
➕ 16-2. 주의
Audit Log 실패 시 원본 변경도 실패시킬지 정책 필요
중요 변경은 함께 rollback 권장
분석용 로그는 transaction 밖으로 분리 가능
before/after에 개인정보 넣지 않기
- 중요한 관리자 변경의 근거가 되는 로그라면 transaction 안에 넣는 것이 좋습니다.
- 단순 분석 이벤트라면 비동기 로그로 분리할 수 있습니다.
✅ 17. 주문 상태 이력 설계
- 상담보다 주문은 상태 정합성이 더 중요할 수 있습니다.
- 주문 상태는 고객 응대, 개통 처리, 배송, 정산과 연결될 수 있기 때문입니다.
➕ 17-1. 주문 상태 예시
RECEIVED:
주문 접수
VERIFYING:
정보 확인 중
APPROVED:
승인 완료
OPENING:
개통 진행 중
OPENED:
개통 완료
SHIPPING:
배송 중
DONE:
완료
CANCELED:
취소
FAILED:
실패
➕ 17-2. 주문 상태 이력 테이블
order_status_histories
- id
- orderId
- fromStatus
- toStatus
- changedByAdminId
- reason
- memo
- createdAt
➕ 17-3. 상담보다 더 조심할 점
개통/배송/정산과 연결 가능
외부 시스템 상태와 불일치 가능
상태 되돌리기 정책 중요
고객 안내 메시지 발송과 연결 가능
- 주문 상태는 단순 관리자 표시값이 아닐 수 있습니다.
- 외부 시스템이나 고객 안내와 연결된다면 상태 변경을 더 엄격히 관리해야 합니다.
✅ 18. ExportJob 상태 이력
- 엑셀 다운로드나 대량 작업을 Queue/Worker로 처리한다면 Job 상태 이력도 유용합니다.
- 특히 실패 원인을 추적하기 좋습니다.
➕ 18-1. ExportJob 상태 예시
PENDING:
작업 대기
PROCESSING:
작업 중
DONE:
완료
FAILED:
실패
EXPIRED:
다운로드 만료
➕ 18-2. 테이블 구조
export_jobs
- id
- status
- requestedByAdminId
- fileUrl
- errorMessage
- createdAt
- updatedAt
export_job_status_histories
- id
- exportJobId
- fromStatus
- toStatus
- message
- createdAt
➕ 18-3. 필요한 이유
작업 실패 원인 추적
Worker 재시도 이력 확인
대량 다운로드 요청 기록
관리자 문의 대응
- 처음에는
export_jobs.status와 errorMessage만으로 시작해도 됩니다.
- 작업 실패가 잦거나 운영자가 확인해야 한다면 이력 테이블을 추가하면 좋습니다.
✅ 19. 알림톡/SMS 발송 이력
- 알림톡이나 SMS는 외부 API와 연결되기 때문에 발송 이력을 남기는 것이 좋습니다.
- 발송 성공/실패, 재시도, 수신 대상, 템플릿 등을 기록해야 합니다.
➕ 19-1. 발송 이력 테이블
notification_logs
- id
- targetType
- targetId
- channel
- templateCode
- recipientMasked
- status
- providerMessageId
- errorCode
- errorMessage
- requestedAt
- sentAt
➕ 19-2. status 예시
PENDING
SENT
FAILED
RETRYING
CANCELED
➕ 19-3. 주의
수신 전화번호 원본 저장 주의
외부 API 응답에 개인정보 포함 여부 확인
실패 사유는 남기되 Secret은 제외
재시도 횟수 관리
- 알림 발송은 성공보다 실패 추적이 중요합니다.
- 상담 신청 저장과 알림 발송은 분리하되, 발송 상태는 추적 가능해야 합니다.
✅ 20. 관리자 화면에서 이력을 어떻게 보여줄까?
- 이력은 DB에만 있어서는 부족합니다.
- 운영자가 확인할 수 있는 화면으로 제공해야 의미가 있습니다.
➕ 20-1. 상담 상세 이력 UI
상담 상세 모달
├─ 기본 정보
├─ 상담 메모
├─ 상태 변경
└─ 이력 탭
- 10:20 신규 → 연락 중 / 관리자 A
- 10:35 연락 중 → 연락 완료 / 관리자 A
- 15:10 연락 완료 → 개통 완료 / 관리자 B
➕ 20-2. 관리자 활동 로그 UI
관리자 활동 로그
- 관리자
- 작업 유형
- 대상
- 변경 전/후
- 시간
- IP
➕ 20-3. 우선순위
1순위:
상담 상세에서 상태 이력 보기
2순위:
관리자별 작업 로그 보기
3순위:
상품/지원금 변경 이력 보기
4순위:
엑셀 다운로드/알림 재발송 기록 보기
- 처음부터 복잡한 로그 관리자 화면이 필요하지는 않습니다.
- 상담 상세 안에서 상태 변경 이력만 보여줘도 운영 품질이 크게 좋아집니다.
✅ 21. 이력 조회 성능과 인덱스
- 이력 테이블은 시간이 지날수록 계속 쌓입니다.
- 조회 패턴에 맞게 인덱스를 준비해야 합니다.
➕ 21-1. 상태 이력 인덱스 후보
@@index([consultId, createdAt])
@@index([changedByAdminId, createdAt])
@@index([toStatus, createdAt])
➕ 21-2. Audit Log 인덱스 후보
@@index([actorType, actorId, createdAt])
@@index([targetType, targetId, createdAt])
@@index([action, createdAt])
@@index([requestId])
➕ 21-3. 조회 패턴 기준
상담 상세에서 해당 상담 이력 조회:
consultId + createdAt
관리자별 작업 조회:
actorId + createdAt
특정 대상의 변경 로그 조회:
targetType + targetId + createdAt
장애 분석:
requestId
- 인덱스는 “자주 조회하는 조건” 기준으로 잡아야 합니다.
- 이력 테이블은 insert가 많기 때문에 인덱스를 너무 많이 만들면 쓰기 비용이 증가합니다.
✅ 22. 이력 데이터 보존 정책
- 이력 데이터는 계속 쌓이기 때문에 보존 정책이 필요합니다.
- 다만 상담/주문/관리자 변경 이력은 운영상 중요한 데이터이므로 쉽게 삭제하면 안 됩니다.
➕ 22-1. 보존 정책 후보
상담 상태 이력:
장기 보관
주문 상태 이력:
장기 보관
Audit Log:
중요 작업은 장기 보관
Access Log:
일정 기간 후 삭제 가능
Debug Log:
짧게 보관
알림 발송 로그:
운영/분쟁 기준에 따라 보관 기간 설정
➕ 22-2. 삭제 전 확인할 것
고객 응대에 필요한가?
정산/계약/분쟁 대응에 필요한가?
법적/회사 정책상 보관 기간이 있는가?
개인정보 포함 여부는 어떤가?
삭제 후 복구 가능한가?
- 모든 로그를 영원히 보관하는 것도 답은 아닙니다.
- 하지만 상태 이력과 Audit Log는 운영 근거가 되므로 보수적으로 다뤄야 합니다.
✅ 23. 상태 이력과 정합성 점검 쿼리
- 현재 상태와 마지막 상태 이력이 일치하는지 점검할 수 있어야 합니다.
- 이런 쿼리는 장애 대응이나 데이터 검증에 도움이 됩니다.
➕ 23-1. 개념
consults.status
=
해당 consult의 마지막 consult_status_histories.toStatus
➕ 23-2. SQL 예시
SELECT c.id, c.status, h.to_status AS last_history_status
FROM consults c
LEFT JOIN LATERAL (
SELECT to_status
FROM consult_status_histories h
WHERE h.consult_id = c.id
ORDER BY h.created_at DESC
LIMIT 1
) h ON true
WHERE h.to_status IS NOT NULL
AND c.status <> h.to_status;
➕ 23-3. 점검 대상
현재 상태와 마지막 이력 불일치
이력 없는 상태 변경 row
존재하지 않는 관리자 ID
존재하지 않는 상담 ID
비정상 상태 전이
- 정합성 점검 쿼리는 운영 안정성의 좋은 방어선입니다.
- 주기적으로 확인하거나 장애 후 확인하면 좋습니다.
✅ 24. 실무 체크리스트
➕ 24-1. 상태 이력 체크리스트
- 현재 상태 컬럼이 원본 테이블에 있는가?
- 상태 변경 이력 테이블이 있는가?
fromStatus, toStatus를 모두 저장하는가?
- 변경한 관리자 ID를 저장하는가?
- 시스템 자동 변경도 표현 가능한가?
- 상태 변경 사유와 메모가 필요한가?
- 상태 변경과 이력 저장이 transaction으로 묶여 있는가?
- 상태 전이 규칙이 있는가?
➕ 24-2. Audit Log 체크리스트
- 관리자 주요 작업이 Audit Log 대상인지 정리했는가?
- action 이름이 명확한가?
- targetType/targetId가 저장되는가?
- actorType/actorId가 저장되는가?
- beforeValue/afterValue에 변경된 필드만 저장하는가?
- 개인정보와 Secret이 저장되지 않는가?
- requestId가 저장되는가?
- 조회 패턴에 맞는 인덱스가 있는가?
➕ 24-3. 관리자 화면 체크리스트
- 상담 상세에서 상태 변경 이력을 볼 수 있는가?
- 상태 변경 시간과 관리자를 볼 수 있는가?
- 변경 사유와 메모를 볼 수 있는가?
- 관리자별 작업 로그가 필요한가?
- 상품/지원금 변경 이력을 볼 수 있는가?
- 엑셀 다운로드 이력이 필요한가?
- 알림톡 재발송 이력이 필요한가?
- 개인정보 노출 범위가 제한되어 있는가?
➕ 24-4. 정합성 체크리스트
- 현재 상태와 마지막 이력이 일치하는가?
- 상태 변경 실패 시 전체 rollback되는가?
- 이력 없는 상태 변경이 발생하지 않는가?
- 잘못된 상태 전이가 차단되는가?
- 동시에 상태 변경 시 충돌 처리가 되는가?
- Audit Log 실패 시 정책이 정해져 있는가?
- 정합성 점검 쿼리가 있는가?
- 장애 후 이력 데이터를 확인할 수 있는가?
✅ 25. AI에게 상태 이력/Audit Log 설계를 점검시킬 때 좋은 질문법
NestJS + Prisma + PostgreSQL 기반 온라인 휴대폰 판매몰에서 상담/주문 상태 변경 이력과 Audit Log를 설계하려고 해.
서비스 상황:
1. 고객은 상품 상세에서 상담 신청을 함
2. 관리자는 상담 목록에서 상태를 변경함
3. 상담 상태는 NEW, CALLING, CALLED, PENDING, CONVERTED, CANCELED, DUPLICATED가 있음
4. 상태 변경 시 누가, 언제, 어떤 상태에서 어떤 상태로 바꿨는지 남기고 싶음
5. 상담 상세 모달에서 상태 변경 이력을 보여주고 싶음
6. 상품 가격/지원금 변경, 엑셀 다운로드, 관리자 권한 변경 같은 주요 작업도 Audit Log로 남기고 싶음
7. 개인정보와 Secret은 로그에 남기면 안 됨
8. 상태 변경과 이력 저장은 transaction으로 묶고 싶음
9. 관리자 여러 명이 동시에 상태를 바꿀 수 있음
요청:
- 상담 상태 이력 테이블 설계
- 주문 상태 이력 테이블 설계
- Audit Log 테이블 설계
- Prisma Schema 예시
- 상태 전이 규칙 예시
- Transaction 처리 예시
- beforeValue/afterValue 저장 기준
- 개인정보 마스킹 기준
- 관리자 화면에서 이력을 보여주는 UI 구조
- 조회 성능을 위한 index 후보
- 정합성 점검 SQL 후보
를 실무 기준으로 정리해줘.
➕ 25-1. AI 답변 검증 기준
현재 상태와 상태 이력을 구분하는가?
상태 변경과 이력 저장을 transaction으로 묶는가?
fromStatus/toStatus를 모두 저장하는가?
changedByAdminId와 시스템 변경을 고려하는가?
Audit Log와 상태 이력의 목적 차이를 설명하는가?
beforeValue/afterValue에 개인정보를 무분별하게 넣지 않는가?
관리자 화면에서 실제로 볼 수 있는 구조를 제안하는가?
정합성 점검과 인덱스까지 고려하는가?
📌 요약
- 상태 변경 이력은 데이터가 언제, 누구에 의해, 어떤 상태에서 어떤 상태로 바뀌었는지 기록하는 구조입니다.
- 현재 상태는
consults.status처럼 원본 테이블에 두고, 변경 과정은 consult_status_histories 같은 이력 테이블에 따로 저장하는 방식이 실무적으로 좋습니다.
- 상태 변경 이력이 없으면 상담 처리 시간, 관리자 작업자, 잘못된 상태 변경, 고객 문의 대응 근거를 확인하기 어렵습니다.
- 상태 변경 시에는
consults.status 업데이트와 consult_status_histories 생성, 필요하면 audit_logs 생성을 하나의 transaction으로 묶어야 합니다.
- 상태 전이 규칙을 두면
CANCELED → CONVERTED처럼 운영상 말이 안 되는 변경을 막을 수 있습니다.
- 상태 변경 사유 코드를 함께 저장하면 부재중, 고객 취소, 중복 신청, 개통 완료 같은 운영 원인을 나중에 분석하기 쉽습니다.
- Audit Log는 상태 이력보다 더 넓은 개념으로, 관리자나 시스템이 수행한 중요한 작업을 기록하는 공통 로그입니다.
- Audit Log에는 actor, action, target, beforeValue, afterValue, requestId, createdAt 등을 저장할 수 있지만, 개인정보와 Secret은 반드시 제외하거나 마스킹해야 합니다.
- 상담 상세에서는 상태 변경 이력을 먼저 보여주고, 필요하면 관리자별 작업 로그나 상품/지원금 변경 이력을 별도 화면으로 확장하는 것이 좋습니다.
- 이력 테이블은 계속 쌓이므로
consultId + createdAt, actorId + createdAt, targetType + targetId + createdAt 같은 조회 패턴 기반 인덱스를 준비해야 합니다.
- 현재 상태와 마지막 이력이 일치하는지 확인하는 정합성 점검 쿼리를 만들어두면 운영 장애나 데이터 오류를 추적하는 데 도움이 됩니다.