홈으로 돌아가기

비동기 만화 파서: Python에서의 아키텍처

Python 비동기 만화 파서 아키텍처 상세 분석. 추상 클래스를 통한 계약, Pydantic 검증, SQLAlchemy ORM 및 마이크로서비스 전환 다룸. 확장성과 코드베이스 유지보수성 강조.

Python 만화 파서: 계약에서 마이크로서비스까지
Advertisement 728x90

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]: ...

이 접근 방식은 다음을 보장합니다:

Google AdInline article slot
  • 오류와 타임아웃의 일관된 처리
  • 각 spider의 독립적인 테스트 가능성
  • 비동기 제너레이터를 통한 수평 확장 지원

비동기성은 asyncio 수준에서 구현되어 I/O 블로킹 없이 수백 개의 동시 요청을 효율적으로 처리할 수 있습니다. 이는 느리거나 불안정한 소스와 작업할 때 특히 중요합니다.

데이터 검증과 ORM 모델

Pydantic은 엄격한 타입 지정과 검증을 위해 사용됩니다. 시스템은 세 수준의 모델로 구축됩니다:

  • BaseManga — 최소 필드 세트: title, poster, url, sku (SHA256 해시)
  • MangaWithGallery — 이미지 URL 목록 추가
  • MangaSchema — 장르, 작가, 언어(선택적)를 포함한 전체 모델

SQLAlchemy로 관리되는 데이터베이스에는 다음 엔티티가 구현됩니다:

Google AdInline article slot
  • Manga, Author, Genre, Language — 핵심 테이블
  • GenreManga — 장르를 위한 다대다 관계
  • Gallery — 메인 쿼리와 함께 로드하지 않도록 이미지용 별도 테이블
  • GeneratedPdf — 제거 예정인 폐기 테이블

갤러리를 별도 엔티티로 분리함으로써 만화 목록 로드 시 N+1 문제를 피했습니다—이미지는 이제 명시적 요청 시에만 로드됩니다.

매니저: 데이터 위의 비즈니스 로직

매니저는 비즈니스 로직 계층 역할을 합니다—특정 작업을 담당하는 클래스들입니다:

  • MangaManager: SKU, URL, ID 기준 CRUD 작업
  • RequestManager: 재시도 로직과 타임아웃이 포함된 HTTP 요청
  • AlertManager: 범용 알림 시스템 (Telegram, Discord 등)
  • SpiderManager: 모든 spider의 중앙 오케스트레이터

SpiderManager는 모듈성 덕분에 특히 흥미롭습니다. 네 개 파일로 분리됩니다:

Google AdInline article slot
  • _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

Advertisement 728x90

다음 읽기