자동화 slecs

API 속도 제한으로 느려지던 자동화 봇 안정화

목차

멤버 정보를 외부 API에서 수집해 enriching 하는 봇(enrich_member_bio.py)이 Wikipedia 의 429 Too Many Requests 에러로 자주 중단되곤 했다. 이번에 요청 방식을 개별 fetch 에서 group-level fetch 로 개선해서 API 속도 제한을 회피하게 했다.

왜 이런 일이 생겼나

자동화 봇이 수백 명의 멤버 정보를 enriching 할 때마다, 각 멤버마다 Wikipedia API 를 독립적으로 호출하고 있었다. 즉 한 사이클에 멤버 수만큼 API 요청이 발생한다. Wikipedia 같은 공개 API 는 대부분 rate limiting 을 두고 있는데—보통 초당 요청 수(requests per second)나 분당 요청 수로 제한—개별 호출 방식이면 순간적으로 그 한계를 깨뜨리기 쉽다.

이런 문제는 여러 곳에서 본다. 보통은 두 가지 증상으로 나타난다. 하나는 봇이 중간에 중단되거나 에러를 반복하는 것. 다른 하나는 응답이 갑자기 느려지다가 결국 타임아웃 되는 것. 이번 경우는 전자였다. 429 에러가 떨어지면 봇 프로세스가 재시도 로직에 빠졌을 테고, 그러다 결국 포기하거나 사람이 수동으로 개입해야 하는 상황까지 간다.

해결 방법: 그룹 단위 fetch 로 리모델링

group-level fetch 는 요청을 배치(batch) 단위로 묶어서 보내는 전략이다. 예를 들어 100 명의 멤버를 한 번에 100 개 요청 대신, 10 명 묶음 10 개 요청으로 변경하는 식이다. 혹은 API 가 배치 쿼리를 지원하면 더 직접적으로 한 번의 쿼리에 여러 항목을 포함시킬 수 있다.

이렇게 하면 두 가지 장점이 생긴다:
- 요청 간격을 의도적으로 늘릴 수 있다. 배치가 끝나면 약간의 대기(sleep) 를 주고, 다음 배치를 시작하는 식으로 rate limit 을 사전에 존중할 수 있다.
- 전체 완료 시간은 크게 늘지 않는다. 순차 처리가 아니라면 말이다. 병렬 처리나 파이프라인 구조를 유지하면서도 동시 요청 수를 제어할 수 있다.

코드 개선의 패턴은 대략 이렇다:

# Before: 개별 호출
for member in members:
    bio = fetch_from_wikipedia(member.name)  # 요청 폭주
    member.bio = bio

# After: 그룹 단위 호출 + 딜레이
BATCH_SIZE = 10
for i in range(0, len(members), BATCH_SIZE):
    batch = members[i:i + BATCH_SIZE]
    bios = batch_fetch_from_wikipedia([m.name for m in batch])
    for member, bio in zip(batch, bios):
        member.bio = bio
    time.sleep(1)  # 배치 간 대기로 rate limit 회피

봇 자동화와 외부 API 의존성

팀 입장에서 이런 작업은 단순 버그 수정이 아니라 시스템 안정성 개선이다. 봇이 몇 시간마다 실패해서 사람이 다시 돌려야 하는 상황은 자동화의 가치를 반감시킨다. 특히 멤버 정보 enrichment 같은 배경 작업은 24/7 일관되게 돌아야 하고, 가끔 실패했다가 성공하는 패턴은 데이터 일관성을 해친다.

외부 API 를 쓸 때 rate limiting 은 피할 수 없는 제약이다. Wikipedia 뿐만 아니라 거의 모든 공개 API(Google, OpenAI, GitHub 등)가 요청 제한을 둔다. 그래서 처음부터 이를 염두에 두고 설계하는 게 좋다. 몇 가지 일반적인 전략:

  • 배치 처리: 이 fix 처럼, 요청을 묶어서 보낸다.
  • 지수 백오프(exponential backoff): 429 를 받으면 대기 시간을 점차 늘리며 재시도한다.
  • 로컬 캐싱: 같은 정보를 반복해서 요청하지 않도록 캐시를 둔다.
  • API 선택: 같은 정보를 제공하는 여러 API 가 있다면, 속도 제한이 관대한 것을 선택하거나 전환한다.

이번 경우엔 배치 처리가 가장 직설적이었을 것 같다. 캐싱도 고려할 수 있겠지만—예를 들어 어제 enriching 한 멤버는 다시 fetch 하지 않기—그건 데이터 신선도와의 트레이드오프다.

회고와 다음

이런 문제가 생기는 건 초기 개발 때 "한 번에 몇 명 정도"를 가정하고 만들었다가, 나중에 스케일이 커지면서 드러나는 패턴이다. 처음부터 "몇 백 명, 몇 천 명을 한 번에 처리한다면?" 을 생각하면 좋지만, 실제로는 필요해질 때 개선하는 게 현실적이다. 그 과정에서 배치 처리, rate limiting 회피, 재시도 로직 같은 기법들을 자연스럽게 배운다.

한 가지 더 체크할 만한 것: 이제 이 봇이 안정화됐으니, 비슷한 패턴을 쓰는 다른 외부 API 호출 봇들도 검토해볼 가치가 있다. 예를 들어 다른 enrichment 봇, 메타데이터 수집 봇 같은 것들도 같은 문제를 겪고 있을 수 있다. 이참에 코드 리뷰나 일반 지침으로 남기면 앞으로 같은 실수를 줄일 수 있다.


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

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

댓글 0

첫 댓글 달아줘.