Home 선분 이력 관리
Post
Cancel

선분 이력 관리

글을 작성하게 된 계기


회사에서 가맹점 매출구간을 기록하는 작업을 하며, 선분 이력 관리에 대해 알게 되었고, 이를 정리하기 위해 글을 작성하게 되었습니다.





1. 왜 고려하게 됐을까?


현재 각 가맹점 매출 구간을 기록 하는 작업을 하고 있습니다. 가맹점 매출 구간이란, 가맹점이 특정 시기(년/반기)에 어떤 매출 구간에 속하는지를 나타내는 것입니다. 예를 들어, 2024년 상반기에는 영세, 2024년 하반기에는 중소 1등과 같이요. 이런 작업을 하다 보니 히스토리를 어떻게 잘 관리할지 에 대한 고민이 들었습니다.

여신금융협회에서 매년 1월, 7월 모든 카드 가맹점의 매출액을 확인하고 매출 구간별로 등급을 나눕니다.



작업 자체는 간단하지만 반기마다 수 백만 건의 데이터가 추가 되고, 자주 사용되는 테이블 이다보니 이래저래 신경쓸 부분이 꽤 많았기 때문입니다. 이를 고민하던 중, 회사 코드에서 특정 테이블의 데이터를 선분 이력 으로 관리하는 것을 보게 됐고, 이에 관심을 가지게 되었습니다.

  • 반기마다 수 백만 건의 데이터가 추가
  • 자주 사용되는 테이블







2. 선분 이력 관리란?


선분 이력이란, 데이터를 점이 아닌 선 단위로 저장변화 기간을 명시적으로 관리 하는 방식입니다. 단순히 상태가 언제 발생했는지를 기록하는 것이 아니라, 그 상태가 언제부터 언제까지 유효했는지를 명시합니다. 이를 통해 과거 특정 시점의 상태를 기록/조회 하고, 상태 변경 이력을 체계적으로 관리할 수 있습니다.

  • 변경 주기가 길고 중요한 상태 값
  • 과거 시점 기준의 분석이 자주 필요한 데이터
  • 계약/가격/수수료/등급 등 이력 추적이 필수적인 비즈니스 규칙
  • 배송 상태 이력 관리



일반적으로 쌓이는 로그나 상태 데이터는 점 이력입니다. 즉, 단순히 특정 시점어떤 상태 였는지를 보여주는 것이죠. 예를 들어 가맹점 A의 영업 상태가 3월부터 5월까지 성업이고, 5월부터 폐업이라면 기존 점 이력은 매달 상태를 중복으로 기록합니다.

1
2
3
4
# 가맹점 A의 상태 변화: 점 이력 (매월 상태 기록)
# 2019-03   2019-04   2019-05   2019-06
#   성업      성업      폐업      폐업
     ●        ●        ●        ●



반면, 선분 이력은 기간기준 으로 묶어 상태 를 표현하는 것이죠. 개념 자체는 간단하죠?

1
2
3
4
5
6
7
# 가맹점 A의 상태 변화: 선분 이력 (기간 단위로 묶음)
# 2019-03 ~ 2019-05 : 성업
# 2019-05 ~ 이후     : 폐업

     ●───────●────────────>
    3월      5월          현재
    성업     폐업



이런 선분 이력은 상태 변화가 잦고, 과거 시점 기준의 분석이 자주 필요한 데이터 에 적합합니다. 이는 많은 부분에서 활용할 수 있는데, 특히 다음과 같은 경우 유용하게 사용할 수 있습니다.

  • 수수료/가격 이력
  • 법적/회계 이력 변경 추적
  • 권한/역할 이력 추적
  • 회원 등급/혜택 이력







3. 고려할 점


간단하지만 데이터가 많을 때, 고려할 점도 존재하는데요, 크게 필요한 정보만 저장 해야 한다는 점, 데이터 참조 방향 입니다. 이에 대해 살펴보겠습니다.

  1. 반드시 필요한 정보만 저장
  2. 데이터 참조 방향



3-1. 필요한 정보만 저장

선분 이력을 관리할 때, 필요한 정보만 저장해야 합니다. 예를 들어, 가맹점의 매출 구간을 관리할 때, 가맹점 테이블의 필드가 수십 개라면 이를 모두 저장할 필요가 없습니다. 데이터가 많을수록 INSERT 성능이 떨어지기 때문입니다. 따라서 필요한 정보만 기록하도록 합니다.

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS merchant_size
(
    id          INT PRIMARY KEY COMMENT 'PK',
    merchant_id VARCHAR(50) NOT NULL COMMENT '가맹점 ID',
    size        VARCHAR(50) NOT NULL COMMENT '매출 구간',
    start_date  DATE        NOT NULL COMMENT '시작일',
    end_date    DATE        NOT NULL COMMENT '종료일'
);



참고로 이는 JPA Envers 를 사용해 간단히 구현할 수 있는데요, 물론 테이블에 필드가 많을 경우, 어노테이션이 주렁주렁 달리게 됩니다. 성능도도어느 정도도 감소 되고요. 작성되는 코드 양과 성능 사이의 절충을 고려해 사용하도록합니다.

1
2
3
4
5
6
7
8
9
@Audited 
@Entity(name = users)
public class User {

    ......

    @NotAudited // 해당 필드는 Envers가 추적하지 않음
    private String internalNote;
}




3-2. 데이터 참조 방향

데이터가 적은 쪽에서 많은 쪽을 참조 해야 합니다. 예를 들어, 우리가 관리하는 가맹점은 수만 건인데, 반기마다 수백만 건의 데이터가 INSERT 된다면, 데이터가 적은 수만 건을 기준 으로 JOIN을 걸어 변경 이력을 저장/변경하는 것이죠. 즉, 데이터가 적은 드라이빙 테이블(Driving Table)을 기준 으로 데이터를 기록하는 것입니다.

The ‘driving’ table is the table we will join FROM – that is JOIN TO other tables.



새로운 데이터가 저장한 후, 과거 데이터도 업데이트해 줘야 하는데요, 이때, 커서 페이징(Cursor Paging)청크(Chunk) 를 사용해 성능을 높일 수 있습니다. 몇만 건의 데이터라도 한 번에 업데이트하면 한 번에 여러 row에 락 이 걸릴 수 있으므로 주의하도록 합니다. 해당 테이블이 자주 사용된다면요.

A locking read, an UPDATE, or a DELETE generally set record locks on every index record that is scanned in the processing of an SQL statement.





4. 정리


선분 이력 관리에 대해 살펴봤는데요, 사실 별다른 내용이 없습니다. 구현된 코드나 로직은 꽤 복잡한데, 적고 보니 별것 없어서 놀랐네요.


This post is licensed under CC BY 4.0 by the author.

테이블에 주석을 추가하는데, 결제 사고가 발생했다고?

pt-online-schema-change를 사용하다 결제 시스템이 마비 됐다?