ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [6] 데이터베이스 물리적 모델링
    프로젝트/대출금 상환 프로젝트 2026. 6. 14. 14:13

    들어가며 

    지금까지 대출금 상환 프로젝트에 대해 개념적 , 논리적 데이터베이스 모델링을 진행했습니다. 

    회원 , 대출계약 , 대출계좌 , 상환 스케줄 , 연체 이력 등 15 개 테이블과 그 관계를 정의하고 정규화 과정 까지 마쳤습니다. 

    아래는 그 결과 ERD 입니다. 

     

     

     

    이번 글에서는 논리적 모델링 결과를 바탕으로 물리적 모델링을 진행하겠습니다. 

    물리적 모델링은 ERD 를 바탕으로 MySQL 이라는 구체적인 DBMS 에 최적화된 형태로 구현하는 과정 입니다. 

    진행 순서

    물리적 모델링은 다음 3단계로 진행 합니다. 

     

    • 테이블과 컬럼 변환: 논리적 모델의 한글 엔티티명/속성명을 명명 규칙(Naming Convention)에 따라 영문으로 변환합니다.
    • 데이터 타입 정의: 각 컬럼에 가장 적합한 자료형(VARCHAR, BIGINT, DECIMAL 등)을 선택합니다. 저장 공간과 성능에 직접적인 영향을 미치는 작업입니다.
    • 제약조건 설정: 기본 키, 외래 키, NOT NULL, UNIQUE 등 데이터 무결성을 보장하기 위한 제약조건을 구체적으로 설정합니다.

     

    물리적 모델링 결과

    물리적 모델링을 마치면 2가지 산출물이 나옵니다. 

     

    • 테이블 정의서: 각 테이블과 컬럼이 무엇을 의미하고 어떤 규칙을 가지는지 상세히 기술한 설계 명세서입니다.
    • DDL 스크립트: 실제로 CREATE TABLE을 실행해서 테이블을 생성할 수 있는 SQL 스크립트입니다.

     


    코드성 테이블 

    코드성 테이블(이자종류, 상환방법, 대출상태, 상환스케줄상태, 연체이력상태 , 연체근거)은 모두 동일한 패턴을 가집니다. 

     

     

     

    PK 가 INT

    항목 수가 명확히 제한된 코드성 데이터라 BIGINT 가 불필요 하다고 생각했습니다.

     

    컬럼명이 name 

    테이블명이 이미 컨텍스트를 주므로 (interest_type 안의 name 은 당연히 이자종류명) interest_type_name 처럼 접두사를 붙이지 않습니다. 

     

    1. 이자종류 (interest_type)

    고정금리, 변동금리와 같은 대출계약에 적용되는 이자 산정 방식의 종류를 관리합니다.

    2. 상환방법 (repayment_method)

    원리금균등상환, 원금균등상환, 만기일시상환와 같은 대출상품이 지원하는 상환 방식을 관리합니다.

    3. 대출상태 (loan_status)

    대출계좌의 현재 상태를 관리합니다. 대출상태 이력을 통해 시점별 변화를 추적합니다.

    4. 상환스케줄상태 (repayment_schedule_status)

    상환 스케줄 한 회차가 현재 어떤 상태인지를 관리합니다. 상환 스케줄이 참조합니다.

    5. 연체이력상태 (delinquency_history_status)

    연체 이력 한 건이 현재 진행 중인지, 해소되었는지 등을 관리합니다. 연체 이력이 참조합니다.

    6. 연체근거 (delinquency_reason)

    연체 이력 한 건에 대해 연체 근거를 관리합니다. 연체 이력이 참조합니다.


    핵심 테이블

     

    1. 회원 (member)

     

    member_id BIGINT

    회원은 계속 누적되는 핵심 데이터입니다. 서비스를 지속하면서 계속 늘어나는 데이터 이므로 BIGINT 를 사용하였습니다. 

     

    가입일시 → created_at

    ERD의 가입일시는 "회원이 가입한 시점"이라는 비즈니스 이벤트입니다. 명명 규칙상 이벤트 발생 시점은 _at 접미사를 쓰고, 이는 곧 "데이터가 처음 생성된 시점"과 동일하므로 별도 컬럼 없이 공통 컬럼 created_at으로 흡수합니다.

     

    password VARCHAR(255)

    평문이 아닌 해시값(BCrypt 등)이 저장되므로, 원본 비밀번호 길이보다 해시 결과 길이를 고려해 충분히 여유 있게 잡았습니다.

     

    2. 주 계좌 (primary_account)

     

    member_id에 UQ

    ERD상 회원:주계좌가 1:1 관계 입니다. FK에 UNIQUE를 걸어 "한 회원은 주 계좌를 하나를 반드시 가진다"는 제약을 DB 레벨에서 강제합니다.

     

    계좌번호 → account_no

    보편적인 약어 활용을 하여 number → no로 축약했습니다.

     

    잔액 → DECIMAL(23,4)

    금융 데이터는 부동소수점(FLOAT/DOUBLE) 사용 시 미세한 오차가 발생할 수 있어 절대 사용하지 않습니다. DECIMAL(23,4)로 정수부 19자리, 소수부 4자리까지 정확하게 저장합니다.

     

    balance DEFAULT 0 

    계좌 생성 시점에 잔액이 0원인 경우가 일반적이므로 기본값을 명시했습니다.

     

    3. 대출상품 (loan_product)

     

    FK interest_type_id, repayment_method_id 타입 일치

    FK는 참조하는 PK와 데이터 타입이 반드시 같아야 합니다. 이자종류, 상환방법의 PK가 INT이므로 여기서도 INT를 사용합니다.

     

    가산금리 → DECIMAL(10,5) 

    기준금리 + 가산금리 - 우대금리 구조에서 가산금리는 2.50000%처럼 소수점 5자리까지의 정밀도가 필요할 수 있습니다. DECIMAL(10,5)는 정수부 5자리, 소수부 5자리까지 — 최대 99999.99999까지 표현 가능해 여유 있게 설정했습니다. 이후 등장할 모든 금리(적용금리, 연체이자율 등) 컬럼에도 동일한 DECIMAL(10,5)를 적용합니다.

     


    운영 테이블

     

    1. 대출계약 (loan_contract)

     

    계약일/만기일 → _date

    시각 정보가 의미 없는 날짜 전용 데이터입니다. 명명 규칙상 _date 접미사를 사용합니다.

     

    계약대출원금 → contract_principal

    "계약 당시 확정된 대출 원금"이라는 의미입니다.

    ERD에서 대출계좌의 대출원금잔액(현재 남은 금액, 변동값)과 구분하기 위해 _principal로만 명명하고 balance를 붙이지 않았습니다.

    계약 시점의 고정값이라는 의미를 살린 것입니다.

     

    FK member_id에는 UQ 없음 

    주계좌와 달리 대출계약은 ERD상 회원:계약이 1:N입니다.

    한 회원이 여러 계약을 가질 수 있으므로 UNIQUE 제약을 걸지 않습니다.

     

    2. 대출계좌 (loan_account)

     

    loan_contract_id에 UQ

    ERD상 대출계약:대출계좌가 1:1 관계입니다. "한 계약은 하나의 대출계좌만 가진다"를 DB 레벨에서 강제합니다.

     

    대출원금잔액 → loan_principal_balance

    대출계약.contract_principal(계약 시점의 고정 원금)과 달리, 상환이 진행되면서 계속 줄어드는 현재 잔액입니다.

    그래서 _balance 접미사를 붙여 변동값임을 명확히 했습니다.

     

    매달 상환일 → monthly_payment_day TINYINT

    매달 1~31일 중 한 값만 가지므로 TINYINT(−128~127)로 충분합니다. INT를 쓰면 4바이트가 낭비됩니다. 

     

    현재 회차 → current_installment_no INT

    보편적인 약어 활용에 따라 number → no로 축약했습니다.

    회차는 보통 12회, 36회 등 INT 범위 내에서 충분합니다.

     

    FK primary_account_id 타입

    주계좌의 PK가 BIGINT이므로 동일하게 BIGINT입니다.

     

    3. 대출상태 이력 (loan_status_history)

     

    PK가 BIGINT

    이력 테이블은 시간이 지날수록 데이터가 계속 누적되는 대표적인 테이블입니다.

    처음엔 적어 보여도 장기적으로 가장 빠르게 row 수가 늘어날 수 있어 BIGINT를 채택했습니다.

     

    종료일 → end_date NULL 허용

    현재 진행 중인 상태는 종료일이 정해지지 않았습니다. "현재 상태"는 end_date IS NULL인 행으로 식별합니다.

     

    loan_account_id에 UQ 없음

    한 대출계좌는 여러 개의 상태 이력(정상→연체→정상...)을 가질 수 있으므로(1:N) UNIQUE를 걸지 않습니다.

     


    상환 테이블

     

    1. 상환 스케줄 (repayment_schedule)

     

    예정일 → scheduled_at

    대출계좌 생성 시점에 1회차~N회차 일정이 한 번에 확정되고 이후 변경되지 않는 값이므로, "변경 가능"이라는 의미를 가진 _dt는 맞지 않습니다.

    반면 _at은 "특정 작업이 발생한 정확한 시점"을 의미하며, "1회차 상환은 2026-07-15에 발생하기로 확정되었다"는 확정된 사건의 시점으로 해석할 수 있어 _at을 사용했습니다.

     

    상환금(원금,이자) → 컬럼 분리

    ERD에는 상환금 (원금,이자) 하나로 표기되어 있지만, 물리 모델에서는 scheduled_principal, scheduled_interest 두 컬럼으로 분리했습니다.

    원금과 이자는 회계적으로 성격이 다른 값이고, 변제충당순서(이자 우선 vs 원금 우선) 로직에서 각각 독립적으로 다뤄야 합니다.

    합산값 하나만 저장하면 상환이력에서 "원금 얼마/이자 얼마 납부했는지"를 계산하기 어렵습니다.

     

    적용금리 → applied_rate DECIMAL(10,5)

    이자율은 4.50000%처럼 소수점 5자리까지의 정밀도가 필요합니다.

    DECIMAL(10,5)는 정수부 5자리, 소수부 5자리까지 표현 가능해 충분한 여유를 둡니다.

     

     

    2. 상환 이력 (repayment_history)

     

    납부 일시 → paid_at

    "회원이 납부했다"는 이미 발생한 비즈니스 이벤트이므로 _at을 사용합니다.

    created_at(이 row가 DB에 생성된 시점)과는 의미가 다릅니다.

    배치가 다음날 새벽에 상환이력을 생성한다면, paid_at은 실제 납부 시각, created_at은 배치가 row를 만든 시각으로 구분됩니다.

     

    delinquency_history_id → NULL 허용 

    ERD상 상환이력:연체이력은 선택적 관계 입니다. 정상 상환은 연체와 무관하므로 FK 값이 없을 수 있습니다.

     

    납부 연체금 → paid_delinquency_charge DEFAULT 0

    연체 없는 정상 상환에서는 0원이 들어가는 것이 자연스러우므로 기본값을 0으로 설정했습니다.

     

     

    3. 연체 이력 (delinquency_history)

     

    연체 일시 → delinquent_at

    "연체가 발생했다"는 이미 일어난 이벤트이므로 _at을 사용합니다.

     

    근거id → delinquency_reason_id, NOT NULL 

    연체 이력이 생성되었다면 그 발생 사유는 반드시 존재하는 정보이므로, 코드성 테이블 delinquency_reason을 참조하는 FK를 NOT NULL로 설정했습니다.

     

    연체 이자율 → delinquent_rate DECIMAL(10,5)

    상환 스케줄.applied_rate와 동일한 정밀도가 필요합니다. 연체이자율은 보통 정상 적용금리 + 가산율로 계산되기 때문입니다.

     

    repayment_schedule_id에 UQ 없음

    ERD상 상환스케줄:연체이력은 1:N 입니다. 한 회차가 여러 일에 걸쳐 연체를 할 수 있으므로 UNIQUE를 걸지 않았습니다.


    마무리 — 테이블 정의서와 DDL

     

    논리적 모델링에서 정의한 15개 엔티티를 명명 규칙에 따라 영문 테이블/컬럼으로 변환하고, 데이터 타입과 제약조건까지 결정했습니다.

    그 결과물로 두 가지를 정리했습니다.

     

    1. 테이블 정의서 

    테이블 정의서는 DDL만으로는 알 수 없는 정보를 담는 문서입니다.

    [링크] 

     

    1. DDL 스크립트

    실제로 CREATE TABLE을 실행해서 테이블을 생성할 수 있는 SQL 스크립트입니다.

    [링크] 

     

     

     

     

Designed by Tistory.