트랜잭션·데이터 정합성 · Data Modeling

재발급 문서를 덮어쓰지 않고 이력 관계로 관리한 이유

공인 성적서의 원본·재발급·폐기 상태를 덮어쓰기 대신 발행 순번과 이력 관계로 모델링하고, 진위확인·동시 재발급까지 검증한 과정을 정리합니다.

Data ModelingOracleAudit TrailConcurrencyEnterprise System

문제의 출발점

이 글은 2024년에 완료된 성적서 재발급·폐기 이력 관리 사례다. 2026년에 진행 중인 QC Hold/Release 설계 업무와 서로 다른 경력이며, 구현·테스트·성과를 섞어 설명하지 않는다.

문서를 다시 발급하는 기능은 파일을 한 번 더 만드는 문제처럼 보인다. 하지만 발행 사실과 진위 여부를 추적해야 하는 문서는 “최신 파일 하나”만 남겨서는 충분하지 않다.

  • 최초 발급본과 재발급본의 관계
  • 기존 문서의 유효·폐기 상태
  • 재발급·폐기의 주체, 시점과 사유
  • 과거 진위확인 코드에 표시할 상태
  • 동시 재발급의 중복 방지

현재 파일을 새 파일로 덮어쓰면 이 질문에 답할 수 없다. 그래서 원본·재발급·폐기 관계가 데이터에 남는 구조로 설계했다.

덮어쓰기 모델의 한계

아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.

DOCUMENT_KEY | FILE_KEY | ISSUED_AT | STATUS

재발급 시 같은 행을 갱신하면 이전 식별값, 파일, 발행 시점과 진위확인 상태가 사라진다. 별도 로그에 변경 전후 문자열을 남기는 방식도 실제 문서와 이력의 관계를 매번 해석해야 한다.

이력이 핵심 도메인 데이터라면 문서 버전을 독립된 행으로 관리하는 편이 명확했다.

발행 순번을 문서 정체성에 포함

아래 DDL은 실제 운영 스키마가 아니라 개념을 설명하기 위한 공개용 예시다. 실제 테이블·컬럼·제약 이름, 타입 길이와 인덱스는 재현하지 않는다.

아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.

CREATE TABLE DOCUMENT_ISSUE (
    TENANT_KEY       VARCHAR2(20) NOT NULL,
    BUSINESS_KEY     VARCHAR2(40) NOT NULL,
    ISSUE_SEQUENCE   NUMBER       NOT NULL,
    DOCUMENT_KEY     VARCHAR2(40),
    FILE_KEY         VARCHAR2(40),
    STATUS           VARCHAR2(20),
    STATUS_REASON    VARCHAR2(200),
    CHANGED_AT       DATE,
    PRIMARY KEY (TENANT_KEY, BUSINESS_KEY, ISSUE_SEQUENCE)
);

기존 데이터는 최초 발행본을 의미하는 순번으로 이관하고, 재발급본은 같은 업무 키를 공유하면서 다른 발행 순번을 갖게 했다.

  • 원본과 재발급본이 같은 업무 대상에 속한다는 사실이 키에 드러난다.
  • 파일과 진위확인 정보를 발행 단위로 보존할 수 있다.
  • 원본을 삭제하거나 덮어쓰지 않고 현재 유효한 문서를 결정할 수 있다.
  • 재발급 이력과 감사 추적을 같은 데이터에서 구성할 수 있다.

표시 규칙과 상태 모델을 함께 사용

재발급 문서는 기존 식별 규칙과 구분되는 표시 규칙을 사용했다. 실제 접미사와 생성 규칙은 공개 글에서 재현하지 않는다.

표시 규칙은 화면 장식이 아니라 다음 처리와 연결됐다.

  • 재발급 여부 검색
  • 상세 화면의 발행 이력
  • 원본·재발급·폐기 문서의 진위확인 메시지
  • 반복 재발급 시 신규 행 생성 여부 판단

삭제 대신 폐기 상태를 기록

이미 발행된 문서를 물리적으로 삭제하면 과거 고객이 보유한 문서와 시스템 기록을 연결할 수 없다. 따라서 폐기는 삭제가 아니라 상태 변경으로 관리했다.

아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.

STATUS
STATUS_REASON
CHANGED_BY
CHANGED_AT

폐기된 코드로 조회하면 단순히 “문서 없음”을 반환하지 않고, 실제 존재했던 문서가 더 이상 유효하지 않다는 상태와 후속 문서 관계를 안내했다.

동시 재발급은 별도 시나리오로 검증

두 사용자가 같은 원본을 거의 동시에 선택하면 각각 “아직 재발급본이 없다”고 판단할 수 있다. 버튼 중복 클릭 방지만으로 해결하지 않고 애플리케이션과 DB 처리 경계에서 중복 생성을 막고 별도 테스트했다.

공개용 개념 모델은 다음과 같다.

아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.

public DocumentIssue reissue(DocumentKey documentKey) {
    DocumentIssue current = repository.lockCurrent(documentKey);
    if (current.hasActiveReissue()) {
        return current.activeReissue();
    }
    return repository.insertNextIssue(current);
}

이 Java 모델도 실제 운영 클래스나 메서드를 옮긴 것이 아닌 개념 예시다.

검증 시나리오

  • 폐기 여부·재발급 여부 검색
  • 최초 재발급 시 신규 발행 이력 생성
  • 반복 재발급에서 불필요한 행이 추가되지 않는지 확인
  • 재발급 문서 폐기
  • 동시 재발급 시 중복 생성 방지
  • 상세 화면의 발행 이력
  • 원본·재발급·폐기 상태별 진위확인 문구
  • 발급 파일의 표시명과 저장명 정합성
  • 최대 50건 일괄 처리

2024년 기능은 동시성 테스트와 사용자 확인을 거쳐 운영에 반영됐다. 2026년 QC Hold/Release는 별도의 진행 중 설계 업무이며 이 글의 DDL, 구현과 검증 범위에 포함하지 않는다.

현재 상태와 전체 이력을 함께 조회

현재 유효한 문서는 발행 순번과 상태를 기준으로 선택하고, 감사 이력은 같은 업무 키의 발행 행을 순서대로 조회한다.

아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.

SELECT *
FROM DOCUMENT_ISSUE
WHERE TENANT_KEY = :tenantKey
  AND BUSINESS_KEY = :businessKey
ORDER BY ISSUE_SEQUENCE DESC;

현재값을 별도 컬럼에 중복 저장하면 갱신 순서와 정합성 규칙을 추가로 관리해야 한다. 이력형 모델은 조회가 조금 복잡해지는 대신 현재 상태가 만들어진 과정을 설명할 수 있다.

정리

감사 추적이 필요한 시스템에서는 이력이 부가 기능이 아니다.

  1. 발행된 문서는 덮어쓰거나 삭제하지 않는다.
  2. 발행 순번을 문서 정체성에 포함한다.
  3. 원본·재발급·폐기를 다른 상태와 관계로 모델링한다.
  4. 진위확인 화면도 같은 상태 모델을 사용한다.
  5. 동시 요청과 반복 실행을 별도 시나리오로 검증한다.