콘텐츠로 이동

아키텍처 개요

시스템 구성도

┌──────────────────────────────────────────────────────────────────────┐
│  trend-collector (Docker container, 105번 서버)                       │
│                                                                      │
│  ┌────────────┐                                                      │
│  │ APScheduler │──── 매 60초 ────┐                                   │
│  └────────────┘                  ▼                                   │
│                         ┌─────────────────┐                          │
│                         │ trend_sources    │                          │
│                         │ DB 조회          │                          │
│                         │ (is_active=1 +   │                          │
│                         │  수집 시간 도래)  │                          │
│                         └────────┬────────┘                          │
│                                  ▼                                   │
│                         ┌─────────────────┐                          │
│                         │ CollectPipeline  │                          │
│                         └────────┬────────┘                          │
│                    ┌─────────────┼─────────────┐                     │
│                    ▼             ▼              ▼                     │
│              ┌──────────┐ ┌──────────┐  ┌──────────┐                │
│              │  Step 1   │ │  Step 2   │  │  Step 3   │               │
│              │  Scrape   │ │  Bronze   │  │  DB Save  │               │
│              └──────────┘ └──────────┘  └──────────┘                │
│                    │             │              │                     │
│  :18090 (health)   │             │              │                     │
└────────────────────┼─────────────┼──────────────┼────────────────────┘
                     │             │              │
              ┌──────▼──────┐  ┌──▼────────┐  ┌──▼────────────┐
              │  외부 사이트  │  │  RustFS    │  │    MySQL       │
              │  m.daum.net  │  │  104:9000  │  │  104:13306     │
              │  (스크래핑)   │  │  haotoday  │  │ trend_collector │
              └─────────────┘  └───────────┘  └───────────────┘

스케줄링 구조

매 60초마다 (APScheduler)
trend_sources 테이블 조회
  │  WHERE is_active = 1
소스별 마지막 수집 시각 확인 (trend_snapshots)
  │  last_collected_at + collect_interval_minutes <= NOW() ?
  ├── 시간 안 됨 → 스킵
  └── 시간 됨 → 해당 소스만 수집 실행
      Scrape → Bronze → DB

소스별 수집 주기는 trend_sources.collect_interval_minutes 컬럼으로 관리한다. 코드 수정 없이 DB UPDATE만으로 수집 주기 변경, 수집 중단(is_active=0)이 가능하다.

레이어 구조

main.py                          ← 엔트리포인트 + 스케줄러 + Health 서버
  │                                 스크래퍼 레지스트리 초기화
pipeline/collector.py            ← DB 조회 → 수집 대상 판별 → 수집 실행
  ├── scraper/                   ← 수집 + 파싱 계층
  │   ├── base.py                   TrendScraper ABC (확장 포인트)
  │   │                             ScrapedResult, ScrapedKeyword DTO
  │   └── daum.py                   DaumTrendScraper 구현
  ├── storage/                   ← 저장 계층
  │   ├── rustfs.py                 RustFS Bronze 저장 (S3 SDK)
  │   ├── database.py               SQLAlchemy 엔진/세션 관리
  │   └── repository.py             DB CRUD + 수집 대상 조회 (get_due_sources)
  └── domain/                    ← 도메인 모델
      └── models.py                 SQLAlchemy 엔티티 (3개 테이블)

데이터 흐름 상세

Step 0: 수집 대상 판별 (DB 기반)

매 60초마다 trend_sources 테이블에서 활성 소스를 조회하고, 마지막 수집 시각 + 수집 주기를 비교하여 수집 대상을 결정한다.

  • 조건: is_active = 1 AND last_collected_at + interval <= NOW()
  • 결과: 수집할 소스 목록 (0개면 스킵)

Step 1: Scrape (수집)

소스별 TrendScraper 구현체가 대상 사이트에 HTTP 요청을 보내고, HTML을 파싱하여 키워드를 추출한다.

  • 입력: URL
  • 출력: ScrapedResult (원본 HTML + 파싱된 키워드 리스트)
  • 실패 시: 에러 로그 + FAILED 스냅샷 DB 기록

Step 2: Bronze 저장 (RustFS)

원본 HTML과 파싱된 JSON을 RustFS Bronze 레이어에 보존한다. 재파싱, 디버깅, 감사 추적 목적.

  • 입력: ScrapedResult
  • 출력: S3 오브젝트 (raw.html + parsed.json)
  • 실패 시: warning 로그만 남기고 Step 3로 계속 진행 (Bronze는 선택적)

Step 3: DB 저장 (MySQL)

정규화된 트렌드 데이터를 MySQL에 적재한다.

  • 입력: ScrapedResult + Bronze 경로
  • 출력: trend_snapshots + trend_keywords 레코드
  • 실패 시: 에러 로그 + FAILED 스냅샷 DB 기록

확장 전략

새 소스 추가 시:

1. scraper/{source}.py 생성 (TrendScraper 상속)
2. main.py의 _init_scrapers()에 등록
3. DB trend_sources에 자동 INSERT (또는 수동 INSERT로 interval/active 설정)

파이프라인은 DB에서 활성 소스를 조회하여 레지스트리에 등록된 스크래퍼를 실행하므로, DB에서 is_active=0으로 변경하면 코드 수정 없이 수집을 중단할 수 있다.

→ 상세: ADR-004. Scraper 인터페이스

인프라 공유

오늘도하오(Hao-Day-Backend) 인프라를 공유하되 논리적으로 분리:

리소스 공유 방식 분리 기준
MySQL 같은 인스턴스 database 분리 (trend_collector vs haotoday)
RustFS 같은 버킷 prefix 분리 (data/bronze/trend/ vs data/bronze/hsk/)

→ 상세: ADR-002. DB 분리, ADR-003. Bronze 레이어