한 줄 정의

사용자 요청 경로와 외부 API 호출 경로를 완벽히 분리하고, 공공데이터(KASI API)를 영속적으로 캐싱하여 시스템 안정성과 일관된 공휴일 조회를 보장하는 데이터 연동 아키텍처.

핵심 요지

외부 기관의 API 응답 지연이나 오류가 사용자 화면(월간 미니맵, 주간 화면, 엑셀 출력 등)에 영향을 주지 않도록, 백그라운드 스케줄러가 데이터를 미리 일괄 동기화(Backfill/Refresh)하여 단일 진실 원천(Single Source of Truth)으로 삼는다.

상세

안정적인 데이터 연동과 유지보수를 위해 다음 원칙들을 준수하여 아키텍처가 구성된다.

  1. 외부 의존성 격리와 캐싱

    • 사용자 렌더링 스레드는 절대 외부 API를 실시간 호출하지 않는다.
    • KoreanPublicHolidayCalendar라는 단일 어댑터를 통해 DB에 저장된 캐시 데이터만 기간 단위로 1회 일괄 조회한다(N+1 쿼리 방지).
  2. 안전한 동기화와 트랜잭션

    • 백그라운드 Job(매일 새벽 03:00)이 YEAR_LOOKBACK(-1)부터 YEAR_LOOKAHEAD(+4)까지의 연도 데이터를 미리 수집한다.
    • API 응답이 완전무결한지 사전 검증한 후 단일 트랜잭션으로 원자적(All or Nothing) 데이터 교체를 수행한다.
    • 복수 작업의 경합이나 덮어쓰기 방지를 위해 Lease 메커니즘과 Fencing Token을 사용한다.
    • 만약 동기화가 실패하더라도 기존의 멀쩡한 캐시 데이터는 그대로 보존되며, 연도별 실패 메타데이터(Sync state)만 기록한다.
  3. 장애 허용과 예외 처리

    • 통신 오류 같은 일시적 문제(429, 5xx 등)는 자동 백오프 재시도한다.
    • 단, 외부 기관(KASI)이 아직 미래 연도(예: 2029년)의 공휴일을 발표하지 않아 응답이 0건으로 올 경우, 이를 오류로 치부하지 않고 ‘정상’으로 유연하게 허용하는 예외 로직(allow_empty: true)을 적용한다.
  4. 보안

    • API 서비스 키는 형상관리에 포함되지 않는 .kamal/secrets.local로 관리한다.
    • 오류 발생 시 API 키가 시스템 로그나 에러 메시지에 노출되지 않도록 철저히 정제(Redaction)한다.

예시

월간 캘린더 화면을 렌더링할 때, 매일짜마다 holiday?(date)를 호출하는 대신, holidays_by_date(start_date, end_date)를 컨트롤러에서 한 번만 호출하여 가져온 해시 맵({Date => Holiday})을 뷰에 전달하여 O(1) 매칭으로 화면을 그린다.

충돌

과거/현재 연도의 데이터가 비어 있을 때는 명백한 이상(실패)으로 처리해야 하지만, 미래 연도의 데이터가 비어 있을 때는 정상(성공)으로 처리해야 하는 정책 간 충돌. 이는 allow_empty 플래그를 타겟 연도에 따라 동적으로 부여하여 해결했다.

관련 노트