728x90
반응형
Django Migration 완벽 가이드
📚 목차
1. 모델 구조 변경 개요 2. 기존 테이블 제거 및 새 모델 적용 단계 3. 마이그레이션 충돌 해결 4. 새로운 모델 구조 설계 5. 데이터 백업 (중요!) 6. 문제 해결 팁 7. 실전 마이그레이션 예제1. 모델 구조 변경 개요
🎯 목표
- ChatRoom, ChatSession 테이블 제거
- ChatMessage 테이블을 Parent-Child 구조로 재설계
- 사용자 질문 (Parent) → AI 답변들 (Child) 1:N 관계
- 같은 질문에 대한 여러 AI 답변 저장 가능
🔄 변경 전후 비교
🔴 기존 구조
- ChatRoom
- ChatSession
- ChatMessage (단일 구조)
🟢 새로운 구조
- UserQuestion (Parent)
- AIResponse (Child)
- ConversationHistory (선택사항)
2. 기존 테이블 제거 및 새 모델 적용 단계
1
기존 마이그레이션 백업 및 새 마이그레이션 생성
# 1. 현재 마이그레이션 상태 확인
python manage.py showmigrations chat
# 2. 새로운 마이그레이션 생성 (기존 테이블 삭제)
python manage.py makemigrations chat --empty --name remove_old_tables
# 3. 새로운 모델을 위한 마이그레이션 생성
python manage.py makemigrations chat --name create_new_structure
2
기존 테이블 삭제 마이그레이션 파일 수정
# migrations/XXXX_remove_old_tables.py
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('chat', '이전_마이그레이션_번호'), # 실제 번호로 변경
]
operations = [
migrations.RunSQL(
"DROP TABLE IF EXISTS chat_sessions;"
),
migrations.RunSQL(
"DROP TABLE IF EXISTS chat_messages;"
),
migrations.RunSQL(
"DROP TABLE IF EXISTS chat_rooms;"
),
]
3
새로운 모델 정의
# chat/models.py - 새로운 구조
from django.db import models
from django.utils import timezone
from django.conf import settings
class UserQuestion(models.Model):
"""사용자 질문 모델 (Parent Table)"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="questions",
verbose_name="사용자",
)
content = models.TextField(verbose_name="질문 내용")
question_type = models.CharField(
max_length=20,
choices=[
("text", "텍스트"),
("image", "이미지 포함"),
("file", "파일 포함"),
],
default="text",
verbose_name="질문 유형",
)
attached_file = models.FileField(
upload_to="question_files/",
null=True,
blank=True,
verbose_name="첨부 파일"
)
is_processed = models.BooleanField(
default=False,
verbose_name="처리됨"
)
is_deleted = models.BooleanField(
default=False,
verbose_name="삭제됨"
)
created_at = models.DateTimeField(
default=timezone.now,
verbose_name="생성일"
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name="수정일"
)
class Meta:
db_table = "user_questions"
verbose_name = "사용자 질문"
verbose_name_plural = "사용자 질문들"
ordering = ["-created_at"]
def __str__(self):
content_preview = (
self.content[:50] + "..."
if len(self.content) > 50
else self.content
)
return f"{self.user.username}: {content_preview}"
class AIResponse(models.Model):
"""AI 응답 모델 (Child Table)"""
question = models.ForeignKey(
UserQuestion,
on_delete=models.CASCADE,
related_name="ai_responses",
verbose_name="관련 질문",
)
content = models.TextField(verbose_name="AI 응답 내용")
ai_model = models.CharField(
max_length=50,
verbose_name="AI 모델",
help_text="예: gpt-4, claude-3, gemini-pro 등"
)
response_time = models.FloatField(
null=True,
blank=True,
verbose_name="응답 시간(초)"
)
user_rating = models.IntegerField(
null=True,
blank=True,
choices=[(i, f"{i}점") for i in range(1, 6)],
verbose_name="사용자 평점 (1-5)"
)
is_selected = models.BooleanField(
default=False,
verbose_name="선택된 응답"
)
created_at = models.DateTimeField(
default=timezone.now,
verbose_name="생성일"
)
class Meta:
db_table = "ai_responses"
verbose_name = "AI 응답"
verbose_name_plural = "AI 응답들"
ordering = ["question", "-created_at"]
3. 마이그레이션 충돌 해결
⚠️ 주의: 마이그레이션 충돌이 발생하면 데이터 손실 위험이 있습니다. 반드시 백업을 먼저 수행하세요.
🔍 충돌 상황 확인
# 현재 마이그레이션 상태 확인
python manage.py showmigrations chat
# 충돌 감지 메시지:
# CommandError: Conflicting migrations detected; multiple leaf nodes...
🛠️ 해결 방법 1: 마이그레이션 병합
# 1. 충돌하는 마이그레이션들을 병합
python manage.py makemigrations --merge
# 2. 병합된 마이그레이션 적용
python manage.py migrate
# 3. 새로운 마이그레이션 생성
python manage.py makemigrations chat --name remove_old_models
🔥 해결 방법 2: 마이그레이션 리셋 (개발 환경)
# 1. 마이그레이션 파일 백업
cp -r chat/migrations chat/migrations_backup
# 2. 불필요한 마이그레이션 파일 삭제
rm chat/migrations/0002_*.py
rm chat/migrations/0003_*.py
# 3. 데이터베이스 마이그레이션 기록 삭제
python manage.py shell
4. 새로운 모델 구조의 장점
✨ 주요 특징
- 1:N 관계: 하나의 질문에 여러 AI 응답 저장
- 응답 순서 관리: response_order 필드로 응답 순서 추적
- 응답 선택: is_selected로 최적 응답 표시
- 사용자 평가: user_rating으로 응답 품질 평가
- 성능 추적: response_time으로 성능 모니터링
💻 사용 예시
# 사용자 질문 생성
question = UserQuestion.objects.create(
user=request.user,
content="Django에서 모델 관계를 어떻게 설정하나요?"
)
# 여러 AI 응답 생성
response1 = AIResponse.objects.create(
question=question,
content="ForeignKey를 사용하여...",
ai_model="gpt-4",
response_time=2.5
)
response2 = AIResponse.objects.create(
question=question,
content="다른 방법으로는...",
ai_model="claude-3",
response_time=1.8
)
# 선택된 응답 표시
response1.is_selected = True
response1.save()
# 질문의 모든 응답 조회
all_responses = question.ai_responses.all()
# 최고 평점 응답 조회
best_response = question.ai_responses.filter(
user_rating__isnull=False
).order_by('-user_rating').first()
5. 데이터 백업 (중요!)
🎯 핵심: 마이그레이션 전에 반드시 데이터를 백업하세요!
📦 전체 데이터베이스 백업
# 전체 데이터베이스 백업
python manage.py dumpdata chat > chat_backup.json
# 특정 모델별 백업
python manage.py dumpdata chat.ChatMessage > messages_backup.json
python manage.py dumpdata chat.ChatRoom > rooms_backup.json
python manage.py dumpdata chat.ChatSession > sessions_backup.json
# 날짜별 백업 파일명
python manage.py dumpdata chat > chat_backup_$(date +%Y%m%d_%H%M%S).json
🔄 데이터 복원
# 백업 데이터 복원
python manage.py loaddata chat_backup.json
# 특정 모델 복원
python manage.py loaddata messages_backup.json
6. 문제 해결 팁
🚨 일반적인 문제들
1
마이그레이션이 계속 실패하는 경우
# 마이그레이션 상태를 가짜로 표시 (주의해서 사용)
python manage.py migrate chat --fake
# 또는 특정 마이그레이션으로 되돌리기
python manage.py migrate chat 0001 --fake
# 마이그레이션 히스토리 확인
python manage.py showmigrations --verbosity=2
2
외래키 제약조건 오류
# 외래키 제약조건을 무시하고 삭제
from django.db import migrations
class Migration(migrations.Migration):
operations = [
# MySQL용
migrations.RunSQL("SET foreign_key_checks = 0;"),
migrations.RunSQL(
"DROP TABLE IF EXISTS chat_sessions;"
),
migrations.RunSQL(
"DROP TABLE IF EXISTS chat_messages;"
),
migrations.RunSQL(
"DROP TABLE IF EXISTS chat_rooms;"
),
migrations.RunSQL("SET foreign_key_checks = 1;"),
]
3
SQLite에서 테이블 삭제 시
# SQLite에서는 다음 명령어 사용
python manage.py dbshell
# SQLite shell에서:
.tables
DROP TABLE IF EXISTS chat_sessions;
DROP TABLE IF EXISTS chat_messages;
DROP TABLE IF EXISTS chat_rooms;
.quit
4
PostgreSQL에서 CASCADE 삭제
# PostgreSQL에서 관련 테이블까지 함께 삭제
DROP TABLE IF EXISTS chat_sessions CASCADE;
DROP TABLE IF EXISTS chat_messages CASCADE;
DROP TABLE IF EXISTS chat_rooms CASCADE;
⚠️ 주의사항
- 운영 환경에서는 반드시 백업 후 마이그레이션을 수행하세요
- 대용량 데이터가 있는 경우 마이그레이션 시간이 오래 걸릴 수 있습니다
- 외래키 제약조건으로 인해 테이블 삭제 순서가 중요합니다
- 마이그레이션 전후로 데이터 무결성을 검증하세요
7. 실전 마이그레이션 예제
🎯 완전한 마이그레이션 프로세스
# 1. 현재 상태 확인
python manage.py showmigrations
# 2. 충돌 해결 (필요시)
python manage.py makemigrations --merge
python manage.py migrate
# 3. 데이터 백업
python manage.py dumpdata chat > backup_before_migration.json
# 4. 기존 모델 제거 마이그레이션
python manage.py makemigrations chat --empty --name remove_old_models
# 5. 새 모델 생성 마이그레이션
python manage.py makemigrations chat --name create_new_structure
# 6. 마이그레이션 적용
python manage.py migrate
# 7. 새 구조 확인
python manage.py dbshell
.tables # SQLite의 경우
\dt # PostgreSQL의 경우
🔍 마이그레이션 검증
# 마이그레이션 후 데이터 검증
from chat.models import UserQuestion, AIResponse
# 기본 통계 확인
print(f"총 질문 수: {UserQuestion.objects.count()}")
print(f"총 응답 수: {AIResponse.objects.count()}")
print(f"처리된 질문 수: {UserQuestion.objects.filter(is_processed=True).count()}")
# 응답별 통계
response_stats = {}
for response in AIResponse.objects.values('ai_model').distinct():
model = response['ai_model']
count = AIResponse.objects.filter(ai_model=model).count()
response_stats[model] = count
print("AI 모델별 응답 통계:", response_stats)
# 질문당 평균 응답 수
if UserQuestion.objects.count() > 0:
avg_responses = AIResponse.objects.count() / UserQuestion.objects.count()
print(f"질문당 평균 응답 수: {avg_responses:.2f}")
# 선택된 응답 통계
selected_count = AIResponse.objects.filter(is_selected=True).count()
print(f"선택된 응답 수: {selected_count}")
🔧 단계별 마이그레이션 파일 예제
1
기존 모델 제거 마이그레이션
# migrations/0003_remove_old_models.py
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('chat', '0002_alter_chatmessage_ai_model'),
]
operations = [
# 외래키 제약조건 때문에 순서가 중요합니다
migrations.DeleteModel(name='ChatSession'),
migrations.DeleteModel(name='ChatMessage'),
migrations.DeleteModel(name='ChatRoom'),
]
2
새 모델 생성 마이그레이션
# migrations/0004_create_new_structure.py
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('chat', '0003_remove_old_models'),
]
operations = [
migrations.CreateModel(
name='UserQuestion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField(verbose_name='질문 내용')),
('question_type', models.CharField(choices=[('text', '텍스트'), ('image', '이미지 포함'), ('file', '파일 포함')], default='text', max_length=20, verbose_name='질문 유형')),
('attached_file', models.FileField(blank=True, null=True, upload_to='question_files/', verbose_name='첨부 파일')),
('is_processed', models.BooleanField(default=False, verbose_name='처리됨')),
('is_deleted', models.BooleanField(default=False, verbose_name='삭제됨')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='생성일')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='수정일')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to=settings.AUTH_USER_MODEL, verbose_name='사용자')),
],
options={
'verbose_name': '사용자 질문',
'verbose_name_plural': '사용자 질문들',
'db_table': 'user_questions',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='AIResponse',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField(verbose_name='AI 응답 내용')),
('ai_model', models.CharField(help_text='예: gpt-4, claude-3, gemini-pro 등', max_length=50, verbose_name='AI 모델')),
('response_time', models.FloatField(blank=True, null=True, verbose_name='응답 시간(초)')),
('user_rating', models.IntegerField(blank=True, choices=[(1, '1점'), (2, '2점'), (3, '3점'), (4, '4점'), (5, '5점')], null=True, verbose_name='사용자 평점 (1-5)')),
('is_selected', models.BooleanField(default=False, verbose_name='선택된 응답')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='생성일')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ai_responses', to='chat.userquestion', verbose_name='관련 질문')),
],
options={
'verbose_name': 'AI 응답',
'verbose_name_plural': 'AI 응답들',
'db_table': 'ai_responses',
'ordering': ['question', '-created_at'],
},
),
]
🎉 마이그레이션 완료 후 확인
# Django Admin에서 새 모델 확인
python manage.py createsuperuser # 필요시
python manage.py runserver
# 또는 Django shell에서 테스트
python manage.py shell
# Django shell에서 테스트
from django.contrib.auth.models import User
from chat.models import UserQuestion, AIResponse
# 테스트 사용자 생성 또는 가져오기
user = User.objects.first()
if not user:
user = User.objects.create_user(
'testuser',
'test@example.com',
'password'
)
# 테스트 질문 생성
question = UserQuestion.objects.create(
user=user,
content="Django 마이그레이션이 잘 작동하나요?",
question_type="text"
)
# 테스트 응답 생성
response = AIResponse.objects.create(
question=question,
content="네, 완벽하게 작동합니다!",
ai_model="test-model",
response_time=1.5
)
print(f"질문: {question}")
print(f"응답: {response}")
# 관계 확인
print("질문의 모든 응답:", question.ai_responses.all())
print("응답의 질문:", response.question)
✅ 체크리스트
- ✅ 데이터 백업 완료
- ✅ 마이그레이션 충돌 해결
- ✅ 기존 테이블 제거
- ✅ 새 모델 구조 적용
- ✅ 데이터 무결성 검증
- ✅ 기능 테스트 완료
728x90
반응형
'코딩' 카테고리의 다른 글
| endless project timeline & flow (0) | 2025.08.06 |
|---|---|
| django migration Reset (0) | 2025.08.06 |
| Authentication System Prompt - Endless-Login (0) | 2025.07.28 |
| 방법이 필요한 게 아니다. 필요한 것은 '무한한 아이디어; Ideas' 이다. (0) | 2025.07.28 |
| Name of New System Development Methodology (0) | 2025.07.28 |