기록은 Postgres에, 재생성 가능한 값은 Redis에
근거와 이력은 Postgres에 남기고 재생성 가능한 조회값만 Redis에 둔 기준
주차장 잔여 대수는 한강으로 갈지 말지를 바꾸는 신호가 된다. 화면에 “아직 20대 남았다”고 표시하려면 그 숫자의 근거와 확인 시각도 설명할 수 있어야 한다.
한강자리에서는 숫자의 근거를 남길 곳과 다시 만들 수 있는 조회 결과를 구분해 Postgres와 Redis의 역할을 정했다.
모든 요청을 Postgres에서 처리하면 자주 읽는 첫 화면과 예측 응답의 부하가 한곳에 모인다. Redis에 기준 데이터와 이력까지 맡기면 cache 장애가 데이터 손실로 번진다.
Postgres와 Redis가 맡은 데이터
먼저 요청 경로에서 두 저장소가 어떻게 쓰이는지 보면 차이가 보인다. Postgres는 나중에 설명해야 하는 기록을 남기고, Redis는 다시 만들 수 있는 조회 결과를 빠르게 꺼내는 쪽에 선다.
flowchart LR
Workers["수집 · 예측 · 알림 워커"] --> PG[("PostgreSQL / PostGIS<br/>기준 데이터 · 이력 · 위치 · 감사 기록")]
Workers --> Redis[("Redis<br/>현재 상태 · 응답 캐시 · 보조 인덱스")]
API["FastAPI 읽기 경로"] --> Redis
Redis -->|적중| API
Redis -->|미스 · 형식 오류 · 오래됨| PG
PG --> API
Postgres는 나중에 다시 설명해야 하는 값을 맡는다. 언제 어떤 출처에서 값이 들어왔는지, 어떤 예측 run이 어떤 결과를 만들었는지, 알림을 보냈거나 보내지 않은 이유가 무엇이었는지 같은 기록은 Postgres에 남아야 한다.
Redis는 사용자 요청 시간을 짧게 만드는 데 쓴다. 최신 주차 상태, forecast 응답, home-summary hot/stale 응답, 출처 상태 요약, push 후보 조회용 index처럼 다시 만들
수 있는 값을 둔다.
이렇게 나눠두면 장애를 볼 때도 말이 정확해진다. Redis가 비었다면 화면용 응답을 다시 만들면 된다. Postgres의 기준 데이터나 이력이 손상됐다면 복구 방법이 완전히 달라진다. 두 저장소를 같은 무게로 보면 대응도 늦어진다.
앱 기능별로 보는 데이터 묶음
Schema는 앱 기능이 기대는 데이터 묶음에 따라 나눴다. 주차, 나들이 출처, 예측, push, 교통·번역은 같은 DB 안에서도 서로 다른 속도와 실패 방식을 가진다.
erDiagram
PARK ||--o{ PARKING_LOT : 보유
PARKING_LOT ||--o{ PARKING_STATUS : 기록
PARKING_STATUS ||--o{ FORECAST_INPUT : 전환
FORECAST_RUN ||--o{ FORECAST_RESULT : 생성
DATA_SOURCE ||--o{ INGESTION_RUN : 보고
DATA_SOURCE ||--o{ OUTING_SIGNAL : 발행
PUSH_SUBSCRIPTION ||--o{ DELIVERY_DECISION : 평가
DELIVERY_DECISION ||--o{ DELIVERY_ATTEMPT : 감사
TRANSIT_DATASET ||--o{ TRANSIT_ROUTE : 포함
TRANSLATION_SOURCE ||--o{ TRANSLATION_CACHE : 렌더링
이 ERD에는 운영자가 먼저 확인할 기능별 기준 데이터와 이력만 담았다. 같은 DB 안에서도 소유 기능을 알아야 장애 범위를 좁힐 수 있다.
실제 schema에는 보조 테이블과 index가 더 많다. 이 그림은 사용자가 보는 기능과 운영자가 확인하는 기록이 어떻게 맞물리는지만 남긴다.
Postgres에 남기는 알림 기록
push 영역에서는 발송 결정, 억제 이유, 전송 시도와 외부 전송자의 응답을 각각 남긴다.
이 기록을 하나의 로그 문자열로만 남기면 나중에 알림을 얼마나 조용하게 보낼지 조절할 수 없다. 조용한 시간이라 억제한 알림과, 전송 실패로 도착하지 않은 알림은 사용자에게는 둘 다 “알림이 안 왔다”처럼 보일 수 있다. 하지만 운영자가 해야 할 일은 다르다.
그래서 알림을 보낼지 말지와 전송 감사 기록은 Postgres에 둔다. Redis는 후보를 빠르게 찾는 데만 사용한다. Postgres 기록에는 알림을 보내지 않은 이유도 남는다.
값과 함께 남기는 확인 시각
공공데이터 앱에서는 값만 저장하면 부족하다.
예를 들어 “여의도 주차 가능 20대”라는 값이 있어도 다음 질문이 남는다.
- 이 값은 언제 확인됐나?
- 서버는 언제 가져왔나?
- 어느 source에서 왔나?
- source가 지금 정상인가?
- 현재값인가, 예측값인가?
그래서 status, forecast, outing, home summary에는 시각과 갱신 상태가 같이 들어간다. 화면은 이 정보를 바탕으로 “최신”, “지연”, “정보 없음”을 구분해 표시한다.
이 구분은 화면 안내까지 바꾼다. 오래된 값을 최신처럼 보여주면 cache는 성공했을지 몰라도 앱은 사용자를 속이게 된다. 반대로 오래된 값이라고 분명히 말하면 사용자는 참고 신호로 받아들일 수 있다.
다시 만들 수 있는 캐시
Redis cache에는 다음 복구 조건을 적용했다.
- Redis 값은 다시 만들 수 있어야 한다.
- Redis miss는 장애가 아니라 정상적인 길이어야 한다.
- malformed cache entry는 무시하고 Postgres 기록으로 fallback한다.
- forecast run이 새로 만들어지면 관련 forecast/home cache를 비운다.
home-summary는 hot cache와 stale backup cache를 나눠 장애 시 마지막으로 믿을 수 있는 값을 보여준다.- push 후보 조회는 Redis-first가 가능하지만 최종 발송과 audit은 Postgres를 따른다.
Redis는 latency를 줄여 주지만 값의 시각과 출처까지 보장하지 않는다. API는 cache에서 읽은 응답에도 최신성과 출처 상태를 함께 표시한다.
화면에 남긴 판단
Postgres와 Redis를 나눈 뒤 앱은 값과 함께 최신값, 마지막 성공 응답 또는 정보 없음 상태를 받게 됐다.
화면은 “정보 없음”과 “한가함”을 구분하고 timestamp로 값의 확인 시각을 알린다. 오래된 값을 표시할 수 있는 위젯에서는 이 상태가 특히 중요했다.
Postgres에는 숫자의 근거와 이력을 남기고 Redis에는 다시 만들 수 있는 조회값을 두었다. API는 이 기록과 조회값으로 최신값, 마지막 성공값과 정보 없음을 구분한다.
Comments
아직 댓글이 없습니다. 첫 댓글을 남겨주세요.
검토 대기 중