ERP-node/docs/DDD1542/DB_INEFFICIENCY_ANALYSIS.md

22 KiB
Raw Permalink Blame History

DB 비효율성 분석 보고서

분석일: 2026-01-20 | 분석 기준: 코드 사용 빈도 + DB 설계 원칙 + 유지보수성


전체 요약

pie title 비효율성 분류
    "🔴 즉시 개선" : 2
    "🟡 검토 후 개선" : 2
    "🟢 선택적 개선" : 2
심각도 개수 항목
🔴 즉시 개선 2 layout_metadata 미사용, user_dept 비정규화
🟡 검토 후 개선 2 히스토리 테이블 39개, cascading 미사용 3개
🟢 선택적 개선 2 dept_info 중복, screen 테이블 통합

🔴 1. screen_definitions.layout_metadata (미사용 컬럼)

현재 구조

erDiagram
    screen_definitions {
        uuid screen_id PK
        varchar screen_name
        varchar table_name
        jsonb layout_metadata "❌ 미사용"
    }
    
    screen_layouts {
        int layout_id PK
        uuid screen_id FK
        jsonb properties "✅ 실제 사용"
        jsonb layout_config "✅ 실제 사용"
        jsonb zones_config "✅ 실제 사용"
    }
    
    screen_definitions ||--o{ screen_layouts : "screen_id"

문제점

항목 상세
중복 저장 screen_definitions.layout_metadatascreen_layouts.properties가 유사 데이터
코드 증거 screenManagementService.ts:534 - "기존 layout_metadata도 확인 (하위 호환성) - 현재는 사용하지 않음"
사용 빈도 전체 코드에서 6회만 참조 (대부분 복사/마이그레이션용)
저장 낭비 JSONB 컬럼이 NULL 또는 빈 객체로 유지

코드 증거

// screenManagementService.ts:534-535
// 기존 layout_metadata도 확인 (하위 호환성) - 현재는 사용하지 않음
// 실제 데이터는 screen_layouts 테이블에서 개별적으로 조회해야 함

영향도 분석

flowchart LR
    A[layout_metadata 삭제] --> B{영향 범위}
    B --> C[menuCopyService.ts]
    B --> D[screenManagementService.ts]
    C --> E[복사 시 해당 필드 제외]
    D --> F[조회 시 해당 필드 제외]
    E --> G[✅ 정상 동작]
    F --> G

개선 방안

-- Step 1: 데이터 확인 (실행 전)
SELECT screen_id, screen_name, 
       CASE WHEN layout_metadata IS NULL THEN 'NULL'
            WHEN layout_metadata = '{}' THEN 'EMPTY'
            ELSE 'HAS_DATA' END as status
FROM screen_definitions
WHERE layout_metadata IS NOT NULL AND layout_metadata != '{}';

-- Step 2: 컬럼 삭제
ALTER TABLE screen_definitions DROP COLUMN layout_metadata;

예상 효과

  • 스키마 단순화
  • 데이터 정합성 혼란 제거
  • 저장 공간 절약 (JSONB 오버헤드 제거)

🔴 2. user_dept 비정규화 (중복 저장)

현재 구조 (비효율)

erDiagram
    user_info {
        varchar user_id PK
        varchar user_name "원본"
        varchar dept_code
    }
    
    dept_info {
        varchar dept_code PK
        varchar dept_name "원본"
        varchar company_code
    }
    
    user_dept {
        varchar user_id FK
        varchar dept_code FK
        varchar dept_name "❌ 중복 (dept_info에서 JOIN)"
        varchar user_name "❌ 중복 (user_info에서 JOIN)"
        varchar position_name "❓ 별도 테이블 필요?"
        boolean is_primary
    }
    
    user_info ||--o{ user_dept : "user_id"
    dept_info ||--o{ user_dept : "dept_code"

문제점

항목 상세
데이터 불일치 위험 부서명 변경 시 dept_info만 수정하면 user_dept.dept_name은 구 데이터 유지
수정 비용 부서명 변경 시 모든 user_dept 레코드 UPDATE 필요
저장 낭비 동일 부서의 모든 사용자에게 부서명 반복 저장
사용 빈도 코드에서 user_dept.dept_name 직접 조회는 2회뿐

비정규화로 인한 데이터 불일치 시나리오

sequenceDiagram
    participant Admin as 관리자
    participant DI as dept_info
    participant UD as user_dept
    
    Admin->>DI: UPDATE dept_name = '개발2팀'<br/>WHERE dept_code = 'DEV'
    Note over DI: dept_name = '개발2팀' ✅
    Note over UD: dept_name = '개발1팀' ❌ 구 데이터
    
    Admin->>UD: ⚠️ 수동으로 모든 레코드 UPDATE 필요
    Note over UD: dept_name = '개발2팀' ✅

권장 구조 (정규화)

erDiagram
    user_info {
        varchar user_id PK
        varchar user_name
        varchar position_name "직위 (여기서 관리)"
    }
    
    dept_info {
        varchar dept_code PK
        varchar dept_name
    }
    
    user_dept {
        varchar user_id FK
        varchar dept_code FK
        boolean is_primary
    }
    
    user_info ||--o{ user_dept : "user_id"
    dept_info ||--o{ user_dept : "dept_code"

참고: position_info 마스터 테이블은 현재 없음. user_info.position_name에 직접 저장 중. 직위 표준화 필요 시 별도 마스터 테이블 생성 검토.

개선 방안

-- Step 1: 중복 컬럼 삭제 준비 (조회 쿼리 수정 선행)
-- 기존: SELECT ud.dept_name FROM user_dept ud
-- 변경: SELECT di.dept_name FROM user_dept ud JOIN dept_info di ON ud.dept_code = di.dept_code

-- Step 2: 중복 컬럼 삭제
ALTER TABLE user_dept DROP COLUMN dept_name;
ALTER TABLE user_dept DROP COLUMN user_name;
-- position_name은 user_info에서 조회하도록 변경
ALTER TABLE user_dept DROP COLUMN position_name;

예상 효과

  • 데이터 정합성 보장 (Single Source of Truth)
  • 수정 비용 감소 (한 곳만 수정)
  • 저장 공간 절약

🟡 3. 과도한 히스토리/로그 테이블 (39개)

현재 구조

graph TB
    subgraph HISTORY["히스토리 테이블 (39개)"]
        H1[authority_master_history]
        H2[carrier_contract_mng_log]
        H3[carrier_mng_log]
        H4[carrier_vehicle_mng_log]
        H5[comm_code_history]
        H6[data_collection_history]
        H7[ddl_execution_log]
        H8[defect_standard_mng_log]
        H9[delivery_history]
        H10[...]
        H11[user_info_history]
        H12[vehicle_location_history]
        H13[work_instruction_log]
    end
    
    subgraph PROBLEM["문제점"]
        P1["스키마 변경 시<br/>모든 히스토리 테이블 수정"]
        P2["테이블 수 폭증<br/>(원본 + 히스토리)"]
        P3["관리 복잡도 증가"]
    end
    
    HISTORY --> PROBLEM

현재 테이블 목록 (39개)

카테고리 테이블명 용도
시스템 authority_master_history 권한 변경 이력
시스템 user_info_history 사용자 정보 이력
시스템 dept_info_history 부서 정보 이력
시스템 login_access_log 로그인 기록
시스템 ddl_execution_log DDL 실행 기록
물류 carrier_mng_log 운송사 변경 이력
물류 carrier_contract_mng_log 운송 계약 이력
물류 carrier_vehicle_mng_log 운송 차량 이력
물류 delivery_history 배송 이력
물류 delivery_route_mng_log 배송 경로 이력
물류 logistics_cost_mng_log 물류 비용 이력
물류 vehicle_location_history 차량 위치 이력
설비 equipment_mng_log 설비 변경 이력
설비 equipment_consumable_log 설비 소모품 이력
설비 equipment_inspection_item_log 설비 점검 이력
설비 dtg_maintenance_history DTG 유지보수 이력
설비 dtg_management_log DTG 관리 이력
생산 defect_standard_mng_log 불량 기준 이력
생산 work_instruction_log 작업 지시 이력
생산 work_instruction_detail_log 작업 지시 상세 이력
생산 safety_inspections_log 안전 점검 이력
영업 supplier_mng_log 공급사 이력
영업 sales_order_detail_log 판매 주문 이력
기타 flow_audit_log 플로우 감사 로그 필요
기타 flow_integration_log 플로우 통합 로그 필요
기타 mail_log 메일 발송 로그 필요
... ... ...

문제점 상세

flowchart TB
    A[원본 테이블 컬럼 추가] --> B[히스토리 테이블도 수정 필요]
    B --> C{수동 작업}
    C -->|잊음| D[❌ 스키마 불일치]
    C -->|수동 수정| E[⚠️ 추가 작업 비용]
    
    F[테이블 39개 × 평균 15컬럼] --> G[약 585개 컬럼 관리]

권장 구조 (통합 감사 테이블)

erDiagram
    audit_log {
        bigint id PK
        varchar table_name "원본 테이블명"
        varchar record_id "레코드 식별자"
        varchar action "INSERT|UPDATE|DELETE"
        jsonb old_data "변경 전 전체 데이터"
        jsonb new_data "변경 후 전체 데이터"
        jsonb changed_fields "변경된 필드만"
        varchar changed_by "변경자"
        inet ip_address "IP 주소"
        timestamp changed_at "변경 시각"
        varchar company_code "회사 코드"
    }

개선 방안

-- 통합 감사 테이블 생성
CREATE TABLE audit_log (
    id bigserial PRIMARY KEY,
    table_name varchar(100) NOT NULL,
    record_id varchar(100) NOT NULL,
    action varchar(10) NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
    old_data jsonb,
    new_data jsonb,
    changed_fields jsonb,  -- UPDATE 시 변경된 필드만
    changed_by varchar(50),
    ip_address inet,
    changed_at timestamp DEFAULT now(),
    company_code varchar(20)
);

-- 인덱스
CREATE INDEX idx_audit_log_table ON audit_log(table_name);
CREATE INDEX idx_audit_log_record ON audit_log(table_name, record_id);
CREATE INDEX idx_audit_log_time ON audit_log(changed_at);
CREATE INDEX idx_audit_log_company ON audit_log(company_code);

-- PostgreSQL 트리거 함수 (자동 감사)
CREATE OR REPLACE FUNCTION audit_trigger_func()
RETURNS TRIGGER AS $$
BEGIN
    IF TG_OP = 'INSERT' THEN
        INSERT INTO audit_log (table_name, record_id, action, new_data, changed_by, changed_at)
        VALUES (TG_TABLE_NAME, NEW.id::text, 'INSERT', row_to_json(NEW)::jsonb, 
                current_setting('app.current_user', true), now());
        RETURN NEW;
    ELSIF TG_OP = 'UPDATE' THEN
        INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, changed_by, changed_at)
        VALUES (TG_TABLE_NAME, NEW.id::text, 'UPDATE', row_to_json(OLD)::jsonb, 
                row_to_json(NEW)::jsonb, current_setting('app.current_user', true), now());
        RETURN NEW;
    ELSIF TG_OP = 'DELETE' THEN
        INSERT INTO audit_log (table_name, record_id, action, old_data, changed_by, changed_at)
        VALUES (TG_TABLE_NAME, OLD.id::text, 'DELETE', row_to_json(OLD)::jsonb,
                current_setting('app.current_user', true), now());
        RETURN OLD;
    END IF;
END;
$$ LANGUAGE plpgsql;

예상 효과

  • 테이블 수 39개 → 1개로 감소
  • 스키마 변경 시 히스토리 수정 불필요 (JSONB 저장)
  • 통합 조회/분석 용이
  • ⚠️ 주의: 기존 히스토리 데이터 마이그레이션 필요

🟡 4. Cascading 미사용 테이블 (3개)

현재 구조

graph TB
    subgraph USED["✅ 사용 중 (9개)"]
        U1[cascading_hierarchy_group]
        U2[cascading_hierarchy_level]
        U3[cascading_auto_fill_group]
        U4[cascading_auto_fill_mapping]
        U5[cascading_relation]
        U6[cascading_condition]
        U7[cascading_mutual_exclusion]
        U8[category_value_cascading_group]
        U9[category_value_cascading_mapping]
    end
    
    subgraph UNUSED["❌ 미사용 (3개)"]
        X1[cascading_multi_parent]
        X2[cascading_multi_parent_source]
        X3[cascading_reverse_lookup]
    end
    
    UNUSED --> DELETE[삭제 검토]

코드 사용 분석

테이블 코드 참조 판정
cascading_hierarchy_group 다수 유지
cascading_hierarchy_level 다수 유지
cascading_auto_fill_group 다수 유지
cascading_auto_fill_mapping 다수 유지
cascading_relation 다수 유지
cascading_condition 7회 ⚠️ 검토
cascading_mutual_exclusion 소수 ⚠️ 검토
cascading_multi_parent 0회 삭제
cascading_multi_parent_source 0회 삭제
cascading_reverse_lookup 0회 삭제
category_value_cascading_group 다수 유지
category_value_cascading_mapping 다수 유지

개선 방안

-- Step 1: 데이터 확인
SELECT 'cascading_multi_parent' as tbl, count(*) FROM cascading_multi_parent
UNION ALL
SELECT 'cascading_multi_parent_source', count(*) FROM cascading_multi_parent_source
UNION ALL
SELECT 'cascading_reverse_lookup', count(*) FROM cascading_reverse_lookup;

-- Step 2: 데이터 없으면 삭제
DROP TABLE IF EXISTS cascading_multi_parent_source;  -- 자식 먼저
DROP TABLE IF EXISTS cascading_multi_parent;
DROP TABLE IF EXISTS cascading_reverse_lookup;

🟢 5. dept_info.company_name 중복

현재 구조

erDiagram
    company_mng {
        varchar company_code PK
        varchar company_name "원본"
    }
    
    dept_info {
        varchar dept_code PK
        varchar company_code FK
        varchar company_name "❌ 중복"
        varchar dept_name
    }
    
    company_mng ||--o{ dept_info : "company_code"

문제점

  • dept_info.company_namecompany_mng.company_name과 동일한 값
  • 회사명 변경 시 두 테이블 모두 수정 필요

개선 방안

-- 중복 컬럼 삭제
ALTER TABLE dept_info DROP COLUMN company_name;

-- 조회 시 JOIN 사용
SELECT di.*, cm.company_name
FROM dept_info di
JOIN company_mng cm ON di.company_code = cm.company_code;

🟢 6. screen 관련 테이블 통합 가능성

현재 구조

erDiagram
    screen_data_flows {
        int id PK
        uuid source_screen_id
        uuid target_screen_id
        varchar flow_type
    }
    
    screen_table_relations {
        int id PK
        uuid screen_id
        varchar table_name
        varchar relation_type
    }
    
    screen_field_joins {
        int id PK
        uuid screen_id
        varchar source_field
        varchar target_field
    }

분석

테이블 용도 사용 빈도
screen_data_flows 화면 간 데이터 흐름 15회 (screenGroupController)
screen_table_relations 화면-테이블 관계 일부
screen_field_joins 필드 조인 설정 일부

통합 가능성

  • 세 테이블 모두 "화면 간 관계" 정의
  • 하나의 screen_relations 테이블로 통합 가능
  • 단, 현재 사용 중이므로 신중한 검토 필요

실행 계획

gantt
    title DB 개선 실행 계획
    dateFormat  YYYY-MM-DD
    section 즉시 실행
    layout_metadata 컬럼 삭제      :a1, 2026-01-21, 1d
    미사용 cascading 테이블 삭제   :a2, 2026-01-21, 1d
    section 단기 (1주)
    user_dept 정규화              :b1, 2026-01-22, 5d
    dept_info.company_name 삭제   :b2, 2026-01-22, 2d
    section 장기 (1개월)
    히스토리 테이블 통합 설계      :c1, 2026-01-27, 7d
    히스토리 마이그레이션          :c2, after c1, 14d

즉시 실행 가능 SQL 스크립트

-- ============================================
-- 🔴 즉시 개선 항목
-- ============================================

-- 1. screen_definitions.layout_metadata 삭제
BEGIN;
-- 백업 (선택)
-- CREATE TABLE screen_definitions_backup AS SELECT * FROM screen_definitions;
ALTER TABLE screen_definitions DROP COLUMN IF EXISTS layout_metadata;
COMMIT;

-- 2. 미사용 cascading 테이블 삭제
BEGIN;
DROP TABLE IF EXISTS cascading_multi_parent_source;
DROP TABLE IF EXISTS cascading_multi_parent;
DROP TABLE IF EXISTS cascading_reverse_lookup;
COMMIT;

-- 3. dept_info.company_name 삭제 (선택)
BEGIN;
ALTER TABLE dept_info DROP COLUMN IF EXISTS company_name;
COMMIT;

7. 채번-카테고리 시스템 (범용화 완료)

현황

테이블 건수 menu_objid 상태
numbering_rules_test 108건 없음 범용화 완료
numbering_rule_parts_test 267건 없음 범용화 완료
category_values_test 3건 없음 범용화 완료
category_column_mapping_test 0건 없음 미사용

연결관계도

erDiagram
    numbering_rules_test {
        varchar rule_id PK "규칙 ID"
        varchar rule_name "규칙명"
        varchar table_name "테이블명"
        varchar column_name "컬럼명"
        varchar category_column "카테고리 컬럼"
        int category_value_id FK "카테고리 값 ID"
        varchar separator "구분자"
        varchar reset_period "리셋 주기"
        int current_sequence "현재 시퀀스"
        date last_generated_date "마지막 생성일"
        varchar company_code "회사코드"
    }

    numbering_rule_parts_test {
        serial id PK "파트 ID"
        varchar rule_id FK "규칙 ID"
        int part_order "순서 (1-6)"
        varchar part_type "유형"
        varchar generation_method "생성방식"
        jsonb auto_config "자동설정"
        jsonb manual_config "수동설정"
        varchar company_code "회사코드"
    }

    category_values_test {
        serial value_id PK "값 ID"
        varchar table_name "테이블명"
        varchar column_name "컬럼명"
        varchar value_code "코드"
        varchar value_label "라벨"
        int value_order "정렬순서"
        int parent_value_id FK "부모 (계층)"
        int depth "깊이"
        varchar path "경로"
        varchar color "색상"
        varchar icon "아이콘"
        bool is_active "활성"
        bool is_default "기본값"
        varchar company_code "회사코드"
    }

    numbering_rules_test ||--o{ numbering_rule_parts_test : "1:N"
    numbering_rules_test }o--o| category_values_test : "카테고리 조건"
    category_values_test ||--o{ category_values_test : "계층구조"

데이터 흐름

┌──────────────────────────────────────────────────────────────────────┐
│                    범용 채번 시스템 (menu_objid 제거 완료)             │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌────────────────────┐              ┌─────────────────────────┐    │
│  │ category_values    │              │  numbering_rules_test   │    │
│  │     _test (3건)    │◄─────────────│       (108건)           │    │
│  ├────────────────────┤    FK        ├─────────────────────────┤    │
│  │ table + column     │   조인       │ table + column 기준     │    │
│  │ 기준 카테고리 값    │              │ category_value_id로     │    │
│  │                    │              │ 카테고리별 규칙 구분     │    │
│  └────────────────────┘              └───────────┬─────────────┘    │
│                                                  │                   │
│                                                  │ 1:N               │
│                                                  ▼                   │
│                                      ┌─────────────────────────┐    │
│                                      │ numbering_rule_parts    │    │
│                                      │     _test (267건)       │    │
│                                      ├─────────────────────────┤    │
│                                      │ 파트별 설정 (최대 6개)   │    │
│                                      │ - prefix, sequence      │    │
│                                      │ - date, year, month     │    │
│                                      │ - custom                │    │
│                                      └─────────────────────────┘    │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

조회 흐름

sequenceDiagram
    participant UI as 사용자 화면
    participant CV as category_values_test
    participant NR as numbering_rules_test
    participant NRP as numbering_rule_parts_test

    UI->>CV: 1. 카테고리 값 조회<br/>(table_name + column_name)
    CV-->>UI: 카테고리 목록 반환
    
    UI->>NR: 2. 채번 규칙 조회<br/>(table + column + category_value_id)
    NR-->>UI: 규칙 반환
    
    UI->>NRP: 3. 채번 파트 조회<br/>(rule_id)
    NRP-->>UI: 파트 목록 반환 (1-6개)
    
    UI->>UI: 4. 파트 조합하여 채번 생성<br/>"PREFIX-2026-0001"

범용화 전/후 비교

항목 기존 (menu_objid 의존) 현재 (범용화)
식별 기준 menu_objid (메뉴별) table_name + column_name
공유 범위 메뉴 단위 테이블 단위 (여러 메뉴에서 공유)
중복 규칙 같은 테이블도 메뉴마다 별도 하나의 규칙을 공유
유지보수 메뉴 변경 시 규칙도 수정 테이블 기준으로 독립

참고

  • 분석 대상: /Users/gbpark/ERP-node/backend-node/src/**/*.ts
  • 스키마 파일: /Users/gbpark/ERP-node/db/plm_schema_20260120.sql
  • 관련 문서: DB_STRUCTURE_DIAGRAM.md, DB_CLEANUP_LOG_20260120.md