데이터파이프라인

6,000회 호출을 한 번에 돌릴 수 없을 때: 중단 재개형 백필 설계

2026. 08. 24

KT Enterprise B2B 에이전트에서 조달 공고 1년치를 수집하려면 API 호출이 6,000회를 넘습니다. 한 번에 돌릴 수 없는 작업을 진행 상태·호출 예산·재개 가능성 세 축으로 나눠 설계한 기록입니다.

프로젝트 배경

소속
휴트리온 · AX·DX사업부 AI개발팀 (Client: KT Enterprise)
프로젝트
KT Enterprise B2B AI Agent · Account Plan 자동 생성
기간
2026.07 ~ 진행중 (2026.09 베타 · 12 상용 목표)
담당
Account Plan 에이전트 1인 전담 · 수집~대시보드 전 계층

KT Enterprise 영업 조직의 생산성 개선을 위해, 영업 대상 기업의 공시(DART)·뉴스·나라장터(G2B) 조달 데이터를 자동 수집·분석해 Account Plan 초안을 생성하는 백엔드 파이프라인입니다. 전체 10명(휴트리온 5·KT ds 5) 규모에서 에이전트 단위 1인 전담 체제로 운영되며, 데이터 수집·전처리부터 분석 에이전트 8종 설계, LLM 연동·비용 통제, 대시보드 API까지 해당 에이전트의 전 계층을 단독으로 맡고 있습니다.

데이터파이프라인배치설계멱등성API제약상용서비스

1. 배경: 한 번에 끝낼 수 없는 6,000회 호출

KT Enterprise B2B 에이전트는 영업 대상 기업의 조달 이력을 근거로 Account Plan 초안을 생성합니다. 어떤 공공기관 사업에 참여했고 어떤 분야에서 낙찰받았는지가 영업 담당자에게 가장 유용한 정보이기 때문입니다.

문제는 조달 공고 데이터를 과거 1년치까지 확보해야 한다는 점이었습니다. 최근 며칠치만으로는 기업의 사업 방향을 판단할 근거가 되지 않습니다.

30분 주기 수집기는 이미 동작하고 있었습니다. 다만 그것은 앞으로 들어올 공고를 받는 구조이고, 과거 데이터를 소급해서 채우는 것은 성격이 다른 작업이었습니다.

2. 문제 정의: 한 번에 끝낼 수 없는 작업

하루치를 스캔하는 함수는 이미 있었습니다. 날짜만 바꿔 365번 부르면 되는 단순한 반복으로 보였습니다.

실제로 돌려보니 두 가지가 걸렸습니다.

첫째, 호출량이 큽니다. 하루를 스캔하면 업무 유형(용역·물품·공사)마다 페이지를 넘기며 조회하기 때문에 하루당 실측 평균 17회가 나갑니다. 365일이면 6,000회를 넘고 수십 분이 소요됩니다.

둘째, 중간에 끊기면 처음부터입니다. 어디까지 처리했는지 남는 곳이 없어서, 네트워크가 한 번 끊기거나 API 한도에 걸리면 그때까지 쓴 호출이 전부 버려집니다. 6,000회짜리 작업에서 5,000회 지점에 실패하면 손실이 큽니다.

3. 기술적 난점

단순 반복문으로 보이던 작업이 어려워진 이유는 외부 API 라는 제약 때문이었습니다.

내부 연산이라면 실패해도 다시 돌리면 그만입니다. 그러나 외부 API 호출은 하루 한도가 있고, 한도를 넘기면 차단됩니다. 재시도 자체가 비용입니다.

여기에 조건이 하나 더 있었습니다. 이 백필 도구를 만들기 전에 다른 방식으로 이미 긁어둔 구간이 존재했습니다. 진행 기록이 없다는 이유로 그 날짜를 다시 조회하면 한도를 헛되이 소모합니다.

결과적으로 "어디까지 했는가"를 시스템이 알아야 하는데, 그 정보가 코드 밖(이미 쌓인 DB)에도 일부 존재하는 상태였습니다.

4. 대안 검토

1안. 한 번에 끝까지 돌린다

  • 장점: 구현이 가장 단순합니다.
  • 단점: 실패 시 손실이 전부입니다. 그리고 하루 한도를 초과할 가능성이 높아, 완주하더라도 다른 수집 작업이 차단될 위험이 있습니다.

2안. 스케줄러에 맡겨 매일 조금씩 채운다

  • 장점: 한도 초과 위험이 없습니다.
  • 단점: 완료 시점을 통제할 수 없습니다. 베타 일정이 정해져 있는 상황에서 언제 1년치가 채워질지 모르는 구조는 받아들이기 어려웠습니다.

3안. 진행 상태를 남기고 예산 안에서 나눠 실행한다

  • 장점: 실행 시점과 소모량을 사람이 통제하면서, 중단되어도 손실이 마지막 한 단위로 제한됩니다.
  • 단점: 상태 관리 계층을 직접 만들어야 합니다.

5. 설계 결정 및 근거

3안을 채택했습니다. 판단 기준은 손실의 단위를 얼마나 작게 만들 수 있는가였습니다. 1안은 손실 단위가 전체이고, 3안은 하루치입니다.

5.1 진행 상태를 파일에 남긴다

날짜별로 성공 여부와 조회·저장 건수를 기록하고, 하루가 끝날 때마다 저장합니다.

json"20260821": { "ok": true, "fetched": 1100, "stored": 370,
              "matched": 392, "at": "2026-08-24T10:09:56" }

저장 시점이 중요합니다. 전체가 끝난 뒤에 한 번 쓰면 중단 시 아무것도 남지 않습니다. 하루 단위로 쓰기 때문에 손실이 최대 하루치로 제한됩니다.

다시 실행하면 기록에 없는 날짜만 이어서 처리합니다.

5.2 호출 예산 안에서 멈춘다

한 번 실행에서 쓸 호출 수를 인자로 받고 그 안에서 종료합니다.

pythonCALLS_PER_DAY = len(BID_LIST_ENDPOINTS) * 6   # 실측 평균 17회
max_days = max(1, budget // CALLS_PER_DAY)

CALLS_PER_DAY 를 추정값이 아니라 실측 평균으로 둔 것이 핵심입니다. 예산을 날짜 수로 환산하려면 하루당 호출량을 알아야 하는데, 이 값이 부정확하면 예산 제어 자체가 무의미해집니다.

예산이 소진되면 남은 날짜와 다시 실행할 명령을 출력하고 종료합니다.

5.3 최근 날짜부터 채운다

과거로 거슬러 올라가는 순서로 처리합니다.

이것은 성능 최적화가 아니라 중단 시점의 가치를 높이는 선택입니다. 어느 시점에 멈추더라도 초안에 실제로 인용되는 최신 공고가 먼저 확보됩니다. 1년 전 공고는 나중에 채워도 초안 품질에 미치는 영향이 적습니다.

5.4 이미 수집된 구간을 흡수한다

DB 에 공고가 있는 날짜를 완료로 표시하는 절차를 실행 앞단에 두었습니다.

판단 기준으로 수집 시각(`fetched_at`)을 사용하고 공고일(`notice_date`)은 쓰지 않았습니다. 하루를 스캔하면 여러 공고일의 데이터가 함께 들어오기 때문에, 공고일을 기준으로 삼으면 실제로 스캔한 날짜와 어긋납니다.

sqlSELECT substr(replace(fetched_at,'-',''),1,8) AS d, COUNT(*)
FROM g2b_bid_notices WHERE fetched_at IS NOT NULL GROUP BY d

이 덕분에 상태 파일이 없는 환경(다른 PC 등)에서도 DB 만 있으면 진행 상황이 복원됩니다.

5.5 하루 실패가 전체를 멈추지 않는다

한 날짜에서 예외가 발생해도 로그만 남기고 다음 날짜로 넘어갑니다. 실패한 날짜는 상태에 기록되어 다음 실행에서 자동으로 재시도됩니다.

전체 중단 방식이었다면 특정 날짜의 일시적 오류 하나로 나머지 300일이 대기 상태가 됩니다.

6. 실험 결과

두 차례 나눠 실행한 결과입니다.

항목
대상 기간365일
완료93일
남음272일 (예상 호출 4,896회)
누적 조회72,134건
누적 저장22,376건
채운 구간2026.05.23 ~ 2026.08.23
실패한 날짜0일

최근 날짜부터 채우도록 설계했으므로 최근 3개월이 먼저 확보됐습니다. 이 시점에 백필을 중단하더라도 초안 생성에는 지장이 없습니다.

조회 72,134건 대비 저장 22,376건으로 약 31%만 적재됩니다. 나머지는 관심 기관과 매칭되지 않는 공고이므로 정상 동작입니다.

중단 후 재개도 확인했습니다. 첫 실행이 예산 소진으로 종료된 뒤 같은 명령을 다시 호출하니, 처리 완료된 날짜를 건너뛰고 이어서 진행했습니다.

7. 결론 및 시사점

외부 API 를 쓰는 배치에서는 재시도가 공짜가 아닙니다. 내부 연산이라면 실패 시 전체 재실행이 합리적인 선택일 수 있습니다. 그러나 호출 한도가 있는 외부 API 에서는 재시도 비용이 한도를 잠식하므로, 손실 단위를 작게 만드는 설계가 우선입니다.

중단 가능성을 전제로 설계하면 순서가 의사결정 항목이 됩니다. 끝까지 돌아갈 것을 전제하면 처리 순서는 아무래도 상관없습니다. 중단될 수 있다고 보는 순간, "어느 것부터 처리해야 중단 시점의 결과가 쓸모 있는가"가 설계 문제로 올라옵니다. 최근 날짜부터 채운 것이 그 결론이었습니다.

진행 상태는 코드 밖에도 존재합니다. 상태 파일만 신뢰했다면 이미 수집된 구간을 다시 조회했을 것입니다. 시스템이 실제로 도달한 지점은 DB 에도 기록되어 있었고, 그것을 읽어 상태를 복원하는 절차가 필요했습니다. 재개 가능한 작업을 설계할 때는 진행 상황을 알려주는 근거가 몇 군데에 있는지 먼저 확인하는 편이 안전합니다.