Skip to content

fix: 기상청 API 호출 제한에도 날씨 캐시 유지#2292

Merged
dh2906 merged 2 commits into
developfrom
fix/2291-weather-api-caching
Jun 30, 2026
Merged

fix: 기상청 API 호출 제한에도 날씨 캐시 유지#2292
dh2906 merged 2 commits into
developfrom
fix/2291-weather-api-caching

Conversation

@dh2906

@dh2906 dh2906 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

🔍 개요

  • 기상청 단기예보 API 요청이 몰려 429가 발생하더라도 기존 예보를 계속 제공하고, 다음 갱신 기회를 확보합니다.
  • 기존에는 매시간 단 한 번의 수집이 실패하면 캐시 만료 후 날씨 조회가 불가능했지만, 이제 향후 24시간 예보를 미리 보관해 일시적인 외부 장애가 사용자 요청으로 전파되지 않습니다.

🚀 주요 변경 내용

  • 갱신 시점부터 향후 24시간 예보를 시간대별 Map으로 구성해 Redis 단일 키에 24시간 동안 저장합니다.
  • /weather 요청은 외부 API를 호출하지 않고 현재 정시의 예보만 캐시에서 선택하며, 공개 응답 형식은 유지합니다.
  • HTTP 429와 기상청 초당 요청 제한 응답만 분리해 5초 간격으로 5회 재시도합니다.
  • 모든 재시도가 실패하면 저장을 수행하지 않아 기존 캐시가 삭제되거나 불완전한 데이터로 교체되지 않습니다.

💬 참고 사항

  • 전역 @EnableRetry를 사용하지 않고 날씨 전용 RetryTemplate을 구성해 기존 Slack, SMS, 메일 재시도 동작에 영향을 주지 않습니다.
  • ./gradlew check --rerun-tasks --no-daemon으로 단위 테스트, Redis 인수 테스트와 전체 회귀 테스트를 검증했습니다.

✅ Checklist (완료 조건)

  • 코드 스타일 가이드 준수
  • 테스트 코드 포함됨
  • Reviewers / Assignees / Labels 지정 완료
  • 보안 및 민감 정보 검증 (API 키, 환경 변수, 개인정보 등)

Compound Engineering
Codex

Summary by CodeRabbit

  • New Features

    • Weather data now supports multiple hourly forecasts within a 24-hour window.
    • Weather results are cached and served by time slot, improving hourly accuracy.
  • Bug Fixes

    • Added automatic retry handling for temporary weather-provider rate limits.
    • Improved fallback behavior when the requested forecast time is unavailable.
    • Better error reporting when weather data cannot be found or refreshed.

dh2906 added 2 commits June 30, 2026 18:39
- 동시 호출 제한을 일반 외부 API 오류와 분리해 재시도 대상이 불필요하게 넓어지는 것을 방지
- 최초 호출 이후 5초 간격으로 5회만 추가 시도하도록 날씨 전용 RetryTemplate을 구성
- 전역 @EnableRetry 없이 날씨 클라이언트에만 정책을 주입해 기존 Slack, SMS, 메일 동작에 영향을 주지 않도록 제한
- 갱신 시점부터 향후 24시간 예보를 yyyyMMddHHmm 기준 Map으로 묶어 날짜 경계를 넘는 시간대도 함께 보관
- Redis 단일 키의 TTL을 24시간으로 늘리고 조회 요청은 현재 정시 예보만 선택해 외부 API와 사용자 요청을 분리
- HTTP 429와 기상청 초당 요청 제한 응답만 전용 예외로 분류해 5초 간격 5회 재시도를 적용
- 모든 갱신 시도가 실패하면 저장을 수행하지 않아 기존 캐시가 삭제되거나 불완전한 데이터로 교체되는 상황을 방지
- 24시간 범위 경계, 재시도 횟수, 현재 시간 조회와 Redis 직렬화를 단위 및 인수 테스트로 검증
@github-actions github-actions Bot added Team Campus 캠퍼스 팀에서 작업할 이슈입니다 버그 정상적으로 동작하지 않는 문제상황입니다. labels Jun 30, 2026
@github-actions github-actions Bot requested review from Soundbar91 and taejinn June 30, 2026 09:58
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces WeatherOpenApiRateLimitException and a RetryTemplate bean to handle HTTP 429 responses from the weather Open API. WeatherClient is refactored to fetch and return a 24-hour map of hourly WeatherForecast slots. WeatherCache stores Map<String, WeatherResponse> keyed by datetime, and WeatherService selects the current hour's entry from that map.

Changes

Weather hourly forecast map and rate-limit retry

Layer / File(s) Summary
Rate-limit exception and base visibility
...exception/WeatherOpenApiException.java, ...exception/WeatherOpenApiRateLimitException.java
DEFAULT_MESSAGE changed to protected; new WeatherOpenApiRateLimitException subclass with withDetail factory method added.
RetryTemplate bean
...config/WeatherRetryConfig.java
New @Configuration class defines weatherRetryTemplate bean: 6 max attempts, 5 s fixed backoff, retries on WeatherOpenApiRateLimitException.
WeatherClient: hourly map retrieval and rate-limit handling
...client/WeatherClient.java
Constructor accepts RetryTemplate; getWeatherForecasts replaces getWeatherForecast, grouping API items into a 24-hour slot map; HTTP 429 and body rate-limit phrases throw WeatherOpenApiRateLimitException.
WeatherCache: single response → hourly map
...model/WeatherCache.java
Replaces single WeatherResponse field with Map<String, WeatherResponse> hourlyWeathers; TTL updated from 2 h to 24 h; of() accepts the hourly map.
WeatherService: hourly map lookup and refresh
...service/WeatherService.java
getWeather() computes forecastDateTime key and looks it up in hourlyWeathers; refreshWeather() calls getWeatherForecasts and saves the full hourly map.
Tests
...WeatherClientTest.java, ...WeatherServiceTest.java, ...WeatherApiTest.java
Client tests add RetryTemplate and verify 429 retry behavior; service tests update mocks/assertions for hourly map; acceptance test seeds cache with Map.of.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • BCSDLab/KOIN_API_V2#2276: Changes WeatherClient's OpenAPI failure handling using previousBaseTime() retry on WeatherOpenApiException, which this PR extends with rate-limit retry via RetryTemplate.

Suggested labels

기능

Suggested reviewers

  • ImTotem
  • Soundbar91
  • BaeJinho4028

🐇 The forecasts come in by the hour now,
No 429 can stop us, no way, no how!
Retry six times with a five-second rest,
A map full of weather — hourly's the best!
The rabbit hops forward through rain, cloud, and sun~ 🌤️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: weather cache retention under KMA API rate limits.
Linked Issues check ✅ Passed The PR adds 429-aware retry handling and preserves cached forecasts, satisfying #2291.
Out of Scope Changes check ✅ Passed All changes relate to weather rate-limit handling, cache expansion, and supporting tests.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/2291-weather-api-caching

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Unit Test Results

675 tests   672 ✔️  1m 21s ⏱️
168 suites      3 💤
168 files        0

Results for commit e050c17.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/in/koreatech/koin/domain/weather/config/WeatherRetryConfig.java (1)

18-21: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Prefer jittered backoff for the 429 retry path.

A fixed 5-second delay makes every instance that gets throttled retry in lockstep, which can recreate the same burst against KMA. Randomized or exponential backoff is a better fit for the rate-limit scenario this PR is addressing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/in/koreatech/koin/domain/weather/config/WeatherRetryConfig.java`
around lines 18 - 21, The retry setup in WeatherRetryConfig uses a fixed backoff
for WeatherOpenApiRateLimitException, which causes synchronized retries under
429 throttling. Update the RetryTemplate builder in the retry configuration to
use jittered or exponential backoff instead of fixedBackoff, keeping the
retryOn(WeatherOpenApiRateLimitException.class) behavior intact and adjusting
the retry interval settings accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/in/koreatech/koin/domain/weather/client/WeatherClient.java`:
- Around line 42-44: The cached forecast window in WeatherClient is ending too
early relative to the Redis TTL, so WeatherService.getWeather() can request an
hour that is still within the cache lifetime but missing from the stored data.
Update the forecast-window logic in WeatherClient to include the final boundary
hour or, alternatively, make the cache expiration align with the exact forecast
boundary instead of a fixed 24h TTL. Check the filtering/range construction
around FORECAST_RANGE_HOURS and the cache save/expiration path so the last
partial hour is always covered.

---

Nitpick comments:
In
`@src/main/java/in/koreatech/koin/domain/weather/config/WeatherRetryConfig.java`:
- Around line 18-21: The retry setup in WeatherRetryConfig uses a fixed backoff
for WeatherOpenApiRateLimitException, which causes synchronized retries under
429 throttling. Update the RetryTemplate builder in the retry configuration to
use jittered or exponential backoff instead of fixedBackoff, keeping the
retryOn(WeatherOpenApiRateLimitException.class) behavior intact and adjusting
the retry interval settings accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f5d623f9-e3d2-4ea7-ba40-f9053c53f405

📥 Commits

Reviewing files that changed from the base of the PR and between ee75f05 and e050c17.

📒 Files selected for processing (9)
  • src/main/java/in/koreatech/koin/domain/weather/client/WeatherClient.java
  • src/main/java/in/koreatech/koin/domain/weather/config/WeatherRetryConfig.java
  • src/main/java/in/koreatech/koin/domain/weather/exception/WeatherOpenApiException.java
  • src/main/java/in/koreatech/koin/domain/weather/exception/WeatherOpenApiRateLimitException.java
  • src/main/java/in/koreatech/koin/domain/weather/model/WeatherCache.java
  • src/main/java/in/koreatech/koin/domain/weather/service/WeatherService.java
  • src/test/java/in/koreatech/koin/acceptance/domain/WeatherApiTest.java
  • src/test/java/in/koreatech/koin/unit/domain/weather/WeatherClientTest.java
  • src/test/java/in/koreatech/koin/unit/domain/weather/WeatherServiceTest.java

Comment on lines +42 to +44
private static final int FORECAST_RANGE_HOURS = 24;
private static final DateTimeFormatter FORECAST_DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMddHHmm");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Cover the TTL’s final partial hour in the cached forecast window.

With [start, start + 24h) filtering and a 24h Redis TTL, a cache saved at e.g. 03:35 contains slots through 02:00 next day, but remains alive until 03:35; WeatherService.getWeather() then throws for 03:00 because that key is missing. Include the boundary hour or expire the cache at the forecast window boundary.

Proposed fix
-    private static final int FORECAST_RANGE_HOURS = 24;
+    private static final int FORECAST_RANGE_HOURS = 25;
-            // 달력 날짜가 아닌 갱신 시각을 기준으로 향후 24시간의 예보만 캐시에 저장한다.
+            // 달력 날짜가 아닌 갱신 시각을 기준으로 캐시 TTL 마지막 시간대까지 예보를 저장한다.

Also applies to: 82-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/in/koreatech/koin/domain/weather/client/WeatherClient.java`
around lines 42 - 44, The cached forecast window in WeatherClient is ending too
early relative to the Redis TTL, so WeatherService.getWeather() can request an
hour that is still within the cache lifetime but missing from the stored data.
Update the forecast-window logic in WeatherClient to include the final boundary
hour or, alternatively, make the cache expiration align with the exact forecast
boundary instead of a fixed 24h TTL. Check the filtering/range construction
around FORECAST_RANGE_HOURS and the cache save/expiration path so the last
partial hour is always covered.

@dh2906 dh2906 merged commit 2b88375 into develop Jun 30, 2026
9 checks passed
@dh2906 dh2906 deleted the fix/2291-weather-api-caching branch June 30, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Team Campus 캠퍼스 팀에서 작업할 이슈입니다 버그 정상적으로 동작하지 않는 문제상황입니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[캠퍼스] 기상청 API 호출 제한 대응

2 participants