277 lines
7.1 KiB
Markdown
277 lines
7.1 KiB
Markdown
|
|
# 마이그레이션 046: 채번규칙 scope_type 확장
|
||
|
|
|
||
|
|
## 📋 목적
|
||
|
|
|
||
|
|
메뉴 기반 채번규칙 필터링을 **테이블 기반 필터링**으로 전환하여 더 직관적이고 유지보수하기 쉬운 시스템 구축
|
||
|
|
|
||
|
|
### 주요 변경사항
|
||
|
|
|
||
|
|
1. `scope_type` 값 확장: `'global'`, `'menu'` → `'global'`, `'table'`, `'menu'`
|
||
|
|
2. 기존 데이터 자동 마이그레이션 (`global` + `table_name` → `table`)
|
||
|
|
3. 유효성 검증 제약조건 추가
|
||
|
|
4. 멀티테넌시 인덱스 최적화
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 실행 방법
|
||
|
|
|
||
|
|
### Docker 환경 (권장)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
docker exec -i erp-node-db-1 psql -U postgres -d ilshin < db/migrations/046_update_numbering_rules_scope_type.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
### 로컬 PostgreSQL
|
||
|
|
|
||
|
|
```bash
|
||
|
|
psql -U postgres -d ilshin -f db/migrations/046_update_numbering_rules_scope_type.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
### pgAdmin / DBeaver
|
||
|
|
|
||
|
|
1. `db/migrations/046_update_numbering_rules_scope_type.sql` 파일 열기
|
||
|
|
2. 전체 내용 복사
|
||
|
|
3. SQL 쿼리 창에 붙여넣기
|
||
|
|
4. 실행 (F5 또는 Execute)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ 검증 방법
|
||
|
|
|
||
|
|
### 1. 제약조건 확인
|
||
|
|
|
||
|
|
```sql
|
||
|
|
SELECT conname, pg_get_constraintdef(oid)
|
||
|
|
FROM pg_constraint
|
||
|
|
WHERE conrelid = 'numbering_rules'::regclass
|
||
|
|
AND conname LIKE '%scope%';
|
||
|
|
```
|
||
|
|
|
||
|
|
**예상 결과**:
|
||
|
|
```
|
||
|
|
conname | pg_get_constraintdef
|
||
|
|
--------------------------------------|---------------------
|
||
|
|
check_scope_type | CHECK (scope_type IN ('global', 'table', 'menu'))
|
||
|
|
check_table_scope_requires_table_name | CHECK (...)
|
||
|
|
check_global_scope_no_table_name | CHECK (...)
|
||
|
|
check_menu_scope_requires_menu_objid | CHECK (...)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 인덱스 확인
|
||
|
|
|
||
|
|
```sql
|
||
|
|
SELECT indexname, indexdef
|
||
|
|
FROM pg_indexes
|
||
|
|
WHERE tablename = 'numbering_rules'
|
||
|
|
AND indexname LIKE '%scope%'
|
||
|
|
ORDER BY indexname;
|
||
|
|
```
|
||
|
|
|
||
|
|
**예상 결과**:
|
||
|
|
```
|
||
|
|
indexname | indexdef
|
||
|
|
------------------------------------|----------
|
||
|
|
idx_numbering_rules_scope_menu | CREATE INDEX ... (scope_type, menu_objid, company_code)
|
||
|
|
idx_numbering_rules_scope_table | CREATE INDEX ... (scope_type, table_name, company_code)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 데이터 마이그레이션 확인
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- scope_type별 개수
|
||
|
|
SELECT scope_type, COUNT(*) as count
|
||
|
|
FROM numbering_rules
|
||
|
|
GROUP BY scope_type;
|
||
|
|
```
|
||
|
|
|
||
|
|
**예상 결과**:
|
||
|
|
```
|
||
|
|
scope_type | count
|
||
|
|
-----------|------
|
||
|
|
global | X개 (table_name이 NULL인 규칙들)
|
||
|
|
table | Y개 (table_name이 있는 규칙들)
|
||
|
|
menu | Z개 (menu_objid가 있는 규칙들)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. 유효성 검증
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 이 쿼리들은 모두 0개를 반환해야 정상
|
||
|
|
-- 1) global인데 table_name이 있는 규칙 (없어야 함)
|
||
|
|
SELECT COUNT(*) as invalid_global
|
||
|
|
FROM numbering_rules
|
||
|
|
WHERE scope_type = 'global' AND table_name IS NOT NULL;
|
||
|
|
|
||
|
|
-- 2) table인데 table_name이 없는 규칙 (없어야 함)
|
||
|
|
SELECT COUNT(*) as invalid_table
|
||
|
|
FROM numbering_rules
|
||
|
|
WHERE scope_type = 'table' AND table_name IS NULL;
|
||
|
|
|
||
|
|
-- 3) menu인데 menu_objid가 없는 규칙 (없어야 함)
|
||
|
|
SELECT COUNT(*) as invalid_menu
|
||
|
|
FROM numbering_rules
|
||
|
|
WHERE scope_type = 'menu' AND menu_objid IS NULL;
|
||
|
|
```
|
||
|
|
|
||
|
|
**모든 카운트가 0이어야 정상**
|
||
|
|
|
||
|
|
### 5. 회사별 데이터 격리 확인 (멀티테넌시)
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 회사별 규칙 개수
|
||
|
|
SELECT
|
||
|
|
company_code,
|
||
|
|
scope_type,
|
||
|
|
COUNT(*) as count
|
||
|
|
FROM numbering_rules
|
||
|
|
GROUP BY company_code, scope_type
|
||
|
|
ORDER BY company_code, scope_type;
|
||
|
|
```
|
||
|
|
|
||
|
|
**각 회사의 데이터가 독립적으로 존재해야 함**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚨 롤백 방법 (문제 발생 시)
|
||
|
|
|
||
|
|
```sql
|
||
|
|
BEGIN;
|
||
|
|
|
||
|
|
-- 제약조건 제거
|
||
|
|
ALTER TABLE numbering_rules DROP CONSTRAINT IF EXISTS check_scope_type;
|
||
|
|
ALTER TABLE numbering_rules DROP CONSTRAINT IF EXISTS check_table_scope_requires_table_name;
|
||
|
|
ALTER TABLE numbering_rules DROP CONSTRAINT IF EXISTS check_global_scope_no_table_name;
|
||
|
|
ALTER TABLE numbering_rules DROP CONSTRAINT IF EXISTS check_menu_scope_requires_menu_objid;
|
||
|
|
|
||
|
|
-- 인덱스 제거
|
||
|
|
DROP INDEX IF EXISTS idx_numbering_rules_scope_table;
|
||
|
|
DROP INDEX IF EXISTS idx_numbering_rules_scope_menu;
|
||
|
|
|
||
|
|
-- 데이터 롤백 (table → global)
|
||
|
|
UPDATE numbering_rules
|
||
|
|
SET scope_type = 'global'
|
||
|
|
WHERE scope_type = 'table';
|
||
|
|
|
||
|
|
-- 기존 제약조건 복원
|
||
|
|
ALTER TABLE numbering_rules
|
||
|
|
ADD CONSTRAINT check_scope_type
|
||
|
|
CHECK (scope_type IN ('global', 'menu'));
|
||
|
|
|
||
|
|
-- 기존 인덱스 복원
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_numbering_rules_table
|
||
|
|
ON numbering_rules(table_name, column_name);
|
||
|
|
|
||
|
|
COMMIT;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 마이그레이션 내용 상세
|
||
|
|
|
||
|
|
### 변경 사항
|
||
|
|
|
||
|
|
| 항목 | 변경 전 | 변경 후 |
|
||
|
|
|------|---------|---------|
|
||
|
|
| **scope_type 값** | 'global', 'menu' | 'global', 'table', 'menu' |
|
||
|
|
| **유효성 검증** | 없음 | table/global/menu 타입별 제약조건 추가 |
|
||
|
|
| **인덱스** | (table_name, column_name) | (scope_type, table_name, company_code)<br>(scope_type, menu_objid, company_code) |
|
||
|
|
| **데이터** | global + table_name | table 타입으로 자동 변경 |
|
||
|
|
|
||
|
|
### 영향받는 데이터
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 자동으로 변경되는 규칙 조회
|
||
|
|
SELECT
|
||
|
|
rule_id,
|
||
|
|
rule_name,
|
||
|
|
scope_type as old_scope_type,
|
||
|
|
'table' as new_scope_type,
|
||
|
|
table_name,
|
||
|
|
company_code
|
||
|
|
FROM numbering_rules
|
||
|
|
WHERE scope_type = 'global'
|
||
|
|
AND table_name IS NOT NULL;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ⚠️ 주의사항
|
||
|
|
|
||
|
|
1. **백업 필수**: 마이그레이션 실행 전 반드시 데이터베이스 백업
|
||
|
|
2. **트랜잭션**: 전체 마이그레이션이 하나의 트랜잭션으로 실행됨 (실패 시 자동 롤백)
|
||
|
|
3. **성능**: 규칙이 많으면 실행 시간이 길어질 수 있음 (보통 1초 이내)
|
||
|
|
4. **멀티테넌시**: 모든 회사의 데이터가 안전하게 마이그레이션됨
|
||
|
|
5. **하위 호환성**: 기존 기능 100% 유지 (자동 변환)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔍 문제 해결
|
||
|
|
|
||
|
|
### 제약조건 충돌 발생 시
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 문제가 되는 데이터 확인
|
||
|
|
SELECT rule_id, rule_name, scope_type, table_name, menu_objid
|
||
|
|
FROM numbering_rules
|
||
|
|
WHERE
|
||
|
|
(scope_type = 'table' AND table_name IS NULL)
|
||
|
|
OR (scope_type = 'global' AND table_name IS NOT NULL)
|
||
|
|
OR (scope_type = 'menu' AND menu_objid IS NULL);
|
||
|
|
|
||
|
|
-- 수동 수정 후 다시 마이그레이션 실행
|
||
|
|
```
|
||
|
|
|
||
|
|
### 인덱스 생성 실패 시
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- 기존 인덱스 확인
|
||
|
|
SELECT indexname, indexdef
|
||
|
|
FROM pg_indexes
|
||
|
|
WHERE tablename = 'numbering_rules';
|
||
|
|
|
||
|
|
-- 충돌하는 인덱스 삭제 후 다시 실행
|
||
|
|
DROP INDEX IF EXISTS <충돌하는_인덱스명>;
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📈 성능 개선 효과
|
||
|
|
|
||
|
|
### Before (기존)
|
||
|
|
```sql
|
||
|
|
-- 단일 인덱스: (table_name, column_name)
|
||
|
|
-- company_code 필터링 시 Full Table Scan 가능성
|
||
|
|
```
|
||
|
|
|
||
|
|
### After (변경 후)
|
||
|
|
```sql
|
||
|
|
-- 복합 인덱스: (scope_type, table_name, company_code)
|
||
|
|
-- 멀티테넌시 쿼리 성능 향상 (회사별 격리 최적화)
|
||
|
|
-- WHERE 절과 ORDER BY 절 모두 인덱스 활용 가능
|
||
|
|
```
|
||
|
|
|
||
|
|
**예상 성능 향상**: 회사별 규칙 조회 시 **3-5배 빠름**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📞 지원
|
||
|
|
|
||
|
|
- **작성자**: 개발팀
|
||
|
|
- **작성일**: 2025-11-08
|
||
|
|
- **관련 문서**: `/채번규칙_테이블기반_필터링_구현_계획서.md`
|
||
|
|
- **이슈 발생 시**: 롤백 스크립트 실행 후 개발팀 문의
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 다음 단계
|
||
|
|
|
||
|
|
마이그레이션 완료 후:
|
||
|
|
|
||
|
|
1. ✅ 검증 쿼리 실행
|
||
|
|
2. ⬜ 백엔드 API 수정 (Phase 2)
|
||
|
|
3. ⬜ 프론트엔드 수정 (Phase 3-5)
|
||
|
|
4. ⬜ 통합 테스트
|
||
|
|
|
||
|
|
**마이그레이션 준비 완료!** 🚀
|
||
|
|
|