Skip to content

[FEAT#523] Validate stage Redis ZSET intermediate queue — priority sub-ordering - #527

Merged
juhy0987 merged 6 commits into
mainfrom
feature/#523/validate-priority-zset-queue
May 20, 2026
Merged

juhy0987 merged 6 commits into
mainfrom
feature/#523/validate-priority-zset-queue

Conversation

@juhy0987

@juhy0987 juhy0987 commented May 20, 2026

Copy link
Copy Markdown
Member

연관 이슈


구현 내용

메타 이슈 #515Phase 2 Validate stage — Parser #522 와 동일 패턴 적용. Kafka partition FIFO 가 priority sub-ordering 을 제공 못 하는 한계를 Redis ZSET 으로 해소.

흐름:

Kafka.TopicNormalized → [intake goroutine] → Redis ZSET → [worker pool] → process
                                                                  ↓ 실패
                                                           RetryScheduler.Enqueue → Kafka 재발행

Sub 1 — Validate Worker RetryScheduler hook (6c848ca)

  • Worker.retryScheduler bus.RetryScheduler 필드 + SetRetryScheduler setter
  • Handle 분기: process 실패 시
    • retryScheduler 주입 시 → enqueueRetry 후 commit (메시지 손실 방지)
    • 미주입 시 → 기존 commit skip → Kafka redeliver (Parser 와 동일)
  • BuildRetryJob(msg) (*core.CrawlJob, error): ProcessingMessage → ContentRef → CrawlJob 변환
    • msg.Headers crawler/target_type 우선, ContentRef.SourceInfo fallback
    • PriorityFromHeader 헬퍼로 priority parsing 통일
    • retry_reason="validate_process_failed" + original_ref_id metadata
    • MaxRetries = bus.DefaultMaxRetries

Sub 2 — Kafka → ZSET intake goroutine (d301d92)

  • worker.ZSetIntake — Parser 동일 패턴, ContentRef 대응
  • FetchMessage → ProcessingMessage unmarshal → ContentRef unmarshal → ZSET Push → Kafka commit
  • ZSET member key = ContentRef.ID
  • 실패 정책 (Parser 와 동일): unmarshal 실패 / 빈 ID → commit / push 실패 → commit skip / commit 실패 → idempotent
  • defer consumer.Close() 로 Kafka reader 자원 정리

Sub 3 — Feature flag + main.go wiring + Stage 통합 (bc36508)

  • VALIDATE_PRIORITY_QUEUE_ENABLED=true + redisClientShared != nil 시 ZSET 모드 활성
  • ZSET 모드 + retryScheduler nil → fatal (메시지 손실 방지 가드)
  • validate.Stage.SetZSetIntake 로 lifecycle 통합
  • 환경변수: VALIDATE_ZSET_QUEUE_KEY / VALIDATE_ZSET_ENTRY_PREFIX / VALIDATE_ZSET_MAX_SIZE / VALIDATE_ZSET_ENTRY_TTL / VALIDATE_ZSET_POP_TIMEOUT
  • .env.example 갱신

Sub 4 — 단위 테스트 (500a1c1)

  • BuildRetryJob 11 cases / PriorityFromHeader 9 cases / ZSetIntake.handleOne 6 cases
  • 모두 Redis / Kafka 의존 없는 순수 단위

CI / 머지 게이트 점검

변경 영향 범위

  • 영향 패키지/모듈: internal/processor/validate, cmd/issuetracker, .env.example
  • 위험도: Medium — Validate stage 핵심 흐름 변경. 단, feature flag default false + 미설정 시 기존 Kafka 흐름 100% 유지 → 안전 fallback.

Required Status Checks

  • 통과 확인 대상 (PR Checks 탭에서 확인):
    • Commit Lint
    • PR Title Lint
    • Linked Issue Check
    • Format Check
    • Build
    • Test
    • Lint

롤백 계획

  1. VALIDATE_PRIORITY_QUEUE_ENABLED=false (또는 unset) — 기존 Kafka consume 흐름 복귀
  2. ZSET 잔존 항목은 entry TTL 24h 후 자연 정리
  3. 영구 롤백: 위 환경변수 + 본 PR revert

후속 작업 (메타 #515 Phase 2)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Optional Redis-backed priority queue for the validate stage with Kafka→ZSET intake.
    • Automatic retry enqueueing for failed validation messages (with DLQ fallback on enqueue failure).
  • Configuration

    • New env vars to control priority queue: VALIDATE_PRIORITY_QUEUE_ENABLED, VALIDATE_ZSET_QUEUE_KEY, VALIDATE_ZSET_ENTRY_PREFIX, VALIDATE_ZSET_MAX_SIZE, VALIDATE_ZSET_ENTRY_TTL, VALIDATE_ZSET_POP_TIMEOUT.
  • Tests

    • Added unit tests for retry-job construction and ZSET intake handling.

Review Change Stack

juhy0987 and others added 4 commits May 21, 2026 00:25
…ldRetryJob (이슈 #523 Sub 1)

Parser PR #526 의 동일 패턴 적용 — ZSET 인입 모드에서 BZPOPMIN 이 곧 ack 라 commit
skip 으로 redeliver 불가. RetryScheduler 경유로 Kafka 재발행 → 다음 intake 가 ZSET
으로 흡수하는 패턴이 메시지 손실 방지의 유일한 방법.

변경:
- Worker.retryScheduler bus.RetryScheduler 필드 추가 (nil 허용)
- SetRetryScheduler setter — Start 전 wiring 단계에서 1회 주입
- Handle: process 실패 시 retryScheduler 주입 여부 분기
  - 주입 시: enqueueRetry → commit (메시지 손실 방지)
  - 미주입 시: 기존 commit skip → Kafka redeliver (Kafka 모드 호환)
- BuildRetryJob: ProcessingMessage → ContentRef → CrawlJob 변환
  - msg.Headers crawler / target_type 우선, ContentRef.SourceInfo fallback
  - PriorityFromHeader 헬퍼로 priority parsing 통일
  - retry_reason="validate_process_failed" / original_ref_id metadata
  - MaxRetries = bus.DefaultMaxRetries
- PriorityFromHeader / isValidTargetType 헬퍼 (Parser 의 동일 이름 함수와 1:1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Parser PR #526 의 zset_intake.go 패턴 복제. 차이점:
- 입력 토픽: TopicNormalized
- payload 구조: ProcessingMessage 한 단계 wrap + Data 필드의 ContentRef
- ZSET member key: ContentRef.ID (Parser 는 RawContentRef.ID)

흐름 (Parser 와 동일):
- FetchMessage → ProcessingMessage unmarshal → ContentRef unmarshal
- priority header → PriorityFromHeader
- zsetQueue.Push(priority, ref.ID, payload)
- Kafka commit (실패 시 idempotent — redeliver 시 동일 ID 재push)

실패 정책 (동일):
- ProcessingMessage / ContentRef unmarshal 실패: commit (재시도 무의미)
- 빈 ref.ID: commit + skip
- ZSET push 실패: commit skip (Kafka redeliver)

API:
- NewZSetIntake(consumer, zsetQueue, log) *ZSetIntake — nil 인자 시 nil 반환
- Run(ctx) — blocking, defer consumer.Close() 로 자원 정리
- HandleOneForTest 단위 테스트 export

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… + Stage SetZSetIntake (이슈 #523 Sub 3)

Parser PR #526 과 동일 패턴 — feature flag default off, Redis + RetryScheduler 미설정
시 안전 fallback.

변경:
- internal/processor/validate/stage.go:
  - Stage.intake *worker.ZSetIntake 필드 추가 (nil 허용)
  - SetZSetIntake setter
  - Start: intake 주입 시 go intake.Run(ctx)

- cmd/issuetracker/main.go:
  - validatePriorityQueueEnabled 분기
    - true 시: PriorityZSetQueue + PriorityZSetConsumer + ZSetIntake 구성
    - false 시: 기존 Kafka consumer 직접 사용
  - validateConsumer 가 모드에 따라 zsetConsumer 또는 kafkaConsumer
  - ZSET 모드 + retryScheduler nil → fatal (메시지 손실 방지)
  - validateStage.SetZSetIntake(validateZSetIntake) 로 lifecycle 통합

- .env.example: VALIDATE_PRIORITY_QUEUE_ENABLED + VALIDATE_ZSET_* (Parser 동일 형식)

환경변수:
- VALIDATE_PRIORITY_QUEUE_ENABLED (default false)
- VALIDATE_ZSET_QUEUE_KEY (default "validate:zset:queue")
- VALIDATE_ZSET_ENTRY_PREFIX (default "validate:zset:entry:")
- VALIDATE_ZSET_MAX_SIZE (default 100000)
- VALIDATE_ZSET_ENTRY_TTL (default 24h)
- VALIDATE_ZSET_POP_TIMEOUT (default 1s)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…take (이슈 #523 Sub 4)

Parser PR #526 와 동일 패턴 단위 테스트:

BuildRetryJob 11 cases:
- 정상 ContentRef → CrawlJob (priority/url/crawler/metadata 검증)
- 헤더 없음 → normal default / 잘못된 priority → normal 보정
- crawler 헤더 우선 / 없을 때 SourceInfo fallback / 둘 다 빈 값 → "validate-retry"
- target_type=category / =article / invalid → Article fallback
- 빈 URL / malformed ProcessingMessage / malformed ContentRef → error

ZSetIntake.HandleOneForTest 6 cases (PriorityPusher + stubConsumer mock):
- 정상 push+commit
- ProcessingMessage unmarshal 실패 → commit
- ContentRef unmarshal 실패 → commit
- 빈 ID → commit
- push 실패 → commit skip (Kafka redeliver)
- 헤더 없을 때 normal priority default

PriorityFromHeader 9 cases: Parser 와 동일 매핑 검증.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 20, 2026 15:33
@juhy0987 juhy0987 added the enhancement New feature or request label May 20, 2026
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 076bcd86-2445-46de-b75a-439859cb4aba

📥 Commits

Reviewing files that changed from the base of the PR and between 500a1c1 and 88ec52e.

📒 Files selected for processing (3)
  • cmd/issuetracker/main.go
  • internal/processor/validate/worker/worker.go
  • test/internal/processor/validate/worker/retry_job_test.go

📝 Walkthrough

Walkthrough

Validate stage now supports optional Redis ZSET-based intake for priority-aware message processing. Kafka messages are conditionally bridged into a ZSET, workers pop by priority, and failed validations are retried by reconstructing job metadata from message headers.

Changes

Validate ZSET Intake and Retry Flow

Layer / File(s) Summary
Configuration and environment variables
.env.example
Redis ZSET queue settings added: enablement flag, queue key, entry prefix, max size, TTL, and pop timeout.
Stage lifecycle wiring for ZSET intake
internal/processor/validate/stage.go
Stage gains optional intake *worker.ZSetIntake and SetZSetIntake; Start conditionally launches intake.Run(ctx) when set.
ZSET intake component (Kafka→Redis bridge)
internal/processor/validate/worker/zset_intake.go
ZSetIntake polls Kafka, unmarshals ProcessingMessage/ContentRef, computes priority, pushes to PriorityZSetQueue, and commits Kafka on push success; parse/schema/empty-ID errors commit to avoid redelivery loops; push failures skip commit.
ZSetIntake unit tests and stubs
test/internal/processor/validate/worker/zset_intake_test.go
Stub PriorityPusher and Consumer validate HandleOne behavior: success push+commit, unmarshal failures commit, empty ID commits, push failure skips commit, and default priority mapping.
Bootstrap and main.go orchestration
cmd/issuetracker/main.go
Creates dedicated validate Kafka consumer, conditionally builds PriorityZSetQueue/PriorityZSetConsumer when enabled, creates ZSetIntake to bridge Kafka→ZSET, injects retryScheduler in ZSET mode (fatal if missing), and wires intake into stage via SetZSetIntake.
Worker retry scheduler and retry-job construction
internal/processor/validate/worker/worker.go
Worker gains retryScheduler field and SetRetryScheduler. Handle now enqueues a retry CrawlJob via BuildRetryJob and RetryScheduler.Enqueue on processing errors (with DLQ fallback), and commits original offset on enqueue success. PriorityFromHeader and isValidTargetType added.
BuildRetryJob and PriorityFromHeader tests
test/internal/processor/validate/worker/retry_job_test.go
Tests cover valid retry job construction, priority and timeout header handling/defaults, crawler header precedence, target type parsing, and malformed payload error paths; table-driven tests for priority mapping/defaulting.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • EinSofINTEREST/IssueTracker#526: Implements parallel ZSET intake + retry scheduler pattern for parser stage using identical BuildRetryJob/PriorityFromHeader/ZSetIntake approach.
  • EinSofINTEREST/IssueTracker#207: Introduced the original processor.Stage and validate.Stage wrapper structure that this PR extends with ZSetIntake field and lifecycle management.
  • EinSofINTEREST/IssueTracker#418: Previously refactored validate.Stage wiring and constructor signatures that this PR further extends with optional ZSET intake.

Suggested labels

enhancement

Poem

🐰 I hopped from Kafka to Redis with glee,
Pushed jobs by priority, one-two-three,
When bumps arrive, I stitch the job anew,
RetryScheduler pats each failed queue,
A tiny rabbit keeps the validate tree.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.35% 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 '[FEAT#523] Validate stage Redis ZSET intermediate queue — priority sub-ordering' clearly and specifically describes the main change: adding a Redis ZSET-based intermediate queue to the Validate stage for priority-based sub-ordering.
Linked Issues check ✅ Passed The PR successfully implements all primary coding objectives from issue #523: Kafka→ZSET intake with priority calculation and Kafka commit, ZSET pop for priority-ordered processing, retry scheduling on failures, environment variable configuration, feature flag gating, and comprehensive unit tests covering retry jobs, priority header parsing, and ZSET intake logic.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #523 requirements: .env.example updates for ZSET configuration, intake goroutine implementation, worker retry mechanism, stage lifecycle integration, and unit test coverage. No extraneous or unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feature/#523/validate-priority-zset-queue

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 and usage tips.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements a priority queue for the validation stage using Redis ZSET, enabling priority-based sub-ordering of messages by moving them from Kafka to an intermediate Redis queue. Key changes include the introduction of a ZSetIntake component to handle the message transfer, lifecycle management for this process within the validation stage, and the integration of a RetryScheduler to prevent message loss during processing failures in ZSET mode. Feedback suggests improving the BuildRetryJob function by inheriting the timeout value from message headers instead of using a hardcoded 30-second default, which would ensure consistency with existing republishing logic.

Comment thread internal/processor/validate/worker/worker.go

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@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: 2

🤖 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 `@cmd/issuetracker/main.go`:
- Around line 1052-1055: The validate-only initialization is being enabled even
when the Validate stage is globally disabled; update the gating logic so
validatePriorityQueueEnabled also requires stagesCfg.ValidateEnabled (i.e.,
combine envBoolOrDefault("VALIDATE_PRIORITY_QUEUE_ENABLED", false) &&
redisClientShared != nil && stagesCfg.ValidateEnabled) and apply the same change
to the duplicate block around validateKafkaConsumer/validateZSetIntake (the
other occurrence referenced in the review), ensuring both
validatePriorityQueueEnabled declarations and any conditionals that instantiate
validateKafkaConsumer or validateWorkerPkg.ZSetIntake honor
stagesCfg.ValidateEnabled.

In `@internal/processor/validate/worker/worker.go`:
- Around line 168-172: The current block that calls w.enqueueRetry(ctx, msg,
err) (via retryScheduler) can drop the message if enqueueErr occurs; modify this
branch so that when enqueueErr != nil you perform a durable fallback before
returning: call the component that publishes to the DLQ (or reinsert into the
durable queue) with the original msg and error metadata (use the same message
payload and include err details), log any errors from that fallback, and only
then return; update the code around retryScheduler / enqueueRetry to invoke the
DLQ publish method (or durableQueue.Enqueue) as the fallback so messages are not
lost when retry enqueue fails.
🪄 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: ed7c5fad-5193-4381-b214-0075d44ab104

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4769f and 500a1c1.

📒 Files selected for processing (7)
  • .env.example
  • cmd/issuetracker/main.go
  • internal/processor/validate/stage.go
  • internal/processor/validate/worker/worker.go
  • internal/processor/validate/worker/zset_intake.go
  • test/internal/processor/validate/worker/retry_job_test.go
  • test/internal/processor/validate/worker/zset_intake_test.go

Comment thread cmd/issuetracker/main.go Outdated
Comment thread internal/processor/validate/worker/worker.go
juhy0987 and others added 2 commits May 21, 2026 01:18
gemini #3275211693 (Medium) 반영. 기존 30s 하드코딩 대신 msg.Headers["timeout_ms"]
헤더를 우선 사용 — republishForReparse 의 동일 정책과 통일.

변경:
- BuildRetryJob: msg.Headers[core.HeaderTimeoutMs] 파싱하여 jobTimeout 결정,
  부재 / 잘못된 값 시 buildRetryDefaultTimeout (30s) 사용
- buildRetryDefaultTimeout 상수 신설 — 의미 명확화

테스트 추가 (3 cases):
- timeout_ms="60000" → 60s 사용
- 헤더 부재 → 30s default
- 잘못된 값 (0/-1/non-numeric/empty) → 30s default

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…llback

coderabbit 2건 Major 피드백 반영:

#3275227525: VALIDATE_PRIORITY_QUEUE_ENABLED 활성화에 STAGES_VALIDATE_ENABLED 추가 의존:
- 기존: stage 비활성 환경에서도 ZSET 분기 진입 → retryScheduler nil 시 fatal 가드 발동
- 변경: validatePriorityQueueEnabled = stagesCfg.ValidateEnabled && env && redisClientShared
  → Validate stage 가 disabled 면 ZSET wiring 자체 skip

#3275227536: ZSET 모드에서 retry enqueue 실패 시 영구 손실 → DLQ fallback:
- 기존: enqueueRetry 실패 시 단순 return → 메시지는 이미 ZSET 에서 pop, 영구 손실
- 변경: enqueueRetry 실패 시 sendToDLQ 호출 → 운영 가시성 + 수동 복구 가능
- DLQ 메시지의 reason 필드에 enqueue 에러 + 원본 process 에러 모두 보존
- DLQ 발행 성공 후 commit (ZSETConsumer 의 commit 은 no-op)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Validate stage priority-aware 처리 — Redis ZSET intermediate queue

2 participants