개발 slecs

소식·굿즈 피드 병렬 수집으로 완성

목차

최근발매 정보를 실시간으로 보여주려면 4개 데이터 소스(뉴스, SNS, 쇼핑몰, 커뮤니티)에서 동시에 정보를 수집해야 했다. 순차 처리로는 30초가 걸리지만, 병렬로 4팀 리서치를 시작하면서 이 작업은 완전히 달라졌다. DB 스키마 확장부터 백엔드 파이프라인, 프론트엔드 렌더링까지 모두 연결한 첫 번째 데이터 피드 완성 이야기다.

병렬 처리는 왜 필요했나

처음 설계 회의에서 나는 "소식 api 콜, 기다렸다가 굿즈 콜, 또 기다렸다가…" 순차 흐름으로만 생각했다. 그런데 각 소스마다 응답 시간이 다르다는 걸 알게 됐다. 한 소스가 5초를 먹으면 전체 파이프라인이 5초 블로킹된다. 네 개를 동시에 요청하면? 가장 느린 놈(보통 2~3초)만 기다리면 되고, 나머지는 병렬이니까 겹친다. 8초 vs 30초의 차이는 운영 관점에서 꽤 크다. 밤 11시에 도는 크론 잡이 30초면 다음 잡과 겹치기 쉽지만, 8초면 충돌 확률이 훨씬 낮아진다.

bot/enrich_seed.py — 씨드 데이터 채우기

"enrich"라는 표현이 재밌는데, 이건 단순히 외부 API를 fetch하는 게 아니라 기존 데이터에 추가 컨텍스트를 덧붙이는 의미다. 뉴스 제목이 들어오면, 우리 시스템이 필요한 형태로 노멀라이즈하고(타임스탬프 포맷, 텍스트 인코딩), 중복을 제거하고, 품질 필터링을 한다.

병렬 처리에서 가장 신경 쓴 부분은 부분 실패 처리다. 4개 소스 중 1개가 500 에러를 뱉으면 어떻게 할까? 내가 원한 건 "나머지 3개는 계속 진행, 1개는 별도 로깅해서 수동 리트라이"였다. 전체 파이프라인을 멈추는 것보다, 팀이 상황을 빠르게 파악하고 조치할 수 있게 하는 게 중요했다.

# 의사 코드
results = []
for source in [news_api, sns_crawler, shop_api, community_feed]:
    try:
        data = await source.fetch()
        results.append(normalize(data))
    except Exception as e:
        log_error(f"Source {source.name} failed: {e}")
        # 나머지는 계속 진행

이렇게 하면 대시보드에 "3/4 sources OK, 1 retry needed"라고 깔끔하게 뜬다.

스키마: 테이블 분리 vs 통합

처음엔 feed 하나로 모두 담으면 어떨까 했다. 근데 테이블 설계를 진행하다 보니 소식과 굿즈가 너무 달랐다.

구분 뉴스 굿즈
주요 필드 제목, URL, 요약, 발행사 상품명, 가격, 재고, 판매링크
검색 패턴 키워드, 카테고리 가격 범위, 재고 여부
데이터 신선도 몇 시간마다 매분

한 테이블에 강제로 담으면 news.price IS NULL, goods.article_url IS NULL 같은 낭비가 생긴다. 결국 도메인별로 테이블을 분리하고, 프론트에서 UNION ALL로 통합 피드를 만들기로 했다. 스키마 설계는 명확하고(각 테이블이 자신의 의미에만 집중), 쿼리는 유연하게.

src/lib/db.ts — 쿼리 통합

새 테이블에 대한 쿼리 헬퍼를 추가했다. 핵심은 getLatestFeed() 함수다. UNION ALL을 쓴 이유는 뉴스와 굿즈 사이에 중복은 절대 없다(서로 다른 테이블)는 보장이 있기 때문. UNION을 쓰면 불필요한 DISTINCT 연산이 들어가서 느려진다. 작은 최적화지만, 홈페이지가 피크타임에 수천 req/s를 받을 때 이런 것들이 쌓인다.

페이지네이션과 선택적 필터(카테고리, 날짜 범위)도 고려해서 쿼리를 설계했다. SQL에서 계산 부담을 최소화하고, 인덱싱이 잘 먹도록.

프론트엔드: 피드 렌더링

pages/index.astro는 홈 화면이니까 전체 최근발매를 카루셀로 보여주고, pages/groups/[slug].astro는 특정 그룹과 관련된 소식·굿즈만 필터링해서 좌측 사이드바에 표시했다.

데이터 페칭은 서버사이드 렌더링 시점(빌드 또는 요청 시간)에 일어난다. 방문자는 이미 최신 정보가 담긴 HTML을 받으니까 자바스크립트 로딩 후 데이터 fetch를 기다릴 필요가 없다.

팀 협업 관점 회고

1) 병렬 처리는 모니터링이 필수

처음 구현했을 때는 타임로그가 부족했다. "파이프라인이 느려졌어"라는 슈팅이 들어오면, 원인이 뉴스 API인지 SNS 크롤러인지 알 수가 없었다. 지금은 각 소스별로 start/end 타임스탬프를 기록해서, 병목을 정확히 파악할 수 있도록 했다. 이게 다음번 최적화의 출발점이 된다.

2) 스키마 버전 관리

이전에 비슷한 작업에서, 스키마를 확장했는데 일부 구버전 앱이 새 테이블을 모르면서 에러가 난 적이 있다. 이번엔 schema_version 테이블에 현재 버전을 기록하고, 앱 시작 시 최소 요구 버전을 체크하도록 했다. 마이그레이션 중에도 버전 체크로 호환성을 보장한다.

3) 증분 롤아웃

네 개 소스 모두를 한 번에 켜지 않았다. 1개씩 켜서, 데이터 품질과 성능을 검증한 후 다음 소스를 활성화했다. "피드의 10%만 새 소스"에서 시작해서 점진적으로 비율을 높혔다. 문제가 생기면 빠르게 롤백할 수 있는 구조다.

다음

이제 피드에 사용자별 개인화 랭킹을 넣을 차례다. 지금은 모든 사용자가 같은 순서로 피드를 본다. 하지만 클릭, 스크롤, 체류 시간 데이터를 모으면, 각자 관심사에 맞춘 피드로 개선할 수 있다. 그리고 리얼타임 알림도 고려 중이다. "당신이 찜한 브랜드의 새 상품이 나왔어요"라는 느낌으로.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.