Python을 사용한 비동기 만화 파서: 아키텍처부터 마이크로서비스까지
확장 가능한 만화 파서를 구축하는 것은 비동기 프로그래밍, 계약 설계, 상태 관리의 도전을 결합한 작업입니다. Manga-Day는 asyncio, Pydantic, SQLAlchemy를 사용해 모놀리스에서 마이크로서비스 아키텍처로 전환하는 실전 사례입니다. 이 프로젝트는 원래 수십 개 사이트에서 데이터를 자동으로 수집하는 도구로 구상되었지만, 확장 가능한 시스템을 설계하는 튜토리얼로 발전했습니다.
아키텍처 기반: 계약과 비동기성
시스템의 핵심은 추상 클래스 BaseSpider를 중심으로 구축됩니다. 각 구체적인 파서(예: multi-manga용)는 단일 인터페이스를 구현하여 코어 변경 없이 새로운 소스를 동적으로 추가할 수 있습니다. 계약에는 세 가지 주요 메서드가 포함됩니다:
from abc import ABC, abstractmethod
class BaseSpider(ABC):
BASE_URL = "https://example-manga.com"
@abstractmethod
async def get(self, url: str, **kwargs) -> Optional[MangaSchema]: ...
@abstractmethod
async def pages(
self, start_page: int | None = None
) -> AsyncGenerator[list[BaseManga], Any]: ...
이 접근 방식은 다음을 보장합니다:
- 오류와 타임아웃의 일관된 처리
- 각 spider의 독립적인 테스트 가능성
- 비동기 제너레이터를 통한 수평 확장 지원
비동기성은 asyncio 수준에서 구현되어 I/O 블로킹 없이 수백 개의 동시 요청을 효율적으로 처리할 수 있습니다. 이는 느리거나 불안정한 소스와 작업할 때 특히 중요합니다.
데이터 검증과 ORM 모델
Pydantic은 엄격한 타입 지정과 검증을 위해 사용됩니다. 시스템은 세 수준의 모델로 구축됩니다:
- BaseManga — 최소 필드 세트: title, poster, url, sku (SHA256 해시)
- MangaWithGallery — 이미지 URL 목록 추가
- MangaSchema — 장르, 작가, 언어(선택적)를 포함한 전체 모델
SQLAlchemy로 관리되는 데이터베이스에는 다음 엔티티가 구현됩니다:
- Manga, Author, Genre, Language — 핵심 테이블
- GenreManga — 장르를 위한 다대다 관계
- Gallery — 메인 쿼리와 함께 로드하지 않도록 이미지용 별도 테이블
- GeneratedPdf — 제거 예정인 폐기 테이블
갤러리를 별도 엔티티로 분리함으로써 만화 목록 로드 시 N+1 문제를 피했습니다—이미지는 이제 명시적 요청 시에만 로드됩니다.
매니저: 데이터 위의 비즈니스 로직
매니저는 비즈니스 로직 계층 역할을 합니다—특정 작업을 담당하는 클래스들입니다:
- MangaManager: SKU, URL, ID 기준 CRUD 작업
- RequestManager: 재시도 로직과 타임아웃이 포함된 HTTP 요청
- AlertManager: 범용 알림 시스템 (Telegram, Discord 등)
- SpiderManager: 모든 spider의 중앙 오케스트레이터
SpiderManager는 모듈성 덕분에 특히 흥미롭습니다. 네 개 파일로 분리됩니다:
_load.py—__all__을 통한 동적 spider 로딩_status.py— 상태 모델과 enum_starter.py— 실행 및 생명 주기 관리_spider.py— 컴포넌트를 단일 인터페이스로 결합
이 분해는 테스트를 간소화하고 변경을 격리시켰습니다—예를 들어, 새 spider 추가 시 __init__.py에 등록만 하면 코어 수정 없이 가능합니다.
마이크로서비스로의 전환: 문제점과 해결책
프로젝트는 모놀리스로 시작했지만, 두 서비스가 변화의 촉매가 되었습니다:
- PDFService — RAM을 너무 많이 소비하여 별도 컨테이너로 이동
- Bot — WebSocket과 JWT 인증이 필요하여 격리된 서비스로 마이그레이션
마이그레이션은 여러 단계를 거쳤습니다:
- 프론트엔드가 코어에서 분리—이제 REST API만 통해 작동하며, Jinja는 구성만 위해 최소 사용
- Bot이 aiohttp를 사용해 WebSocket으로 재작성하고 JWT를 통한 역할 기반 액세스 구현
- PDF 생성이 이제 제한된 메모리 사용의 백그라운드 작업으로 실행
주요 도전 과제:
- 서비스 간 복잡한 객체 직렬화
- 부분 실패 시 데이터 일관성
- 헬스 체크 설정과 우아한 종료
주요 교훈
- 구현보다 계약이 더 중요 — 추상 클래스가 코어 리팩토링 없이 확장을 가능하게 함
- 비동기성 ≠ 자동 성능 — 동시 작업 수를 제어하고 세마포어 사용
- ORM은 만능이 아님 — 수동 쿼리 최적화와 비정규화(예: 갤러리 분리)가 성능에 핵심
- 마이크로서비스는 복잡성을 더하지만 유연성을 제공 — PDFService 분리로 메모리 문제 해결했으나 배포 복잡성 증가
- 테스트 가능성은 아키텍처 결과물 — SpiderManager의 모듈성이 외부 의존성 모킹 없이 단위 테스트 허용
이 프로젝트는 수년간 진화할 수 있는 시스템 구축 방법을 이해하는 출발점이 되었습니다. 코드베이스는 수만 줄로 성장했지만, 명확한 책임 분리로 유지보수가 가능합니다. 다음 단계로는 작업 큐를 위한 메시지 브로커 구현과 모든 풀 리퀘스트에 대한 자동 테스트를 포함한 완전한 CI/CD가 있습니다.
— Editorial Team
아직 댓글이 없습니다.