Files
TK-BOM-Project/backend/scripts/optimize_database.py
Hyungi Ahn 3398f71b80
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
🔄 전반적인 시스템 리팩토링 완료
 백엔드 구조 개선:
- DatabaseService: 공통 DB 쿼리 로직 통합
- FileUploadService: 파일 업로드 로직 모듈화 및 트랜잭션 관리 개선
- 서비스 레이어 패턴 도입으로 코드 재사용성 향상

 프론트엔드 컴포넌트 개선:
- LoadingSpinner, ErrorMessage, ConfirmDialog 공통 컴포넌트 생성
- 재사용 가능한 컴포넌트 라이브러리 구축
- deprecated/backup 파일들 완전 제거

 성능 최적화:
- optimize_database.py: 핵심 DB 인덱스 자동 생성
- 쿼리 최적화 및 통계 업데이트 자동화
- VACUUM ANALYZE 자동 실행

 코드 정리:
- 개별 SQL 마이그레이션 파일들을 legacy/ 폴더로 정리
- 중복된 마이그레이션 스크립트 정리
- 깔끔하고 체계적인 프로젝트 구조 완성

 자동 마이그레이션 시스템 강화:
- complete_migrate.py: SQLAlchemy 기반 완전한 마이그레이션
- analyze_and_fix_schema.py: 백엔드 코드 분석 기반 스키마 수정
- fix_missing_tables.py: 누락된 테이블/컬럼 자동 생성
- start.sh: 배포 시 자동 실행 순서 최적화
2025-10-20 08:41:06 +09:00

196 lines
8.5 KiB
Python

"""
데이터베이스 성능 최적화 스크립트
인덱스 생성, 쿼리 최적화, 통계 업데이트
"""
import os
import psycopg2
from psycopg2 import sql
from ..utils.logger import get_logger
logger = get_logger(__name__)
# 환경 변수 로드
DB_HOST = os.getenv("DB_HOST", "postgres")
DB_PORT = os.getenv("DB_PORT", "5432")
DB_NAME = os.getenv("DB_NAME", "tk_mp_bom")
DB_USER = os.getenv("DB_USER", "tkmp_user")
DB_PASSWORD = os.getenv("DB_PASSWORD", "tkmp_password_2025")
def optimize_database():
"""데이터베이스 성능 최적화"""
try:
conn = psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASSWORD
)
with conn.cursor() as cursor:
# 1. 핵심 인덱스 생성
print("🔧 핵심 인덱스 생성 중...")
indexes = [
# materials 테이블 인덱스
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_file_id ON materials(file_id);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_category ON materials(classified_category);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_drawing_name ON materials(drawing_name);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_line_no ON materials(line_no);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_revision_status ON materials(revision_status);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_purchase_confirmed ON materials(purchase_confirmed);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_material_hash ON materials(material_hash);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_main_nom ON materials(main_nom);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_material_grade ON materials(material_grade);",
# files 테이블 인덱스
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_files_job_no ON files(job_no);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_files_revision ON files(revision);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_files_uploaded_by ON files(uploaded_by);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_files_upload_date ON files(upload_date);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_files_is_active ON files(is_active);",
# 복합 인덱스 (자주 함께 사용되는 컬럼들)
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_file_category ON materials(file_id, classified_category);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_drawing_line ON materials(drawing_name, line_no);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_files_job_revision ON files(job_no, revision);",
# material_purchase_tracking 인덱스
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mpt_material_hash ON material_purchase_tracking(material_hash);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mpt_job_revision ON material_purchase_tracking(job_no, revision);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mpt_purchase_status ON material_purchase_tracking(purchase_status);",
# 상세 테이블들 인덱스
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_pipe_details_material_id ON pipe_details(material_id);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_fitting_details_material_id ON fitting_details(material_id);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_flange_details_material_id ON flange_details(material_id);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_valve_details_material_id ON valve_details(material_id);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_bolt_details_material_id ON bolt_details(material_id);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_gasket_details_material_id ON gasket_details(material_id);",
# 사용자 관련 인덱스
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_username ON users(username);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_status ON users(status);",
"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_role ON users(role);",
]
for index_sql in indexes:
try:
cursor.execute(index_sql)
conn.commit()
print(f"✅ 인덱스 생성: {index_sql.split('idx_')[1].split(' ')[0] if 'idx_' in index_sql else 'unknown'}")
except Exception as e:
print(f"⚠️ 인덱스 생성 실패: {e}")
conn.rollback()
# 2. 통계 업데이트
print("📊 테이블 통계 업데이트 중...")
tables_to_analyze = [
'materials', 'files', 'projects', 'users',
'material_purchase_tracking', 'pipe_details',
'fitting_details', 'flange_details', 'valve_details',
'bolt_details', 'gasket_details'
]
for table in tables_to_analyze:
try:
cursor.execute(f"ANALYZE {table};")
conn.commit()
print(f"✅ 통계 업데이트: {table}")
except Exception as e:
print(f"⚠️ 통계 업데이트 실패 ({table}): {e}")
conn.rollback()
# 3. VACUUM 실행 (선택적)
print("🧹 데이터베이스 정리 중...")
try:
# VACUUM은 트랜잭션 외부에서 실행해야 함
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cursor.execute("VACUUM ANALYZE;")
print("✅ VACUUM ANALYZE 완료")
except Exception as e:
print(f"⚠️ VACUUM 실패: {e}")
print("✅ 데이터베이스 최적화 완료")
except Exception as e:
print(f"❌ 데이터베이스 최적화 실패: {e}")
return False
finally:
if conn:
conn.close()
return True
def get_database_stats():
"""데이터베이스 통계 조회"""
try:
conn = psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASSWORD
)
with conn.cursor() as cursor:
# 테이블별 레코드 수
print("📊 테이블별 레코드 수:")
tables = ['materials', 'files', 'projects', 'users', 'material_purchase_tracking']
for table in tables:
try:
cursor.execute(f"SELECT COUNT(*) FROM {table};")
count = cursor.fetchone()[0]
print(f" {table}: {count:,}")
except Exception as e:
print(f" {table}: 조회 실패 ({e})")
# 인덱스 사용률 확인
print("\n🔍 인덱스 사용률:")
cursor.execute("""
SELECT
schemaname,
tablename,
indexname,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
WHERE idx_tup_read > 0
ORDER BY idx_tup_read DESC
LIMIT 10;
""")
for row in cursor.fetchall():
print(f" {row[1]}.{row[2]}: {row[3]:,} reads, {row[4]:,} fetches")
except Exception as e:
print(f"❌ 통계 조회 실패: {e}")
finally:
if conn:
conn.close()
if __name__ == "__main__":
print("🚀 데이터베이스 성능 최적화 시작")
# 현재 통계 확인
get_database_stats()
# 최적화 실행
if optimize_database():
print("✅ 최적화 완료")
# 최적화 후 통계 확인
print("\n📊 최적화 후 통계:")
get_database_stats()
else:
print("❌ 최적화 실패")