Skip to content

feat: Redis 멱등성 키, 지연 큐 인프라 구축 - #68

Merged
neibler merged 2 commits into
developfrom
feature/49
Jul 11, 2026
Merged

feat: Redis 멱등성 키, 지연 큐 인프라 구축#68
neibler merged 2 commits into
developfrom
feature/49

Conversation

@neibler

@neibler neibler commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • 새 기능

    • 지연된 알림 작업을 예약하고 예정된 시간에 자동 처리할 수 있습니다.
    • Redis 연결 상태를 애플리케이션 시작 시 확인합니다.
    • 동일한 작업의 중복 처리를 방지하고, 만료 후 재처리할 수 있습니다.
    • 지연 작업 폴링 활성화 여부와 실행 주기를 설정할 수 있습니다.
  • 개선 사항

    • 여러 작업을 동시에 처리할 때 중복 실행 가능성을 줄였습니다.
    • 개별 작업 처리 오류가 발생해도 다른 작업은 계속 처리됩니다.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@neibler, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aaa67676-2db1-4281-bbaf-8634d9393565

📥 Commits

Reviewing files that changed from the base of the PR and between 2da8e51 and 1bf9f3d.

📒 Files selected for processing (2)
  • src/main/java/com/piuda/callcare/global/config/redis/RedisConfig.java
  • src/test/java/com/piuda/callcare/global/config/redis/AbstractRedisIntegrationTest.java
📝 Walkthrough

Walkthrough

Redis 연결 설정과 멱등성 키 저장소를 추가하고, 원자적 Redis 지연 큐와 주기적 폴러를 구성했다. Testcontainers 기반 통합 테스트와 integration 태그별 테스트 실행 설정도 추가했다.

Changes

Redis 인프라 및 지연 처리

Layer / File(s) Summary
Redis 연결 및 멱등성 저장소
src/main/java/com/piuda/callcare/global/config/redis/RedisConfig.java, src/main/java/com/piuda/callcare/global/config/redis/IdempotencyKeyStore.java
Redis 커넥션·템플릿과 기동 시 연결 확인을 구성하고, TTL 기반 키 획득·조회·해제를 제공한다.
지연 큐 및 처리 계약
src/main/java/com/piuda/callcare/global/config/redis/DelayedQueue.java, src/main/java/com/piuda/callcare/global/config/redis/DelayedJobHandler.java
Redis Sorted Set에 실행 시각을 저장하고 Lua 스크립트로 due payload를 원자적으로 조회·삭제하는 큐와 처리 인터페이스를 추가했다.
지연 큐 폴러 실행 흐름
src/main/java/com/piuda/callcare/global/config/redis/DelayedQueuePoller.java, src/main/java/com/piuda/callcare/global/config/redis/LoggingDelayedJobHandler.java, src/main/resources/application.yml
설정된 주기로 notification 큐를 폴링하고 payload를 핸들러에 전달하며, 기본 로그 핸들러와 폴러 설정을 추가했다.
Redis 통합 테스트 및 실행 설정
build.gradle, src/test/java/com/piuda/callcare/global/config/redis/*
Testcontainers Redis 테스트 기반과 큐·멱등성 동작 테스트를 추가하고, 시스템 프로퍼티에 따른 테스트 태그 필터링을 구성했다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DelayedQueuePoller
  participant DelayedQueue
  participant Redis
  participant DelayedJobHandler
  DelayedQueuePoller->>DelayedQueue: notification 큐의 due payload 조회
  DelayedQueue->>Redis: due payload 원자적 조회 및 삭제
  Redis-->>DelayedQueue: payload 목록 반환
  DelayedQueue-->>DelayedQueuePoller: due payload 전달
  DelayedQueuePoller->>DelayedJobHandler: payload별 handle 호출
Loading

Suggested labels: ✨ Feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.91% 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 Redis 멱등성 키와 지연 큐 인프라 추가라는 핵심 변경을 정확히 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/49

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.

@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 (4)
src/main/java/com/piuda/callcare/global/config/redis/RedisConfig.java (1)

49-58: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

헬스 체크 ping()이 기동을 블로킹할 수 있다.

수동으로 생성한 LettuceConnectionFactory에는 커맨드 타임아웃이 설정되어 있지 않다. Redis가 접근 불가능한 경우 connection.ping()이 무기한 또는 기본 타임아웃(최대 60s)까지 블로킹되어 애플리케이션 시작이 지연된다. ApplicationRunner는 컨텍스트 초기화 후 실행되므로 준비 상태(readiness)가 늦어진다.

위 제안대로 자동 구성으로 전환하고 spring.data.redis.timeout을 짧게(예: 2s) 설정하거나, 수동 구성을 유지한다면 LettuceClientConfiguration.builder().commandTimeout(Duration.ofSeconds(2)).build()를 적용한다.

🤖 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/com/piuda/callcare/global/config/redis/RedisConfig.java` around
lines 49 - 58, Update the Redis connection configuration used by
LettuceConnectionFactory to apply a short command timeout, such as 2 seconds,
via LettuceClientConfiguration.builder().commandTimeout(...).build(), or switch
to the existing Spring Boot Redis auto-configuration with
spring.data.redis.timeout set accordingly. Ensure redisConnectionHealthCheck’s
connection.ping() cannot block application startup indefinitely.
src/main/java/com/piuda/callcare/global/config/redis/DelayedQueue.java (1)

36-40: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Lua unpack() 스택 한계(~8000)에 유의.

unpack(due)는 Lua 5.1 스택 크기(약 8000)까지만 안전한다. 기본 DEFAULT_POLL_LIMIT=100이면 문제없지만, pollDue(queueKey, limit)의 공개 API에서 큰 limit을 전달하면 스크립트 에러가 발생할 수 있다.

limit 상한을 캡하거나, 큰 limit이 필요하면 루프로 ZREM을 수행하는 방안을 고려한다.

🤖 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/com/piuda/callcare/global/config/redis/DelayedQueue.java`
around lines 36 - 40, Update the RedisScript used by pollDue to avoid calling
unpack(due) with an unbounded public limit; either cap the accepted poll limit
to the Lua-safe range or replace the bulk ZREM with a loop that removes each due
item safely. Preserve polling and removal behavior for the default limit and
large requested limits.
src/test/java/com/piuda/callcare/global/config/redis/DelayedQueueIT.java (1)

22-39: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Thread.sleep 기반 시간 대기 테스트가 CI 환경에서 플래키할 수 있습니다.

두 테스트 모두 500ms 지연/TTL에 대해 Thread.sleep(700)으로 200ms 마진만 두고 있습니다. CI 환경 부하 시 마진이 부족할 수 있습니다.

  • src/test/java/com/piuda/callcare/global/config/redis/DelayedQueueIT.java#L22-L39: Thread.sleep(700) → Awaitility 기반 폴링 대기 또는 마진 증가(예: 1500ms) 권장
  • src/test/java/com/piuda/callcare/global/config/redis/IdempotencyKeyStoreIT.java#L32-L45: 동일하게 Thread.sleep(700) → Awaitility 또는 마진 증가 권장

Awaitility를 사용하면 정확한 타이밍 없이 조건 충족 시점까지 대기하므로 플래키니스를 줄일 수 있습니다:

await().atMost(2, SECONDS).untilAsserted(() ->
    assertThat(delayedQueue.pollDue(QUEUE)).containsExactly(payload));
🤖 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/test/java/com/piuda/callcare/global/config/redis/DelayedQueueIT.java`
around lines 22 - 39, Replace fixed Thread.sleep(700) waits with
Awaitility-based condition polling in DelayedQueueIT.java lines 22-39 and
IdempotencyKeyStoreIT.java lines 32-45. For
DelayedQueueIT.pollDue_returnsPayload_onlyAfterDelay, wait up to a suitable
timeout until pollDue returns the expected payload; apply the same
condition-based waiting to the corresponding idempotency test, preserving the
existing pre-delay assertions.
src/main/java/com/piuda/callcare/global/config/redis/DelayedQueuePoller.java (1)

32-42: 🩺 Stability & Availability | 🔵 Trivial

순차 처리로 인해 폴링 주기가 밀릴 수 있습니다.
handle()가 무거워지면 다음 poll() 실행이 지연될 수 있으니, 처리량이 늘어날 가능성이 있으면 비동기 처리나 전용 스레드풀 분리를 고려하세요.

🤖 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/com/piuda/callcare/global/config/redis/DelayedQueuePoller.java`
around lines 32 - 42, Update the poll method in DelayedQueuePoller so each due
payload is processed asynchronously or delegated to a dedicated executor instead
of blocking the scheduled polling thread on delayedJobHandler.handle. Preserve
the existing per-payload exception logging and configure or reuse an appropriate
bounded thread pool for processing.

Source: Path instructions

🤖 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/com/piuda/callcare/global/config/redis/RedisConfig.java`:
- Around line 35-43: Remove the manual redisConnectionFactory and
stringRedisTemplate bean methods from RedisConfig so Spring Boot
RedisAutoConfiguration creates them and applies all spring.data.redis.*
properties. Preserve the existing `@Value` fields and redisConnectionHealthCheck
logic, updating imports or injection only as needed after removing these bean
definitions.

---

Nitpick comments:
In `@src/main/java/com/piuda/callcare/global/config/redis/DelayedQueue.java`:
- Around line 36-40: Update the RedisScript used by pollDue to avoid calling
unpack(due) with an unbounded public limit; either cap the accepted poll limit
to the Lua-safe range or replace the bulk ZREM with a loop that removes each due
item safely. Preserve polling and removal behavior for the default limit and
large requested limits.

In
`@src/main/java/com/piuda/callcare/global/config/redis/DelayedQueuePoller.java`:
- Around line 32-42: Update the poll method in DelayedQueuePoller so each due
payload is processed asynchronously or delegated to a dedicated executor instead
of blocking the scheduled polling thread on delayedJobHandler.handle. Preserve
the existing per-payload exception logging and configure or reuse an appropriate
bounded thread pool for processing.

In `@src/main/java/com/piuda/callcare/global/config/redis/RedisConfig.java`:
- Around line 49-58: Update the Redis connection configuration used by
LettuceConnectionFactory to apply a short command timeout, such as 2 seconds,
via LettuceClientConfiguration.builder().commandTimeout(...).build(), or switch
to the existing Spring Boot Redis auto-configuration with
spring.data.redis.timeout set accordingly. Ensure redisConnectionHealthCheck’s
connection.ping() cannot block application startup indefinitely.

In `@src/test/java/com/piuda/callcare/global/config/redis/DelayedQueueIT.java`:
- Around line 22-39: Replace fixed Thread.sleep(700) waits with Awaitility-based
condition polling in DelayedQueueIT.java lines 22-39 and
IdempotencyKeyStoreIT.java lines 32-45. For
DelayedQueueIT.pollDue_returnsPayload_onlyAfterDelay, wait up to a suitable
timeout until pollDue returns the expected payload; apply the same
condition-based waiting to the corresponding idempotency test, preserving the
existing pre-delay assertions.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b6d39e75-16d4-4a6a-a88b-36f0477251e7

📥 Commits

Reviewing files that changed from the base of the PR and between 328b8d5 and 2da8e51.

📒 Files selected for processing (11)
  • build.gradle
  • src/main/java/com/piuda/callcare/global/config/redis/DelayedJobHandler.java
  • src/main/java/com/piuda/callcare/global/config/redis/DelayedQueue.java
  • src/main/java/com/piuda/callcare/global/config/redis/DelayedQueuePoller.java
  • src/main/java/com/piuda/callcare/global/config/redis/IdempotencyKeyStore.java
  • src/main/java/com/piuda/callcare/global/config/redis/LoggingDelayedJobHandler.java
  • src/main/java/com/piuda/callcare/global/config/redis/RedisConfig.java
  • src/main/resources/application.yml
  • src/test/java/com/piuda/callcare/global/config/redis/AbstractRedisIntegrationTest.java
  • src/test/java/com/piuda/callcare/global/config/redis/DelayedQueueIT.java
  • src/test/java/com/piuda/callcare/global/config/redis/IdempotencyKeyStoreIT.java

Comment thread src/main/java/com/piuda/callcare/global/config/redis/RedisConfig.java Outdated
@neibler
neibler merged commit f94d699 into develop Jul 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant