source catalog와 ingestion run
수집 계획과 실행 결과를 나눠 출처별 최신성과 실패를 설명했다
공공데이터 앱에서 수집 작업은 “API를 호출한다”로 끝나지 않는다. 어떤 출처가 언제 돌아야 하는지, 실패했을 때 무엇을 보여줄지, row 수가 갑자기 바뀌었는지, 마지막 성공이 언제였는지를 알아야 한다.
한강자리에서는 수집 계획표와 수집 기록을 나눠 이 문제를 다룬다.
둘은 비슷해 보이지만 역할이 다르다. 수집 계획표(source catalog)는 “무엇을 언제 돌릴 것인가”에 답하고, 수집 기록(ingestion run)은 “방금 돌린 일이 어떻게 끝났는가”에 답한다. 이 둘이 같이 있어야
어느 출처가 언제 흔들렸는지 설명할 수 있다.
무엇을 언제 수집할지 한곳에 적었다
수집 계획표에는 수집 작업의 목록이 들어 있다. 코드 곳곳에 주기와 역할을 흩어두지 않고, 출처 ID, job ID, owner, 역할, interval, stale로 볼 시간을 한곳에서 읽을 수 있게 한다.
flowchart LR Catalog["수집 계획표"] --> Scheduler["워커 스케줄러"] Catalog --> Cron["Kubernetes CronJob"] Scheduler --> Jobs["주차 / 나들이 / 예측 작업"] Cron --> Batch["기준 데이터와 백테스트 작업"]
이 목록에는 다음 성격의 출처가 함께 들어간다.
| 범주 | 예 |
|---|---|
| parking master | 주차장 기준 데이터 동기화 |
| parking status | 실시간 주차 값 polling |
| outing facility | 시설 데이터 수집 |
| outing event | 행사 정보 수집 |
| outing notice | 공지·통제 정보 수집 |
| realtime context | 혼잡도, 날씨, 교통 문맥 수집 |
| forecast | 예측 만들기와 backtest |
어디서 실행되느냐보다 화면에 어떤 영향을 주느냐를 먼저 봤다. 어떤 출처가 화면에 보이는 값의 근거인지, 어떤 작업이 최신성에 영향을 주는지, 어떤 작업은 재시도해도 사용자 화면에 직접 보이지 않는지 구분해야 한다.
push는 여기서 일부러 빠져 있다. push worker도 백그라운드 작업이지만, 이 목록이 다루는 것은 “외부 출처를 읽고 화면에 보이는 데이터의 최신성을 바꾸는 작업”이다. 알림 후보 만들기와 outbox dispatch는 출처 수집이 아니라 발송 쪽 일에 가깝다. 이 둘을 같은 목록으로 묶으면 한 화면에서는 편해 보일 수 있지만, 장애 원인을 볼 때는 오히려 헷갈린다.
수집이 어떻게 끝났는지도 남겼다
ingestion run은 수집 작업이 어떻게 끝났는지를 남긴 기록이다.
flowchart LR Fetch["출처 가져오기"] --> Parse["파싱"] Parse --> Normalize["정규화"] Normalize --> Upsert["도메인 행 upsert"] Upsert --> Run["수집 실행 기록"] Run --> Health["출처 상태"] Health --> API["API 최신성/출처 상태"]
한강자리의 outing 수집은 성공하면 다음 값을 남긴다.
- 출처 ID.
- 시작 시각과 종료 시각.
- 성공 여부.
- 읽은 row 수.
- status distribution.
- schema hash.
- 실패 error 요약.
이 값들은 서버를 보는 사람에게도, 앱 화면에도 필요하다. parser가 실패했는지 볼 수 있고, API는 출처 확인 결과를 만들어 화면에 “fresh”, “stale”, “unavailable” 같은 구분을 줄 수 있다.
이런 값은 나중에 붙이기 어렵다. 처음부터 run 기록이 없으면 장애가 난 뒤에 “지난주에도 이 출처가 흔들렸나?”를 확인할 방법이 없다. 그래서 수집 결과 row만 저장하는 것이 아니라, 수집이라는 행위 자체를 기록으로 남겼다.
”데이터 없음”과 “수집 실패”는 다르다
공공데이터 화면에서 가장 위험한 단순화는 모든 실패를 “없음”으로 보이는 것이다.
행사가 0건인 것과 행사 출처가 실패한 것은 다르다. 시설이 없는 것과 facility parser가 빈 row를 만든 것도 다르다. 실시간 문맥이 오래된 것과 출처가 disabled된 것도 다르다.
수집 계획표와 수집 기록을 두면 이 차이를 유지할 수 있다.
| 구분 | 의미 | 사용자에게 주는 신호 |
|---|---|---|
| fresh | 최근 성공 데이터가 있음 | 참고할 수 있음 |
| partial | 일부 출처만 사용 가능 | 제한적 참고 |
| stale | 마지막 성공이 오래됨 | 현장 차이 가능성 표시 |
| unavailable | 수집 실패 또는 출처 불가 | 없음과 구분 |
row 수와 모양 변화도 같이 본다
공공데이터 출처는 조용히 바뀔 수 있다. 필드명이 바뀌거나, HTML 모양이 바뀌거나, 특정 공원 row가 사라질 수 있다.
그래서 수집 결과에는 schema hash와 row count가 필요하다. 이 값이 있어야 “오늘은 행사가 적다”와 “parser가 절반만 읽었다”를 구분할 수 있다.
status distribution도 같은 신호를 준다. 시설 값이 갑자기 전부 unknown이 되거나, 혼잡도 분포가 비정상적으로 치우치면 출처나 parser의 문제일 수 있다.
화면 안내는 수집 결과를 따라간다
출처 확인 결과는 서버를 보는 사람만 보는 값이 아니다. 앱과 위젯이 “얼마나 믿을 수 있는가”를 표현하는 재료다.
한강자리 API는 출처별 마지막 성공과 stale로 볼 시간을 바탕으로 fresh, stale, unavailable을 나눈다. 이 정보는 화면에서 출처와 갱신 여부를 설명하는 데 쓰이고, 위젯에서는 warning text와 unavailable 이유가 된다.
출처 확인 결과는 UI로 이어져야 한다. 대시보드에서 보는 stale과 사용자가 앱에서 보는 “최신 정보가 아닐 수 있음”은 같은 사실의 두 표현이다. 이 연결이 끊기면 서버 쪽은 문제를 알고 있는데 앱은 모르는 척하게 된다.
erDiagram
DATA_SOURCES ||--o{ INGESTION_RUNS : 기록
DATA_SOURCES ||--o{ OUTING_SIGNALS : 발행
DATA_SOURCES ||--o{ OUTING_FACILITIES : 공급
INGESTION_RUNS }o--|| SOURCE_HEALTH : 파생
수집 과정도 화면의 일부였다
수집 계획표는 무엇을 언제 수집할지 적은 목록이고, ingestion run은 수집이 어떻게 끝났는지 남긴 기록이다. row count, status distribution, schema hash는 parser가 제대로 읽었는지 보는 최소 신호가 됐다.
“0건”과 “수집 실패”를 API에서 분리하지 않으면 사용자는 같은 빈 화면만 본다. 출처 확인 결과가 화면의 최신성 표현과 alert를 같이 먹여 살리기 때문에, 공공데이터 앱에서는 원자료만큼 수집 과정의 기록도 오래 남겨야 했다.
수집 계획표와 ingestion run은 서버를 보는 사람만 쓰는 표가 아니었다. 사용자가 보는 “최신 정보가 아닐 수 있음”이라는 안내도 결국 같은 기록에서 나온다. 수집 과정도 화면의 일부가 된 셈이다.