오래된 데이터가 보일 때 확인할 순서

오래된 데이터가 보일 때 확인할 순서. 다루는 범위: 사용자 요청의 API 도달 확인, 구성 목록보다 필요한 확인 순서, 원인 후보를 줄이는 대시보드. 실제 구현과 운영 기록을 기준으로 설명한다.

한강자리의 백엔드 실행 환경은 집에 둔 작은 서버 위의 K3s cluster다. 사용자가 앱을 열었는데 주차 정보가 오래됐다고 해보자. 이때 바로 코드를 열면 늦다. 먼저 요청이 서버까지 닿았는지, API는 살아 있는지, worker가 새 값을 가져왔는지 나눠 봐야 한다.

사용자용 API는 Cloudflare edge와 Tunnel을 거쳐 K3s ingress로 들어온다. 원본 서버를 직접 공개하는 방식은 피했고, 사용자가 들어오는 길과 운영자가 손보는 길을 섞지 않았다.

미니PC라도 App Store에 올라간 앱이 호출하는 API가 그 위에서 돈다면 실제 서버다. 사용자는 서버가 집에 있는지 cloud VM에 있는지 알 필요가 없다. 느리거나 오래된 데이터를 보면 앱을 믿기 어려워진다.

사용자 요청의 API 도달 확인

첫 확인은 사용자 요청이 어떤 경로를 지나 API까지 도착하는지다. 이 경로가 막혔는지, API 안에서 특정 기능만 실패하는지, 데이터가 오래된 것인지는 서로 다른 문제다.

flowchart LR
  Public["iOS 앱 / 위젯<br/>Cloudflare edge"] --> Private["비공개 진입 경로<br/>Cloudflare Tunnel · K3s ingress"]
  Private --> API["FastAPI 서비스"]
  API --> Redis["Redis"]
  API --> PG["Postgres"]

브랜드 사이트와 지원/개인정보 페이지는 API와 분리했다. 정적 웹과 앱 API는 장애 성격이 다르다. 같은 앱의 이름을 달고 있어도 실행 환경을 나누는 편이 문제를 찾기 쉽다.

flowchart TB
  Site["hangangjari.app<br/>브랜드 · 지원 · 개인정보"] --> Pages["정적 사이트"]
  APIName["API 도메인"] --> Edge["Cloudflare edge"]
  Edge --> Tunnel["Cloudflare Tunnel"]
  Tunnel --> Runtime["K3s cluster"]

터널은 origin을 직접 공개하지 않고 엣지 네트워크를 통해 서비스로 라우팅한다. 그래서 사용자가 들어오는 길과 서버를 손보러 들어가는 길을 분리할 수 있다.

이 구분은 장애를 볼 때도 도움이 된다. 사용자가 들어오는 길이 막힌 것인지, 운영자가 쓰는 제어면이 문제인지가 섞이면 복구 순서가 흐려진다.

구성 목록보다 필요한 확인 순서

K3s 안에는 API, worker, 상태를 가진 저장소, ingress, metrics/logs 쪽 구성 요소가 있다. 이름을 모두 외우는 것보다 오래된 값이 보였을 때 어느 부분을 먼저 확인할지 정해두는 편이 낫다.

구성역할
FastAPI APIiOS 앱과 위젯이 읽는 앱 API
주차 worker주차 상태 수집
나들이 worker행사, 공지, 시설, 실시간 상황 수집
예측 worker예측 만들기와 home summary warmup
알림 worker후보 만들기, 보내기 규칙 확인, outbox 발송
Postgres/PostGIS기준 데이터, 이력, 감사 기록
Rediscache, 최신 상태, 보조 index
K3s ingress사용자 API routing
Prometheus/Grafana/logsmetrics, logs, dashboard
flowchart TB
  Desired["서버 상태 저장소<br/>원하는 상태"] --> GitOps["GitOps 동기화"]
  GitOps --> API["API 배포"]
  GitOps --> Workers["워커 배포"]
  GitOps --> State["Postgres / Redis"]
  GitOps --> Ingress["인그레스"]
  API --> Signals["메트릭 · 로그"]
  Workers --> Signals
  State --> Signals

미니PC라고 해서 확인할 것이 줄어들지는 않는다. 사용자가 쓰는 API라면 올리는 방법, 백업, metrics/logs, 복구 방법이 있어야 실제 서버로 볼 수 있다.

원인 후보를 줄이는 대시보드

대시보드는 꾸미는 화면이 아니다. 사용자 제보가 왔을 때 어디를 볼지 줄여주는 화면이어야 한다.

flowchart TD
  Checks["확인할 항목"] --> APIQ["API<br/>상태 · 지연 · 오류율"]
  Checks --> WorkerQ["워커<br/>마지막 성공 · 행 수 · 최신성"]
  Checks --> DataQ["데이터<br/>캐시 적중/미스 · 예측 실행 · outbox 적체"]
  Checks --> InfraQ["K3s<br/>ingress · GitOps 상태 · 백업 상태"]
  APIQ --> Triage["어디가 흔들렸는지 보기"]
  WorkerQ --> Triage
  DataQ --> Triage
  InfraQ --> Triage

한강자리에서 먼저 보고 싶은 신호는 다음과 같다.

  • API가 살아 있는가.
  • protected route가 정상적으로 거절·허용되는가.
  • 특정 endpoint latency가 튀고 있는가.
  • worker가 최신 데이터를 계속 수집하는가.
  • 출처별 마지막 성공 시각이 오래됐는가.
  • Redis cache가 비정상적으로 비어 있는가.
  • forecast가 최신 run을 만들고 있는가.
  • push outbox가 쌓이고 있지 않은가.
  • DB backup과 restore drill이 정상인가.

metrics 수집에서는 애플리케이션이 내부 신호를 노출하고, 수집기가 주기적으로 가져가는 방식이 기본이다. 한강자리도 API와 worker가 위 신호를 남기도록 맞췄다.

제보 뒤 코드보다 먼저 볼 상태

문제가 생기면 바로 코드를 의심하지 않는다. 어디서 실패했는지 먼저 본다.

flowchart TD
  Alert["사용자 제보 또는 스모크 실패"] --> Public{"사용자 API 상태 정상?"}
  Public -->|아니오| Runtime{"ingress와 K3s가 정상인가?"}
  Runtime -->|예| Edge["엣지와 터널 확인"]
  Runtime -->|아니오| Pods["pod와 GitOps 상태 확인"]
  Public -->|예| Feature{"특정 API만 실패하는가?"}
  Feature -->|예| Fresh{"최신성이 떨어졌는가?"}
  Fresh -->|예| Worker["워커와 수집 실행 확인"]
  Fresh -->|아니오| Cache["캐시/화면용 응답과 DB 조회 확인"]
  Feature -->|아니오| Client["클라이언트 캐시 또는 위젯 스냅샷 확인"]

이 순서가 있으면 API가 죽은 문제인지, 출처가 실패한 문제인지, 캐시가 오래된 문제인지, 클라이언트가 오래된 snapshot을 보고 있는지 나눠 볼 수 있다. 작은 서버에서 돈다고 해서 사용자가 만나는 실패까지 작아지지는 않았다.

복구 우선순위에 따른 데이터 분류

백업은 “파일이 있다”로 끝나지 않는다. 복구해봐야 백업이다.

한강자리에서는 DB 백업 상태와 복구 가능성도 확인 대상에 포함한다. 공공데이터는 다시 수집할 수 있는 것도 많다. 하지만 사용자 설정, push subscription, 앱 event, forecast/backtest 이력은 사라지면 복구가 어렵다.

그래서 다시 만들 수 있는 데이터와 반드시 남겨야 하는 데이터를 구분해야 한다. Redis cache가 비는 것과 Postgres의 기준 데이터가 손상되는 것은 같은 장애가 아니다. 복구 연습은 이 차이를 실제로 확인하는 과정이다.

서버 크기보다 중요한 실패의 모양

사용자가 오래된 데이터를 봤을 때는 요청 경로, API, worker, cache와 클라이언트 snapshot 순으로 상태를 나눠 확인한다.

한강자리에서 서버 쪽을 정리하며 가장 크게 바뀐 생각은 서버 크기와 해야 할 일이 비례하지 않는다는 점이었다. 사용자가 붙는 순간, 작은 서버도 느림과 실패와 복구를 설명할 수 있어야 했다.

Comments

댓글

    이미지 확대