이력데이터모델링으로 시간에 따른 데이터 변화를 관리하는 법

이력데이터모델링의 스냅샷과 이벤트 기반 관리 방식, 시간 처리와 조회 성능을 고려한 데이터 설계 방법을 정리한다.

2026-08-14 · 최초 발행 2025-08-10

과거 상태를 다시 구성해야 하는 데이터

데이터가 바뀐 뒤에도 특정 시점의 상태를 확인해야 한다면 현재 값만 남기는 모델로는 부족하다. 이력데이터모델링은 변경 과정을 추적·보존해 과거 상태를 재구성하고 분석할 수 있도록 만드는 데이터 설계 방식이다.

규제 준수와 감사뿐 아니라 데이터 분석, 의사결정 지원에서도 이력은 중요한 근거가 된다. 시간 축이 추가되면서 모델의 복잡도와 데이터 규모가 커지므로, 기록 대상과 보관 방식을 처음부터 정해야 한다.

이력 데이터에는 몇 가지 공통적인 성질이 있다.

  • 같은 구조의 컬럼 값이 시간에 따라 변경된다.
  • 지속적인 변경으로 대량의 데이터가 누적된다.
  • 이력 데이터의 증가는 시스템 성능에 상당한 영향을 준다.
  • 시간 차원이 더해져 데이터 모델의 복잡성이 증가한다.

현재 데이터와 과거 데이터를 분리하는 방식

이력 테이블의 구분 방식은 현재 상태를 어디에 둘 것인지, 과거 상태를 어느 수준까지 복원해야 하는지에 따라 달라진다.

한 테이블에서 유효 기간을 관리하는 내부 스냅샷 이력

현재 데이터와 과거 이력을 하나의 테이블에 함께 보관하고, 시작일자와 종료일자로 각 레코드의 유효 구간을 구분한다.

CUSTOMERintcustomer_idstringnamestringaddressdatevalid_fromdatevalid_tobooleanis_current

단일 테이블에서 모든 이력을 다룰 수 있어 조회 쿼리는 단순하다. 반면 테이블 크기가 빠르게 커지고, 현재 데이터와 이력 데이터가 섞여 관리된다.

현재 테이블과 전체 이력을 나누는 스냅샷 전체 이력

현재 상태는 마스터 테이블에 두고, 이력은 별도 테이블에 전체 스냅샷으로 적재하는 형태다.

has historyCUSTOMERintcustomer_idstringnamestringaddressCUSTOMER_HISTORYinthistory_idintcustomer_idstringnamestringaddressdatevalid_fromdatevalid_to

현재 데이터 조회 성능을 높이고 운영 데이터와 이력 데이터를 명확히 분리할 수 있다. 대신 두 테이블을 조인해야 하며 데이터 중복이 발생한다.

변경된 값만 남기는 스냅샷 과거 이력

최신 상태는 마스터 테이블에서 유지하고, 바뀐 데이터만 이력 테이블에 기록한다.

has price changesPRODUCTintproduct_idstringnamedecimalpricedatelast_updatedPRODUCT_PRICE_HISTORYinthistory_idintproduct_iddecimalold_pricedatechange_date

변경 속성만 관리하므로 저장 공간을 효율적으로 쓰고 이력 테이블 크기를 줄일 수 있다. 그러나 특정 시점의 전체 상태를 되살리는 작업이 복잡해지며, 여러 속성이 함께 바뀌면 모델도 복잡해진다.

연관 엔티티를 함께 기록하는 스냅샷 군집 전체 이력

관련된 여러 엔티티의 변화를 하나의 이력 구조로 통합 관리하는 방식이다.

has position historyhas employee historyEMPLOYEE_POSITION_HISTORYinthistory_idintemployee_idintdepartment_idstringposition_titledecimalsalarydatevalid_fromdatevalid_toEMPLOYEEDEPARTMENT

연관 엔티티 간의 이력을 일관되게 관리하고 복합적인 변경을 추적하기 쉽다. 그만큼 테이블 설계가 복잡해지고, 대용량 데이터를 처리할 때 성능 문제가 생길 수 있다.

기록이 생성되는 시점에 따른 구분

이력은 데이터 변경 자체를 남길 수도 있고, 업무 이벤트나 프로세스 진행 상태를 기록할 수도 있다.

변경 이력

마스터 데이터가 수정될 때마다 이력 레코드를 만드는 방식이다. 예를 들어 고객 주소가 바뀌면 기존 주소의 유효 기간을 닫고 새 주소의 이력을 추가한다.

-- 고객 주소 변경 시
BEGIN TRANSACTION;

  -- 기존 이력 종료
  UPDATE CUSTOMER_HISTORY
  SET valid_to = CURRENT_TIMESTAMP - 1 second
  WHERE customer_id = 1001 AND valid_to = '9999-12-31';

  -- 새 이력 추가
  INSERT INTO CUSTOMER_HISTORY (customer_id, name, address, valid_from, valid_to)
  SELECT customer_id, name, 'New Address', CURRENT_TIMESTAMP, '9999-12-31'
  FROM CUSTOMER
  WHERE customer_id = 1001;

  -- 마스터 테이블 업데이트
  UPDATE CUSTOMER
  SET address = 'New Address'
  WHERE customer_id = 1001;

COMMIT;

발생 이력

주문, 결제, 배송처럼 업무 과정에서 일어나는 모든 이벤트를 기록하는 방식이다.

주문 생성결제 대기결제 완료배송 준비배송배송 완료이력 생성: 주문 생성이력 생성: 결제 대기이력 생성: 결제 완료이력 생성: 배송 준비이력 생성: 배송이력 생성: 배송 완료

진행 이력

워크플로우의 상태 변화를 추적하는 이력이다. 보험 청구 심사처럼 단계별 처리 상태를 남겨야 하는 프로세스에 사용한다.

지급승인심사접수고객지급승인심사접수고객상태: 접수됨 (이력 생성)상태: 심사중 (이력 생성)상태: 승인됨 (이력 생성)상태: 지급완료 (이력 생성)청구 제출심사 요청승인 요청지급 지시

금융과 인사 데이터에서의 모델 예시

은행 계좌는 거래와 잔액 변화를 함께 관리해야 한다. 계좌의 현재 잔액과 거래 내역, 기준일별 잔액 이력을 분리하면 각각의 조회 목적을 반영할 수 있다.

hashasACCOUNTintaccount_idstringaccount_numberdecimalcurrent_balancedatelast_updatedTRANSACTIONinttransaction_idintaccount_idstringtransaction_typedecimalamountdatetimetransaction_datestringdescriptionBALANCE_HISTORYinthistory_idintaccount_iddecimalbalancedateas_of_date

인사 정보에서는 직원의 직위, 급여, 부서 변경을 현재 정보와 과거 정보로 나눠 관리할 수 있다.

hashasEMPLOYEEintemployee_idstringnamestringemaildatehire_dateCURRENT_POSITIONintposition_idintemployee_idintdepartment_idstringjob_titledecimalsalarydatestart_datePOSITION_HISTORYinthistory_idintemployee_idintdepartment_idstringjob_titledecimalsalarydatestart_datedateend_date

이력 테이블을 운영할 때 확인할 조건

날짜 또는 시간 기준의 파티셔닝, 시간 범위 조회를 위한 인덱스, 오래된 이력의 아카이빙은 데이터 누적에 대비하는 기본 전략이다.

-- 이력 테이블 파티셔닝 예시 (PostgreSQL)
CREATE TABLE customer_history (
    history_id SERIAL,
    customer_id INTEGER,
    name VARCHAR(100),
    address VARCHAR(255),
    valid_from TIMESTAMP,
    valid_to TIMESTAMP
) PARTITION BY RANGE (valid_from);

-- 분기별 파티션 생성
CREATE TABLE customer_history_2023q1 PARTITION OF customer_history
    FOR VALUES FROM ('2023-01-01') TO ('2023-04-01');
CREATE TABLE customer_history_2023q2 PARTITION OF customer_history
    FOR VALUES FROM ('2023-04-01') TO ('2023-07-01');

마스터 테이블과 이력 테이블은 트랜잭션 안에서 함께 갱신해야 한다. 데이터가 변경될 때 이력을 자동 생성해야 한다면 트리거를 사용할 수 있다.

-- 이력 생성 트리거 예시 (MySQL)
DELIMITER //
CREATE TRIGGER customer_update_trigger
AFTER UPDATE ON customer
FOR EACH ROW
BEGIN
    INSERT INTO customer_history (customer_id, name, address, valid_from, valid_to)
    VALUES (OLD.customer_id, OLD.name, OLD.address, OLD.valid_from, NOW());

    UPDATE customer
    SET valid_from = NOW()
    WHERE customer_id = NEW.customer_id;
END; //
DELIMITER ;

시간은 valid_from, valid_to 같은 컬럼으로 유효 기간을 표현한다. 국제 서비스에서는 시간대를 고려해야 하며, 비즈니스 시간과 시스템 시간도 구분해 관리해야 한다.

조회 패턴 역시 모델 설계의 일부다. 특정 시점의 상태, 고객별 전체 변경 이력, 기간 내 변경 대상은 서로 다른 조건과 정렬을 요구한다.

-- 특정 시점의 고객 상태 조회
SELECT c.*
FROM customer_history c
WHERE c.customer_id = 1001
  AND '2023-06-15' BETWEEN c.valid_from AND c.valid_to;

-- 특정 고객의 전체 변경 이력 조회
SELECT c.*
FROM customer_history c
WHERE c.customer_id = 1001
ORDER BY c.valid_from;

-- 특정 기간 동안 변경된 고객 목록
SELECT DISTINCT c.customer_id
FROM customer_history c
WHERE c.valid_from BETWEEN '2023-01-01' AND '2023-12-31';

이벤트와 변경 로그를 이력으로 활용하는 접근

이벤트 소싱은 상태 변경을 모두 이벤트로 저장해 전체 이력을 보존하는 패턴이다. 저장된 이벤트는 상태 재구성, 분석, 알림 처리에 활용할 수 있다.

이벤트 생성이벤트 저장소이벤트 구독상태 재구성분석 처리알림 처리

CDC(Change Data Capture)는 데이터베이스의 변경 사항을 실시간으로 감지하고 이력으로 관리하는 기술이다. 변경 로그는 ETL 프로세스를 거쳐 타겟 DB 또는 데이터 웨어하우스로 전달하거나, 실시간 분석에 사용할 수 있다.

소스 DBCDC 엔진변경 로그ETL 프로세스타겟 DB/데이터 웨어하우스실시간 분석

시계열 데이터베이스는 시간에 따른 데이터 변화를 저장하고 조회하는 데 특화된 시스템이다. 압축 저장, 집계 함수, 시간 기반 쿼리를 통해 대시보드와 알림에 데이터를 제공할 수 있다.

시계열 데이터 수집시계열 DB압축 저장집계 함수시간 기반 쿼리대시보드알림

이력데이터모델링은 단순히 과거 데이터를 쌓는 일이 아니다. 시간에 따른 시스템 상태를 정확히 추적하고 재구성하도록 하며, 규제 준수와 감사, 데이터 분석, 의사결정 지원에 필요한 기반을 제공한다. 업무 성격과 요구사항에 맞는 이력 유형을 선택하고 성능과 데이터 일관성을 함께 설계해야 한다.

이력데이터모델링데이터 모델링스냅샷 패턴이벤트 소싱CDC