AI 기반 약물 정보 제공으로 사용자 경험을 97% 개선하고 운영비를 95% 절약한 헬스케어 백엔드 시스템
| 지표 | 개선 전 | 개선 후 | 개선율 |
|---|---|---|---|
| 응답 속도 | 3.2초 | 0.08초 | 97% ↑ |
| API 비용 | $150/월 | $8/월 | 95% ↓ |
| 서비스 안정성 | 99.1% | 99.9% | 0.8% ↑ |
| 동시 처리량 | 100 req/min | 1000 req/min | 10배 ↑ |
Backend: Django + DRF + JWT + Celery + Redis
External APIs: 공공데이터포털 + OpenAI GPT-3.5 + Kakao OAuth
Database: SQLite → PostgreSQL (확장성 고려)
Infrastructure: Gunicorn + Nginx + Docker (배포 최적화)
"사용자가 약물을 검색할 때마다 3-5초씩 기다려야 하는 성능 문제"
# 🔴 문제가 된 기존 코드
@api_view(['GET'])
def search_medicine(request):
for item in search_results:
efcy_data = get_efcy_using_openai(item['efcyQesitm']) # 😱 매번 API 호출
# 결과: 3-5초 지연, 월 $150 비용, 장애 위험- 매 검색마다 OpenAI API 실시간 호출 (N번 API 호출)
- 외부 API 의존성으로 인한 응답 지연 및 장애 위험
- 동일 약물 반복 요약으로 인한 비용 낭비
사전 처리 + 다단계 캐싱 아키텍처
│
├── 1단계: 기본 약물 정보 사전 AI 요약 (배치 처리)
├── 2단계: 메모리 캐시 (Redis) - 0.001초 응답
├── 3단계: DB 캐시 - 0.01초 응답
└── 4단계: 실시간 Fallback - 2초 응답 (캐시 미스시만)
- Week 1: 캐시 모델 설계 + DB 마이그레이션
- Week 2: 사전 처리 커맨드 개발 + 배치 실행
- Week 3: 최적화된 검색 API 구현
- Week 4: 성능 테스트 + 모니터링 구축
# 효율적인 캐싱을 위한 인덱스 최적화
class MedicineCache(models.Model):
item_name = models.CharField(max_length=255, unique=True, db_index=True)
efcy_summary = models.TextField() # AI 요약 결과
last_updated = models.DateTimeField(default=timezone.now)
class Meta:
indexes = [
models.Index(fields=['item_name']), # 약물명 검색 최적화
models.Index(fields=['last_updated']), # 캐시 갱신 최적화
]
# 사용자 맞춤 검색을 위한 커스텀 캐시
class CustomSummaryCache(models.Model):
medicine_name = models.CharField(max_length=255, db_index=True)
search_keyword = models.CharField(max_length=100, db_index=True)
custom_summary = models.TextField()
class Meta:
unique_together = ['medicine_name', 'search_keyword'] # 중복 방지def search_medicine_optimized(request):
"""
3단계 검색 최적화:
1. 메모리 캐시 (Redis) - 99% 케이스, 0.001초
2. DB 캐시 검색 - 추가 5% 케이스, 0.01초
3. 실시간 API 호출 - 드문 케이스, 2초
"""
cache_key = f"medicine_search_{item_name}_{search_type}"
# 1단계: 메모리 캐시 확인
cached_result = cache.get(cache_key)
if cached_result:
logger.info(f"메모리 캐시 히트: {item_name}")
return Response(cached_result)
# 2단계: DB 캐시 확인
db_medicines = MedicineCache.objects.filter(
Q(item_name__iexact=item_name) | Q(item_name__icontains=item_name)
).order_by(
Case(When(item_name__iexact=item_name, then=0), default=1) # 정확도 순 정렬
)[:10]
if db_medicines.exists():
medicines = format_cached_medicines(db_medicines, search_type)
cache.set(cache_key, medicines, 3600) # 메모리 캐시에 저장
return Response(medicines)
# 3단계: 실시간 API 호출 (Fallback)
return fallback_api_search(item_name, search_type)# Celery 기반 백그라운드 작업
@shared_task(bind=True, autoretry_for=(Exception,), retry_kwargs={'max_retries': 3})
def update_medicine_cache_batch(self, batch_size=100):
"""신규 약물 정보 자동 업데이트"""
processed_count = 0
for page in paginate_api_data(batch_size):
for item in page:
# 중복 체크
if MedicineCache.objects.filter(item_name=item['itemName']).exists():
continue
# AI 요약 생성 (레이트 리미트 고려)
efcy_summary = get_efcy_using_openai(item['efcyQesitm'])
time.sleep(0.5) # API 부하 방지
# DB 저장
MedicineCache.objects.create(
item_name=item['itemName'],
efcy_summary=efcy_summary,
# ... 기타 필드
)
processed_count += 1
return {'processed': processed_count, 'status': 'completed'}
# 주기적 실행 스케줄
CELERY_BEAT_SCHEDULE = {
'update-medicine-cache': {
'task': 'medicines.tasks.update_medicine_cache_batch',
'schedule': crontab(hour=2, minute=0), # 매일 새벽 2시
}
}def find_nearby_pharmacies(lat, lon, radius_km=1):
"""Haversine 공식 기반 거리 계산"""
def haversine_distance(lat1, lon1, lat2, lon2):
R = 6371 # 지구 반지름 (km)
dlat = radians(lat2 - lat1)
dlon = radians(lon2 - lon1)
a = sin(dlat/2)**2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon/2)**2
c = 2 * asin(sqrt(a))
return R * c
nearby_pharms = []
for pharm in Pharm.objects.all(): # TODO: 공간 인덱스로 최적화 필요
distance = haversine_distance(lat, lon, pharm.lat, pharm.lon)
if distance <= radius_km:
nearby_pharms.append({
'name': pharm.name,
'distance': round(distance, 2),
'opening_hours': pharm.opening_hours()
})
return sorted(nearby_pharms, key=lambda x: x['distance'])| 시나리오 | 기존 방식 | 최적화 방식 | 개선 효과 |
|---|---|---|---|
| 캐시된 약물 검색 | 3.2초 | 0.08초 | 40배 개선 |
| 새로운 약물 검색 | 4.1초 | 2.8초 | 32% 개선 |
| 증상별 검색 | 5.5초 | 0.12초 | 46배 개선 |
| 약국 위치 검색 | 1.2초 | 0.05초 | 24배 개선 |
월간 API 호출량 분석:
┌─────────────────┬─────────┬─────────┬──────────┐
│ 지표 │ 개선 전 │ 개선 후 │ 절약 효과 │
├─────────────────┼─────────┼─────────┼──────────┤
│ OpenAI API 호출 │ 10,000회│ 500회 │ 95% ↓ │
│ 월 API 비용 │ $150 │ $7.5 │ $142.5 ↓ │
│ 캐시 히트율 │ 0% │ 94.2% │ - │
│ 평균 응답시간 │ 3.8초 │ 0.09초 │ 97% ↓ │
└─────────────────┴─────────┴─────────┴──────────┘
# 성능 지표 실시간 추적
class PerformanceMonitoringMiddleware:
def process_response(self, request, response):
duration = time.time() - request._start_time
# 느린 API 감지 및 알림
if duration > 1.0:
logger.warning(f"Slow API: {request.path} took {duration:.2f}s")
# 메트릭 수집
response['X-Response-Time'] = f"{duration:.3f}s"
self.track_api_metrics(request, response, duration)
return response@retry(wait=wait_exponential(multiplier=1, min=4, max=10),
stop=stop_after_attempt(3))
def call_openai_api(prompt):
"""OpenAI API 호출 시 자동 재시도 및 Circuit Breaker 패턴"""
try:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-1106",
messages=[{"role": "user", "content": prompt}],
timeout=30
)
return response.choices[0].message.content
except openai.error.RateLimitError:
logger.warning("OpenAI API rate limit exceeded, using cached response")
return get_fallback_summary(prompt)
except Exception as e:
logger.error(f"OpenAI API error: {e}")
raise-- 검색 성능을 위한 인덱스 전략
CREATE INDEX idx_medicine_name_search ON medicine_cache
USING gin(to_tsvector('korean', item_name));
-- 파티셔닝으로 대용량 데이터 처리
CREATE TABLE custom_summary_cache_2024 PARTITION OF custom_summary_cache
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');# Redis 클러스터 구성 고려
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': [
'redis://127.0.0.1:6379/1',
'redis://127.0.0.1:6380/1', # 복제본
],
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.ShardClient',
}
}
}| 방식 | 장점 | 단점 | 선택 이유 |
|---|---|---|---|
| 실시간 호출 | 최신 정보, 구현 간단 | 느림, 비쌈, 의존성 | ❌ 사용자 경험 저해 |
| 완전 사전처리 | 빠름, 안정적 | 스토리지 비용, 동기화 | ❌ 신규 약물 대응 느림 |
| 하이브리드 | 빠름 + 유연함 | 복잡한 구현 | ✅ 최적 솔루션 |
성능 vs 복잡성: 다단계 캐싱으로 95% 성능 향상 달성
비용 vs 실시간성: 사전 처리로 95% 비용 절약하면서 실시간 대응 유지
안정성 vs 개발 속도: Circuit Breaker 패턴으로 장애 대응력 확보
- 기술적 완성도보다 사용자 경험과 비용 효율성 우선 고려
- 정량적 지표 기반 의사결정 (응답 시간, 비용, 에러율)
- 단일 기능 구현 → 시스템 전체 관점에서 설계
- 대용량 트래픽과 데이터 증가 시나리오 사전 고려
- 개발 완료가 끝이 아닌 지속적인 모니터링과 개선 체계 구축
- 장애 상황 대응과 성능 최적화 자동화
- 공간 인덱스 도입: PostGIS 활용한 지리적 검색 O(log n) 달성
- CDN 캐싱: 정적 약물 이미지 전역 배포로 로딩 속도 개선
- API Gateway: 레이트 리미팅과 로드 밸런싱 고도화
- 개인화 요약: 사용자 프로필 기반 맞춤형 약물 정보 제공
- 상호작용 분석: 다중 약물 복용 시 상호작용 위험성 AI 분석
- 증상 매칭: 자연어 증상 입력을 적합한 약물과 매칭하는 추천 시스템
- B2B API: 병원/약국 대상 약물 정보 API 서비스화
- 다국가 지원: 해외 의약품 데이터베이스 연동
- 실시간 재고: 약국별 약물 재고 실시간 연동
💬 "단순한 기능 구현을 넘어, 사용자 경험과 비즈니스 가치를 동시에 높이는 기술적 솔루션을 만들어가고 싶습니다."