[Feat] 일별 사용량 동기화 배치 구현 (SKIP LOCKED 기반 다중 워커 병렬 처리)#5
Conversation
- 03:00 manager lock 획득 실패 시 worker 시작 감지로 진입 - scheduler가 계산한 usageDate를 worker 시작 감지에 전달 - 최신 RUNNING batch 대신 usageDate 기준 RUNNING usage sync batch 조회 - 애플리케이션 기동 직후 worker 자동 감지 경로 제거
- TaskScheduler 스레드 경합 해소: - `application.yaml`의 `spring.task.scheduling.pool.size`를 2로 확장하여 03:00 KST Manager cron과 Worker 지연 스케줄링 간 병목 방지 - 장애 관측용 Micrometer Metric 노출: - `LineDailyUsageSyncBatchMetrics`를 추가해 최신 RUNNING 배치의 실패 타겟 수, 상태 코드, 실행 시간을 Gauge로 노출 (AlertManager 연동용) - 관리자용 Rerun API 및 실패 타겟 부분 재처리 구현: - `AdminTrafficBatchController`에 `FAILED`/`ABANDONED` 배치 재처리를 위한 `/rerun` 엔드포인트 추가 - `LineDailyUsageSyncRerunService`를 구현하여 과거 이력을 덮어쓰지 않고 새로운 RUNNING 메타데이터 생성 - `LineDailyBatchTargetMapper`를 통해 기존 성공 데이터(`DONE`/`SKIPPED`)는 보존하고 `FAILED` 타겟만 `RETRYABLE`로 전환하여 안전하게 재개되도록 트랜잭션 분리
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthrough일별 사용량 동기화 배치: 앱 사용량을 individual/shared/qos로 분리해 Redis 스냅샷을 읽고 Manager가 Target을 적재·Worker가 선점해 MySQL로 동기화하며 Resume/Rerun·모니터링·테스트를 추가했습니다. 변경사항일별 사용량 동기화 배치 (단일 cohort)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Sequence Diagram (high-level manager→worker flow)sequenceDiagram
participant Scheduler
participant Redis
participant Manager
participant DB
participant Worker
Scheduler->>Redis: try acquire manager lock (SET NX PX)
alt lock acquired
Scheduler->>Manager: run(usageDate, managerId)
Manager->>DB: insertPendingBatch / insertTargets(INSERT IGNORE chunks)
Manager->>DB: completeTargetInsert -> startUsageSyncWithTargetCount
Manager->>Redis: release lock (lua)
Manager->>Worker: trigger worker scheduler (afterCommit)
else lock not acquired
Scheduler->>Worker: start worker scheduler (polling)
end
Worker->>DB: selectClaimableTargets FOR UPDATE SKIP LOCKED
Worker->>Redis: read daily total/app/shared keys
Worker->>DB: insertDailyTotal/app/shared, mark target DONE/FAILED/RETRYABLE
DB->>Worker: update processed count (CAS)
건설적 권고(요약):
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
|
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 31
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/main/java/com/pooli/data/domain/entity/DailyAppTotalData.java (1)
12-25: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win클래스 책임 Javadoc 보강이 필요합니다.
Line 12의 클래스가 변경되었는데 클래스 책임/역할 설명 Javadoc이 없습니다. 이번 필드 분리 의도(개인/공유/QoS 사용량 보관)를 클래스 수준에서 명시해 주세요.
As per coding guidelines
**/*.{java,kt}: "When creating or modifying a class, write or update Javadoc that explains the class responsibility and role."🤖 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/pooli/data/domain/entity/DailyAppTotalData.java` around lines 12 - 25, Add a Javadoc comment to the DailyAppTotalData class documenting its responsibility and role: state that this DTO/entity stores per-day aggregated app usage totals and explicitly explain the intent of the separated fields individualUsageData, sharedUsageData, and qosUsageData (i.e., personal usage, shared/pooled usage, and QoS-attributed usage) and note any invariants or usage expectations (e.g., which fields can be null or summed). Place the Javadoc immediately above the class declaration for DailyAppTotalData so it accompanies the class and clarifies its purpose for future maintainers.src/main/java/com/pooli/traffic/service/runtime/TrafficRemainingBalanceCacheService.java (1)
78-98: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff
incrementAmountIfPresent의 Redis 호출 2회 구조 검토현재
readAmount()로 존재 여부를 확인한 후increment()를 호출하므로 Redis 왕복이 2회 발생합니다. 동시성 환경에서 두 호출 사이에 다른 스레드가 키를 삭제하면 의도치 않게 새 키가 생성될 수 있습니다.Lua 스크립트로 "존재하면 increment" 로직을 원자적으로 처리하거나,
HINCRBY의 반환값과HEXISTS결과를 파이프라인으로 묶는 방식을 고려할 수 있습니다. 다만 현재 사용 패턴에서 실제 문제가 되는지는 호출 빈도와 키 생명주기에 따라 다릅니다.🤖 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/pooli/traffic/service/runtime/TrafficRemainingBalanceCacheService.java` around lines 78 - 98, incrementAmountIfPresent currently does a readAmount(...) then opsForHash().increment(...), causing two Redis round-trips and risking a race that can create a new hash between calls; fix by replacing the two-step logic with a single atomic Redis call (use cacheStringRedisTemplate.execute with a RedisCallback or EVAL a Lua script) that: 1) checks that the hash and its "amount" field exist, 2) reads the current value to enforce the unlimited sentinel (-1) no-decrement rule, and 3) performs HINCRBY only when allowed, returning a boolean success; update incrementAmountIfPresent to call this single atomic operation and remove the separate readAmount call to avoid creating a key inadvertently.
🤖 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 `@docs/일별` 동기화 배치 요약.md:
- Line 38: Multiple Markdown headers (e.g., the header "## 1. 동기화 배치 절차 구성 요소"
and other headers at the referenced locations) violate markdownlint MD022 by
missing the required blank line before or after; fix each header by ensuring
there is exactly one blank line both immediately above and immediately below
each header line (or at least one blank line where appropriate) so headers are
separated from surrounding content, then re-run markdownlint to confirm warnings
at the listed header locations (45, 85-86, 126, 165-166, 205, 249, 254, 262,
267) are resolved.
- Around line 5-36: Add the standard Mermaid init directive line %%{init:
{"theme": "default", "themeVariables": {"background": "`#ffffff`"}}}%% at the very
top of every Mermaid code block (the blocks that start with ```mermaid and
contain sequenceDiagram/other diagrams) so they render with a consistent white
background; update the existing sequenceDiagram block (the one with participants
Scheduler/Redis/Manager/Worker/DB) and repeat the same change for the other
Mermaid blocks referenced in the comment (the blocks currently at the later
sections of the document) to ensure consistent rendering across all diagrams.
In `@docs/일별` 사용량 동기화 배치 가이드.md:
- Around line 41-46: Several fenced code blocks lack a language tag and
empty-line padding which triggers MD040/MD031 lint warnings; update each
triple-backtick block (e.g., the block containing "Spring 앱 (Micrometer Gauge) →
Prometheus (metric scraping) → AlertManager (Docker 컨테이너) → Discord 웹훅" and the
other fenced sections referenced) to include an explicit language token such as
text, bash, or sql as appropriate, and ensure there is a blank line before and
after each fence (add a blank line above the opening ``` and below the closing
```), so all fenced blocks are consistently annotated and have surrounding
whitespace to satisfy the linter.
In `@GEMINI.md`:
- Around line 218-223: The nested fenced block that starts with the outer
````markdown```` wrapper and contains the inner ```mermaid ... ``` block is
missing blank lines before and after, causing MD031; update the document so
there is an empty line immediately before the outer fenced block and an empty
line immediately after it (i.e., add a blank line above the ````markdown````
opening fence and below the closing fence) to satisfy the lint rule.
In `@scripts/test-data/generate_test_data_csv.py`:
- Around line 1397-1402: The zip over chosen_apps, individual_app_usage_parts,
shared_app_usage_parts, and qos_app_usage_parts should be made strict to enforce
equal lengths; update the zip call in the loop that iterates "for app_id,
individual_usage, shared_app_usage, qos_usage in zip(...)" to pass strict=True
(i.e., zip(chosen_apps, individual_app_usage_parts, shared_app_usage_parts,
qos_app_usage_parts, strict=True)) so mismatched list lengths raise immediately
instead of silently truncating.
In `@src/main/java/com/pooli/data/service/impl/DataServiceImpl.java`:
- Around line 211-214: In DataServiceImpl.java update the recalculation guards
to skip sentinel remaining=-1 cases by requiring remainingAmount >= 0 before
doing total - remaining; specifically, change the conditional that uses
personalTotalAmount and personalRemainingAmount (where personalUsedAmount is
computed) to include personalRemainingAmount >= 0, and do the same for the
analogous businessTotalAmount/businessRemainingAmount block so you never compute
total - remaining when remaining is the -1 sentinel.
In
`@src/main/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetrics.java`:
- Around line 82-86: The current branch in LineDailyUsageSyncBatchMetrics that
checks batchJob.getStatus() != LineDailyBatchStatus.RUNNING incorrectly zeroes
failedCount and runDurationSeconds; stop resetting failedCount there so failure
metrics are preserved across terminal states and only reset runDurationSeconds
to 0 for non-RUNNING states. Update the block in the method containing
failedCount and runDurationSeconds (refer to failedCount.set(...),
runDurationSeconds.set(...), and the status check using
LineDailyBatchStatus.RUNNING) to only set runDurationSeconds.set(0L) when not
RUNNING and leave failedCount unchanged.
In
`@src/main/java/com/pooli/traffic/domain/batch/LineDailyBatchJobCreateResult.java`:
- Around line 7-10: Add Javadoc comments to the record
LineDailyBatchJobCreateResult and document each record component: explain that
the boolean created indicates whether a new LineDailyBatchJob was created (true)
or an existing one was reused (false), and that the batchJob component holds the
corresponding LineDailyBatchJob instance returned (the newly created job when
created==true or the existing job when created==false); also state the
nullability contract for batchJob (if it can be null or never null) so callers
understand the exact behavior per the project DTO/commenting guideline.
In
`@src/main/java/com/pooli/traffic/domain/dto/response/LineDailyUsageSyncResumeResDto.java`:
- Around line 20-23: Add short, field-level JavaDoc or line comments immediately
above each DTO field in LineDailyUsageSyncResumeResDto (batchJobId, usageDate,
status, resumeAccepted) that explain the domain meaning and response contract
for that field; for example, describe what batchJobId identifies, the
semantics/format of usageDate, the possible states represented by
LineDailyBatchStatus, and what true/false for resumeAccepted denotes. Keep each
comment concise (one sentence) and use the same comment style as other DTOs to
satisfy the /* .java */ guideline for DTOs/enums/records.
In `@src/main/java/com/pooli/traffic/mapper/DailyAppUsageBatchInsertRow.java`:
- Around line 6-11: Add Javadoc comments for the record
DailyAppUsageBatchInsertRow and document each record component: applicationId,
individualUsageData, sharedUsageData, and qosUsageData. For each component
(applicationId, individualUsageData, sharedUsageData, qosUsageData) add a short
description explaining its meaning, units (e.g., bytes, seconds), and any
important constraints or interpretation (nullable/optional, aggregation
semantics). Ensure the comments follow JavaDoc style above the record
declaration so the DTO, enum, and record documentation guideline is satisfied.
In `@src/main/java/com/pooli/traffic/mapper/LineDailyBatchJobMapper.java`:
- Around line 13-16: The mapper currently relies on "select then insert" in
LineDailyBatchJobMapper which exposes a TOCTOU race causing duplicate
batch_name+usage_date rows; add DB-level idempotency and an atomic upsert in the
mapper (e.g., add a new method like upsertLineDailyBatchJob or
insertIfNotExists) that uses a unique constraint on (batch_name, usage_date)
plus a single-statement upsert (INSERT ... ON CONFLICT DO NOTHING/UPDATE or
MERGE depending on your DB) and return the existing/created record id; update
callers to use this new upsert method instead of separate select+insert so
creation is atomic and safe under concurrent manager retries.
In `@src/main/java/com/pooli/traffic/service/batch/DailyAppUsage.java`:
- Around line 3-11: Add Javadoc to the DailyAppUsage record that documents each
record component: annotate the record with a Javadoc summary and include `@param`
tags describing applicationId (the app identifier), individualUsageData
(per-user usage bytes/count for that app), sharedUsageData (shared/group usage
bytes/count attributed to that app), and qosUsageData (QoS-related usage
bytes/count for that app); update the JavaDoc above the record declaration for
DailyAppUsage so each component’s meaning is explicitly documented per the
DTO/record guideline.
In `@src/main/java/com/pooli/traffic/service/batch/DailySharedUsage.java`:
- Around line 6-9: Add component-level Javadoc comments to the record
DailySharedUsage describing each record component: document what familyId
represents (e.g., the unique identifier of the family/group) and what
usageAmount represents (e.g., the aggregated/shared usage amount in the chosen
unit), using brief, clear descriptions for the components so the record serves
as a self-documented DTO per coding guidelines.
In `@src/main/java/com/pooli/traffic/service/batch/LineDailyBatchJobService.java`:
- Around line 31-54: The current check-then-insert in
createPendingForAutomaticRunIfAbsent (using
lineDailyBatchJobMapper.selectLatestByBatchNameAndUsageDate then
lineDailyBatchJobMapper.insert) can race and create duplicates for (batch_name,
usage_date); add a DB-level uniqueness constraint on LINE_DAILY_BATCH_JOB for
(batch_name, usage_date) and change the persistence logic in
LineDailyBatchJobMapper.xml to an atomic upsert/INSERT IGNORE/ON DUPLICATE KEY
UPDATE (or catch duplicate-key SQL exception) so concurrent callers either reuse
the existing row; if you catch a duplicate-key error, re-query
selectLatestByBatchNameAndUsageDate and return that existing LineDailyBatchJob
instead of failing.
In
`@src/main/java/com/pooli/traffic/service/batch/LineDailyBatchManagerScheduler.java`:
- Around line 49-74: The current runForUsageDate method can skip starting
workers if lineDailyBatchManagerService.run(usageDate, managerInstanceId)
throws; wrap the manager invocation in try/catch and ensure workerStartAllowed
is determined even on exception by invoking a safe fallback check (e.g., call a
new/existing idempotent method on LineDailyBatchManagerService such as
isWorkerStartRequired(usageDate) or fetch the batch state) and then call
lineDailyBatchWorkerScheduler.startForUsageDate(usageDate) if that check
indicates work exists; keep the finally block that calls
releaseManagerLock(lockKey, managerInstanceId) unchanged and ensure exceptions
are logged/rethrown only after the fallback decision so the lock release and
worker start decision are always handled.
In
`@src/main/java/com/pooli/traffic/service/batch/LineDailyBatchTargetClaimService.java`:
- Around line 30-35: The claim method is missing input validation: at the start
of LineDailyBatchTargetClaimService.claim(usageDate, workerId, limit) add checks
to reject null/empty workerId and non-positive limit (e.g., throw
IllegalArgumentException or a domain-specific exception) before calling
lineDailyBatchTargetMapper.selectClaimableTargetsForUpdate; ensure the
validation runs prior to using PROCESSING_LEASE_TIMEOUT_SECONDS and limit so no
invalid values are passed into the DB claim query and include clear error
messages referencing workerId and limit.
In
`@src/main/java/com/pooli/traffic/service/batch/LineDailyBatchWorkerScheduler.java`:
- Around line 60-80: The runStartCheckCycle method can exit without scheduling a
next cycle if an exception is thrown; wrap the entire body of runStartCheckCycle
in a try/catch that catches Throwable, logs the error (include context like
usageDate), and always calls scheduleNextCheck(usageDate, <RETRY_DELAY_MS> or
START_CHECK_DELAY_MS + nextJitterMs()) in the catch/finally path to guarantee
the loop continues; ensure exceptions from
lineDailyBatchJobService.findRunningUsageSyncBatch(...) and
lineDailyUsageSyncWorkerService.run(...) are handled and do not prevent
scheduling the retry.
In `@src/main/java/com/pooli/traffic/service/batch/LineDailyUsageReadResult.java`:
- Around line 17-18: The hasAnyUsage() method in the LineDailyUsageReadResult
record can NPE when appUsages is null; either normalize appUsages to an empty
list in the record compact constructor (e.g., set appUsages = List.of() when
null) or make hasAnyUsage() null-safe by changing its check to use (appUsages !=
null && !appUsages.isEmpty()). Update the record compact constructor or the
hasAnyUsage() method accordingly so appUsages is never dereferenced when null.
- Around line 11-15: Add Javadoc or inline comments for the record
LineDailyUsageReadResult explaining the semantic meaning of each record
component: describe what totalUsageData represents (e.g., aggregated total usage
value and its unit/source), what appUsages contains (e.g., list of DailyAppUsage
entries and their aggregation scope/source), and what sharedUsage represents
(e.g., DailySharedUsage for cross-app/shared resources and its aggregation
scope); place the comments directly above the record declaration and annotate
each component so downstream batch logic can unambiguously interpret the data.
In
`@src/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncPersistenceService.java`:
- Around line 26-27: Add brief JavaDoc or inline comments above the constants
MAX_TARGET_RETRY_COUNT and LAST_ERROR_MESSAGE_MAX_LENGTH in
LineDailyUsageSyncPersistenceService explaining what each constant controls
(e.g., MAX_TARGET_RETRY_COUNT = max retry attempts for target persistence
operations, LAST_ERROR_MESSAGE_MAX_LENGTH = truncation limit for stored error
messages), why the chosen values (10 and 512) were selected (e.g., tradeoff
between retry aggressiveness and system load; reason for 512 bytes/characters as
reasonable log size), and any implications of changing them (what to watch for
if increased/decreased). Ensure the comments reference the exact symbols
MAX_TARGET_RETRY_COUNT and LAST_ERROR_MESSAGE_MAX_LENGTH so future readers can
quickly understand purpose and tuning considerations.
In
`@src/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncWorkerService.java`:
- Around line 192-230: The cause-chain traversal in
isRetryableDbException(DataAccessException) can loop if an exception's
getCause() returns itself; modify that method to guard against cycles by
tracking visited Throwables (e.g., a Set<Throwable> seen) or enforcing a max
depth and bail out when a repeat or depth limit is reached, while preserving the
existing instanceof checks (QueryTimeoutException, CannotAcquireLockException,
DeadlockLoserDataAccessException, PessimisticLockingFailureException,
ConcurrencyFailureException); keep the helper
isRetryableDbException(RuntimeException) delegating to the DataAccessException
overload unchanged.
In `@src/main/resources/application.yaml`:
- Around line 36-39: 현재 application.yaml에 하드코딩된 task.scheduling.pool.size 값을
환경변수로 외부화하세요: task.scheduling.pool.size 항목을 ${TASK_SCHED_POOL_SIZE:2} 형태의
플레이스홀더로 바꿔 기본값 2를 유지하고 외부에서 TASK_SCHED_POOL_SIZE로 재정의할 수 있게 하며, 관련 문서(환경변수 이름 및
기대 타입: integer)를 업데이트하고 배포 환경에서 해당 환경변수로 값을 주입해 변경 없이 풀 크기를 조정할 수 있도록 합니다.
In
`@src/main/resources/db/migration/V2605250300__split_daily_app_total_usage_data.sql`:
- Around line 1-5: The migration currently alters DAILY_APP_TOTAL_DATA by adding
individual_usage_data, shared_usage_data, qos_usage_data and dropping
total_usage_data in one step, which breaks zero-downtime deploys; split this
into two migrations: first migration should only ADD the three columns (BIGINT
NOT NULL DEFAULT 0) and leave total_usage_data intact, rollout and perform
application switch/backfill to start using
individual_usage_data/shared_usage_data/qos_usage_data, then create a follow-up
migration that only DROP COLUMN total_usage_data once all instances no longer
read it. Ensure you reference DAILY_APP_TOTAL_DATA and the columns
individual_usage_data, shared_usage_data, qos_usage_data, total_usage_data when
implementing the split.
In
`@src/main/resources/db/migration/V2605250400__create_line_daily_batch_job.sql`:
- Around line 2-23: The table LINE_DAILY_BATCH_JOB currently only has a
non-unique index idx_line_daily_batch_job_lookup on (batch_name, usage_date)
which allows concurrent inserts of the same batch_name+usage_date; add a
DB-level uniqueness constraint on (batch_name, usage_date) to enforce integrity
(e.g. create a UNIQUE INDEX/CONSTRAINT on those columns or change
idx_line_daily_batch_job_lookup to be UNIQUE). Before creating the constraint,
include a safe deduplication step in the migration to detect and resolve any
existing duplicate rows (merge/delete duplicates or abort with an explicit
error), and create the unique index/constraint in a way that won’t break
existing data (handle locking/long-running operations per your DB best
practices). Ensure references in code that expect non-uniqueness handle the new
constraint if needed.
In
`@src/main/resources/db/migration/V2605260100__drop_line_daily_batch_job_updated_at.sql`:
- Around line 1-2: The migration currently drops LINE_DAILY_BATCH_JOB.updated_at
immediately which is unsafe for zero-downtime deploys; revert this DROP COLUMN
from V2605260100__drop_line_daily_batch_job_updated_at.sql (remove the ALTER ...
DROP COLUMN statement) and instead split into two steps: (1) remove all
application references to updated_at in your app changes and deploy, and (2) add
a separate follow-up migration (e.g.,
V<later>__drop_line_daily_batch_job_updated_at.sql) that performs ALTER TABLE
LINE_DAILY_BATCH_JOB DROP COLUMN updated_at only after the code no longer
references the column; ensure the migration files and commit message reference
the table LINE_DAILY_BATCH_JOB and column updated_at so reviewers can track the
two-step removal.
In `@src/main/resources/lua/traffic/deduct_unified.lua`:
- Around line 145-156: The unconditional checks for daily_shared_usage_key and
family_id cause non-shared requests to fail; instead, keep existing general
validations for target_data and app_id but move or narrow the validation of
daily_shared_usage_key and family_id into the shared-deduction branch (the code
path where shared_deducted > 0) so they are only required when shared usage will
be written/read; update logic around the shared_deducted calculation to assert
daily_shared_usage_key is present and family_id > 0 before any shared-related
Redis operations.
In `@src/main/resources/mapper/traffic/LineDailyBatchTargetMapper.xml`:
- Around line 105-133: The query selectClaimableTargetsForUpdate can benefit
from the existing composite index KEY idx_line_daily_batch_target_claim
(usage_date, status, status_updated_at); to avoid extra sorting/lookup caused by
ORDER BY status_updated_at ASC, id ASC you should extend that index to include
id (e.g. (usage_date, status, status_updated_at, id)) in the migration
V2605250410__create_line_daily_batch_target.sql or add a new migration that adds
the fourth column, and validate under load that the index expansion improves
throughput before rolling it out.
In `@src/main/resources/mapper/traffic/TrafficDailyUsageBatchMapper.xml`:
- Around line 32-55: 현재 insertDailyAppUsages 매퍼는 빈 appUsages 컬렉션이 들어오면 SQL 문법
오류가 날 가능성이 있으므로, insertDailyAppUsages 엘리먼트 내부에서 appUsages가 null이 아니고 size>0일 때만
VALUES/foreach 블록을 렌더하도록 가드(<if test="appUsages != null and appUsages.size() >
0">)를 추가하세요; 호출부인 LineDailyUsageSyncPersistenceService#insertUsages가 현재 방어하고 있지만
향후 호출 경로를 대비해 매퍼 수준에서 appUsages를 체크하여 안전하게 방어하십시오.
In `@src/test/java/com/pooli/traffic/config/TrafficSchedulingConfigTest.java`:
- Around line 18-24: The test taskSchedulerPoolSizeIsTwo in
TrafficSchedulingConfigTest currently asserts YAML text contains a specific
indentation-sensitive fragment; instead load the YAML string returned by
read(APPLICATION_YAML) into a PropertySource (e.g., using
YamlPropertySourceLoader or Spring's Binder) and read the property key
"task.scheduling.pool.size" as an int, then assertEquals(2). Replace the fragile
assertTrue(yaml.contains(...)) with code that parses the YAML and asserts the
numeric value for task.scheduling.pool.size is 2.
In
`@src/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java`:
- Around line 95-226: Several tests extract SQL using
substring(sql.indexOf("<select id=\"...\"")) or substring(sql.indexOf("<update
id=\"...\"")) without finding the matching closing tag, allowing overbroad
snippets and false positives; update each extraction (for ids:
selectClaimableTargetsForUpdate, markTargetsProcessing,
markTargetTerminalIfProcessing, markTargetRetryableIfProcessing,
markTargetFailedIfProcessing, markFailedTargetsRetryableByUsageDate and any
other tests using substring(indexOf(...))) to compute a start = sql.indexOf(tag)
and an explicit end = sql.indexOf(closingTag, start) (use "</select>" or
"</update>" as appropriate) and then use substring(start, end +
closingTag.length()) (with a guard for end == -1 to fail the test meaningfully)
so each assertion checks only the exact element body.
In
`@src/test/java/com/pooli/traffic/service/batch/LineDailyBatchJobServiceTest.java`:
- Around line 281-292: Add a complementary unit test in
LineDailyBatchJobServiceTest that covers the CAS failure path: create a
LineDailyBatchJob via batchJob(LineDailyBatchStatus.RUNNING), mock
lineDailyBatchJobMapper.completeRunningUsageSyncBatchIfCountsMatch(running.getId())
to return 0, call
lineDailyBatchJobService.completeRunningUsageSyncBatchIfCountsMatch(running),
assert that the result is false, and verify the mapper method was invoked; this
mirrors the existing successful test but asserts false for the 0-affected-rows
case to ensure the failure path is covered.
---
Outside diff comments:
In `@src/main/java/com/pooli/data/domain/entity/DailyAppTotalData.java`:
- Around line 12-25: Add a Javadoc comment to the DailyAppTotalData class
documenting its responsibility and role: state that this DTO/entity stores
per-day aggregated app usage totals and explicitly explain the intent of the
separated fields individualUsageData, sharedUsageData, and qosUsageData (i.e.,
personal usage, shared/pooled usage, and QoS-attributed usage) and note any
invariants or usage expectations (e.g., which fields can be null or summed).
Place the Javadoc immediately above the class declaration for DailyAppTotalData
so it accompanies the class and clarifies its purpose for future maintainers.
In
`@src/main/java/com/pooli/traffic/service/runtime/TrafficRemainingBalanceCacheService.java`:
- Around line 78-98: incrementAmountIfPresent currently does a readAmount(...)
then opsForHash().increment(...), causing two Redis round-trips and risking a
race that can create a new hash between calls; fix by replacing the two-step
logic with a single atomic Redis call (use cacheStringRedisTemplate.execute with
a RedisCallback or EVAL a Lua script) that: 1) checks that the hash and its
"amount" field exist, 2) reads the current value to enforce the unlimited
sentinel (-1) no-decrement rule, and 3) performs HINCRBY only when allowed,
returning a boolean success; update incrementAmountIfPresent to call this single
atomic operation and remove the separate readAmount call to avoid creating a key
inadvertently.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: 86877dc1-c285-4786-a44d-9e7f5a6dc559
📒 Files selected for processing (77)
AGENTS.mdGEMINI.mddocs/일별 동기화 배치 요약.mddocs/일별 사용량 동기화 배치 가이드.mdscripts/test-data/generate_test_data_csv.pysrc/main/java/com/pooli/common/config/AppClockConfig.javasrc/main/java/com/pooli/data/domain/entity/DailyAppTotalData.javasrc/main/java/com/pooli/data/service/DataService.javasrc/main/java/com/pooli/data/service/impl/DataServiceImpl.javasrc/main/java/com/pooli/family/service/FamilySharedPoolsService.javasrc/main/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetrics.javasrc/main/java/com/pooli/traffic/controller/AdminTrafficBatchController.javasrc/main/java/com/pooli/traffic/domain/batch/BatchName.javasrc/main/java/com/pooli/traffic/domain/batch/LineDailyBatchJob.javasrc/main/java/com/pooli/traffic/domain/batch/LineDailyBatchJobCreateResult.javasrc/main/java/com/pooli/traffic/domain/batch/LineDailyBatchStatus.javasrc/main/java/com/pooli/traffic/domain/batch/LineDailyBatchTarget.javasrc/main/java/com/pooli/traffic/domain/batch/LineDailyBatchTargetStatus.javasrc/main/java/com/pooli/traffic/domain/dto/response/LineDailyUsageSyncRerunResDto.javasrc/main/java/com/pooli/traffic/domain/dto/response/LineDailyUsageSyncResumeResDto.javasrc/main/java/com/pooli/traffic/mapper/DailyAppUsageBatchInsertRow.javasrc/main/java/com/pooli/traffic/mapper/LineDailyBatchJobMapper.javasrc/main/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapper.javasrc/main/java/com/pooli/traffic/mapper/TrafficDailyUsageBatchMapper.javasrc/main/java/com/pooli/traffic/service/batch/DailyAppUsage.javasrc/main/java/com/pooli/traffic/service/batch/DailyAppUsageAccumulator.javasrc/main/java/com/pooli/traffic/service/batch/DailySharedUsage.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchJobService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchManagerScheduler.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchManagerService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchTargetClaimService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchWorkerScheduler.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageReadResult.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageRedisReader.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncPersistenceService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncRerunService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncResumeService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncWorkerRunResult.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncWorkerService.javasrc/main/java/com/pooli/traffic/service/decision/TrafficDeductLuaExecutor.javasrc/main/java/com/pooli/traffic/service/runtime/TrafficRedisKeyFactory.javasrc/main/java/com/pooli/traffic/service/runtime/TrafficRemainingBalanceCacheService.javasrc/main/resources/application.yamlsrc/main/resources/db/migration/V2605250300__split_daily_app_total_usage_data.sqlsrc/main/resources/db/migration/V2605250400__create_line_daily_batch_job.sqlsrc/main/resources/db/migration/V2605250410__create_line_daily_batch_target.sqlsrc/main/resources/db/migration/V2605260100__drop_line_daily_batch_job_updated_at.sqlsrc/main/resources/lua/traffic/deduct_unified.luasrc/main/resources/mapper/data/DataMapper.xmlsrc/main/resources/mapper/traffic/LineDailyBatchJobMapper.xmlsrc/main/resources/mapper/traffic/LineDailyBatchTargetMapper.xmlsrc/main/resources/mapper/traffic/TrafficDailyUsageBatchMapper.xmlsrc/test/java/com/pooli/data/service/impl/DataServiceImplTest.javasrc/test/java/com/pooli/family/service/FamilySharedPoolsServiceTest.javasrc/test/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetricsTest.javasrc/test/java/com/pooli/traffic/acceptance/TrafficAcceptanceTestSupport.javasrc/test/java/com/pooli/traffic/acceptance/TrafficDataPolicyAcceptanceTest.javasrc/test/java/com/pooli/traffic/acceptance/TrafficDataUsageAcceptanceTest.javasrc/test/java/com/pooli/traffic/config/TrafficSchedulingConfigTest.javasrc/test/java/com/pooli/traffic/controller/AdminTrafficBatchControllerTest.javasrc/test/java/com/pooli/traffic/mapper/LineDailyBatchJobMapperSqlContractTest.javasrc/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.javasrc/test/java/com/pooli/traffic/mapper/TrafficDailyUsageBatchMapperSqlContractTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyBatchJobServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyBatchManagerSchedulerTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyBatchManagerServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyBatchTargetClaimServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyBatchWorkerSchedulerTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyUsageRedisReaderTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyUsageSyncPersistenceServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyUsageSyncRerunServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyUsageSyncResumeServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyUsageSyncWorkerServiceTest.javasrc/test/java/com/pooli/traffic/service/decision/TrafficDeductLuaExecutorTest.javasrc/test/java/com/pooli/traffic/service/runtime/TrafficLuaPolicyContractTest.javasrc/test/java/com/pooli/traffic/service/runtime/TrafficRedisKeyFactoryTest.javasrc/test/java/com/pooli/traffic/service/runtime/TrafficRemainingBalanceCacheServiceTest.java
| ```mermaid | ||
| sequenceDiagram | ||
| autonumber | ||
| participant Scheduler as 모든 서버 | ||
| participant Redis as Redis | ||
| participant Manager as Manager (1대) | ||
| participant Worker as Worker (나머지 + Manager) | ||
| participant DB as MySQL | ||
|
|
||
| Scheduler->>Redis: 03:00 KST Manager 락 획득 시도 | ||
| Redis-->>Manager: 락 획득 성공 (Manager 선출) | ||
| Redis-->>Worker: 락 획득 실패 (Worker 대기) | ||
|
|
||
| rect rgb(240, 248, 255) | ||
| note right of Manager: 1. Target Insert 절차 (대상 준비) | ||
| Manager->>DB: 메타데이터 생성 및 Target Row 일괄 적재 | ||
| Manager->>DB: 동기화 배치 상태 "RUNNING" 전환 | ||
| end | ||
|
|
||
| Manager-->>Worker: Manager도 세팅 완료 후 Worker로 합류 | ||
|
|
||
| rect rgb(240, 255, 240) | ||
| note right of Worker: 2. Usage Sync 절차 (다중 워커 병렬 동기화) | ||
| loop 잔여 타겟 소진 시까지 반복 | ||
| Worker->>DB: 100건 할당/선점 (SKIP LOCKED) | ||
| Worker->>Redis: 대상 유저의 사용량 스냅샷 조회 | ||
| Worker->>DB: [단일 트랜잭션] 사용량 적재 & 상태 완료 갱신 | ||
| end | ||
| end | ||
|
|
||
| Worker->>DB: 처리할 타겟이 없으면 최종 "COMPLETED" 갱신 | ||
| ``` |
There was a problem hiding this comment.
Mermaid 블록에 공통 init directive를 추가해 렌더링 일관성을 맞춰주세요.
여러 Mermaid 다이어그램에 %%{init: {"theme": "default", "themeVariables": {"background": "#ffffff"}}}%%가 빠져 있어 환경별 배경/테마가 달라질 수 있습니다.
Based on learnings "All Mermaid diagrams must include the init directive %%{init: {"theme": "default", "themeVariables": {"background": "#ffffff"}}}%% at the top to render with a white background".
Also applies to: 53-83, 91-124, 131-163, 173-203, 215-247
🤖 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 `@docs/일별` 동기화 배치 요약.md around lines 5 - 36, Add the standard Mermaid init
directive line %%{init: {"theme": "default", "themeVariables": {"background":
"`#ffffff`"}}}%% at the very top of every Mermaid code block (the blocks that
start with ```mermaid and contain sequenceDiagram/other diagrams) so they render
with a consistent white background; update the existing sequenceDiagram block
(the one with participants Scheduler/Redis/Manager/Worker/DB) and repeat the
same change for the other Mermaid blocks referenced in the comment (the blocks
currently at the later sections of the document) to ensure consistent rendering
across all diagrams.
| Worker->>DB: 처리할 타겟이 없으면 최종 "COMPLETED" 갱신 | ||
| ``` | ||
|
|
||
| ## 1. 동기화 배치 절차 구성 요소 |
There was a problem hiding this comment.
헤더 전후 공백줄(MD022) 경고를 정리해 주세요.
현재 다수 헤더에서 빈 줄 규칙이 깨져 markdownlint 경고가 발생합니다. 문서 린트가 파이프라인에 포함되어 있으면 병합 저해 요인이 됩니다.
Also applies to: 45-45, 85-86, 126-126, 165-166, 205-205, 249-249, 254-254, 262-262, 267-267
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 38-38: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 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 `@docs/일별` 동기화 배치 요약.md at line 38, Multiple Markdown headers (e.g., the header
"## 1. 동기화 배치 절차 구성 요소" and other headers at the referenced locations) violate
markdownlint MD022 by missing the required blank line before or after; fix each
header by ensuring there is exactly one blank line both immediately above and
immediately below each header line (or at least one blank line where
appropriate) so headers are separated from surrounding content, then re-run
markdownlint to confirm warnings at the listed header locations (45, 85-86, 126,
165-166, 205, 249, 254, 262, 267) are resolved.
| ``` | ||
| Spring 앱 (Micrometer Gauge) | ||
| → Prometheus (metric scraping) | ||
| → AlertManager (Docker 컨테이너) | ||
| → Discord 웹훅 | ||
| ``` |
There was a problem hiding this comment.
코드 블록 언어 지정/공백 규칙을 일관되게 맞춰 주세요.
여러 fenced block에서 언어 미지정(MD040)과 fence 주변 공백(MD031) 경고가 확인됩니다. text, bash, sql 등 의도를 나타내는 language를 명시하고, Line 139~Line 141처럼 fence 전후 빈 줄을 추가해 문서 lint 안정성을 확보해 주세요.
Also applies to: 76-78, 86-88, 114-133, 139-141, 184-187
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 41-41: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 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 `@docs/일별` 사용량 동기화 배치 가이드.md around lines 41 - 46, Several fenced code blocks
lack a language tag and empty-line padding which triggers MD040/MD031 lint
warnings; update each triple-backtick block (e.g., the block containing "Spring
앱 (Micrometer Gauge) → Prometheus (metric scraping) → AlertManager (Docker 컨테이너)
→ Discord 웹훅" and the other fenced sections referenced) to include an explicit
language token such as text, bash, or sql as appropriate, and ensure there is a
blank line before and after each fence (add a blank line above the opening ```
and below the closing ```), so all fenced blocks are consistently annotated and
have surrounding whitespace to satisfy the linter.
| ````markdown | ||
| ```mermaid | ||
| %%{init: {"theme": "default", "themeVariables": {"background": "#ffffff"}}}%% | ||
| ... diagram code ... | ||
| ``` | ||
|
|
||
| </div> | ||
| ``` | ||
|
|
||
| **Critical line-break rules** (to prevent markdown parsing errors): | ||
| - There MUST be at least **one empty line** immediately after the opening `<div>` tag. | ||
| - There MUST be at least **one empty line** immediately before the closing `</div>` tag. | ||
| - Violating this rule will cause the Mermaid block to render as plain text. | ||
| ```` |
There was a problem hiding this comment.
펜스 코드 블록 주변 공백 규칙을 맞춰 주세요.
Line 218~Line 223의 중첩 fenced block은 앞뒤 빈 줄이 없어 MD031 경고가 발생합니다. 문서 lint 파이프라인 안정성을 위해 fenced block 전후에 빈 줄을 추가해 주세요.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 218-218: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 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 `@GEMINI.md` around lines 218 - 223, The nested fenced block that starts with
the outer ````markdown```` wrapper and contains the inner ```mermaid ... ```
block is missing blank lines before and after, causing MD031; update the
document so there is an empty line immediately before the outer fenced block and
an empty line immediately after it (i.e., add a blank line above the
````markdown```` opening fence and below the closing fence) to satisfy the lint
rule.
| for app_id, individual_usage, shared_app_usage, qos_usage in zip( | ||
| chosen_apps, | ||
| individual_app_usage_parts, | ||
| shared_app_usage_parts, | ||
| qos_app_usage_parts, | ||
| ): |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 1) Ruff 설정에서 B905 적용 여부 확인
rg -n "B905|\\[tool\\.ruff|select\\s*=|ignore\\s*=" .
# 2) 대상 zip 사용 위치 확인
rg -n -C2 "zip\\(" scripts/test-data/generate_test_data_csv.pyRepository: Iwantcod/pooli-be
Length of output: 554
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) ruff.toml에서 B905가 select/ignore에 어떻게 포함되는지 확인
python3 - <<'PY'
import pathlib, re
p = pathlib.Path("ruff.toml")
txt = p.read_text(encoding="utf-8")
for m in re.finditer(r'^\s*select\s*=\s*\[', txt, re.M):
start = max(0, txt.rfind("\n", 0, m.start()))
print(txt.splitlines()[0:120])
PY
rg -n "B905" ruff.toml || true
rg -n "select\s*=" ruff.toml || true
rg -n "ignore\s*=" ruff.toml || true
# 2) 문제의 zip 직전/직후에서 변수들이 어떻게 만들어지는지 확인
FILE="scripts/test-data/generate_test_data_csv.py"
sed -n '1370,1435p' "$FILE" | nl -ba -w2 -s':'Repository: Iwantcod/pooli-be
Length of output: 3168
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="scripts/test-data/generate_test_data_csv.py"
# 1) 해당 zip 입력들이 어디서 만들어지는지(길이 보장/가정이 있는지) 추적
for v in individual_app_usage_parts shared_app_usage_parts qos_app_usage_parts chosen_apps; do
echo "---- $v (assignments) ----"
rg -n "^[[:space:]]*$v[[:space:]]*=" "$FILE" || true
echo "---- $v (usage near zip) ----"
rg -n "$v" "$FILE" | head -n 40
done
# 2) zip 주변 더 넓게 문맥 확인(라인 번호는 awk로 표시)
python3 - <<'PY'
from pathlib import Path
p=Path("scripts/test-data/generate_test_data_csv.py")
lines=p.read_text(encoding="utf-8").splitlines()
start=1375
end=1425
for i in range(start, end+1):
if i-1 < 0 or i-1 >= len(lines): continue
print(f"{i:4d}: {lines[i-1]}")
PYRepository: Iwantcod/pooli-be
Length of output: 3647
🌐 Web query:
Ruff B905 zip() strict keyword explanation and rationale
💡 Result:
Ruff rule B905 enforces the use of the strict keyword argument in Python's built-in zip function [1]. Rationale and Explanation The B905 rule is derived from flake8-bugbear and addresses a common source of silent logic errors in Python [1]. By default, the built-in zip function truncates its output to the length of the shortest input iterable [1][2]. If iterables are expected to be the same length, this behavior can mask bugs where data is missing or mismatched, often resulting in silent failures that are difficult to diagnose [3][4]. To mitigate this, Python 3.10 introduced the strict=True keyword argument (via PEP 618), which causes zip to raise a ValueError if it encounters iterables of different lengths [3][2]. The B905 rule mandates that developers explicitly define the strictness of their zip operations [1]. - If your iterables are intended to be of equal length, you should use strict=True [1][2]. - If you intend to work with iterables of different lengths, you should use strict=False to make that design choice explicit [1]. Implementation Details The rule B905 applies when zip is called with two or more iterables or any starred argument [1]. Because this rule relies on features introduced in Python 3.10, Ruff typically gates it based on the project's configured target Python version [5][6]. If you are targeting older versions of Python that do not support strict, you may need to adjust your Ruff configuration (e.g., target-version or requires-python) to prevent the rule from being incorrectly enforced [5][6]. Note that zip is lazy, meaning the length-checking (and any resulting ValueError) occurs only when the zip object is actually iterated over, not when it is initially created [2][7].
Citations:
- 1: https://docs.astral.sh/ruff/rules/zip-without-explicit-strict/
- 2: https://realpython.com/python-zip-function/
- 3: https://peps.python.org/pep-0618/
- 4: python/cpython@59cf853
- 5: B905 incorrectly raised on older python versions astral-sh/ruff#6418
- 6: B905 not being raised when "B" rules enabled astral-sh/ruff#16541
- 7: https://stackoverflow.com/questions/78549723/why-is-pythons-zip-strictness-only-enforced-when-the-object-is-used-not-create
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Ruff가 B905를 실제로 적용하는지(타겟 파이썬 버전/규칙 범위) 확인
echo "---- ruff.toml (relevant) ----"
rg -n "target-version|requires-python|python-version|line-length|lint\.select|lint\.ignore|select|ignore" ruff.toml
# 2) split_integer_total가 app_count 길이를 보장하는지 확인
FILE="scripts/test-data/generate_test_data_csv.py"
rg -n "def split_integer_total\\b" "$FILE"
python3 - <<'PY'
from pathlib import Path
p=Path("scripts/test-data/generate_test_data_csv.py")
lines=p.read_text(encoding="utf-8").splitlines()
# def split_integer_total 있는 줄 찾기
defline=None
for i,l in enumerate(lines, start=1):
if l.startswith("def split_integer_total"):
defline=i; break
if not defline:
print("split_integer_total not found"); raise SystemExit(0)
start=defline-5; end=defline+80
for i in range(start, end+1):
if 1<=i<=len(lines):
print(f"{i:4d}: {lines[i-1]}")
PY
# 3) split_integer_total 호출부 인자들이 의도대로 길이 일치하는지(반환값 형태) 보강 확인
rg -n "split_integer_total\\(" "$FILE"Repository: Iwantcod/pooli-be
Length of output: 3969
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# ruff config에서 target-version/파이썬 버전 지정 여부 확인
rg -n "target-version|requires-python|python-version" -S ruff.toml pyproject.toml setup.cfg . || true
# app_ids 정의 및 길이 제약 확인
FILE="scripts/test-data/generate_test_data_csv.py"
rg -n "app_ids\s*=|rng\.sample\(app_ids" "$FILE"
python3 - <<'PY'
from pathlib import Path
p=Path("scripts/test-data/generate_test_data_csv.py")
lines=p.read_text(encoding="utf-8").splitlines()
# zip 근처에서 app_ids가 어디서 오나 역추적용으로 앞부분(대략 1350~1398)만 출력
for i in range(1320, 1405):
if 1<=i<=len(lines):
print(f"{i:4d}: {lines[i-1]}")
PY
# split_integer_total 호출에서 total/shared 값 음수 가능성만 간단 체크(관련 정의 근처만 출력)
python3 - <<'PY'
from pathlib import Path
p=Path("scripts/test-data/generate_test_data_csv.py")
lines=p.read_text(encoding="utf-8").splitlines()
for i in range(1280, 1385):
if 1<=i<=len(lines):
if any(k in lines[i-1] for k in ["total_usage", "shared_usage", "shared_usage", "total_usage"]):
print(f"{i:4d}: {lines[i-1]}")
PYRepository: Iwantcod/pooli-be
Length of output: 4864
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Python 타겟/요구 버전 관련 설정 전역 탐색
rg -n --hidden --no-ignore-vcs "target-version|requires-python|python-version|python_version" . || true
# 혹시 pyproject에만 있을 수도 있어 파일 존재 여부도 확인
ls -la | head
ls -la pyproject.toml 2>/dev/null || true
ls -la ruff.toml 2>/dev/null || true
ls -la .python-version 2>/dev/null || trueRepository: Iwantcod/pooli-be
Length of output: 675
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "ruff.toml lines:"
wc -l ruff.toml
echo "---- ruff.toml (head) ----"
nl -ba ruff.toml | head -n 120
echo "---- ruff.toml (tail) ----"
nl -ba ruff.toml | tail -n 120Repository: Iwantcod/pooli-be
Length of output: 179
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p=Path("ruff.toml")
lines=p.read_text(encoding="utf-8").splitlines()
for i,l in enumerate(lines, start=1):
print(f"{i:3d}: {l}")
PYRepository: Iwantcod/pooli-be
Length of output: 2905
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ruff check scripts/test-data/generate_test_data_csv.pyRepository: Iwantcod/pooli-be
Length of output: 39007
zip()에 strict=를 명시해 의도(길이 동일)를 코드에 고정해 주세요.
Ruff B905가 실제로 해당 zip()에 대해 발생합니다. 현재는 app_count를 기준으로 리스트 길이를 맞추고 있어 바로 데이터 누락이 생길 가능성은 낮지만, strict=True를 명시하면 이후 refactor로 길이 불일치가 들어올 때 조용히 잘려서 DAILY_APP_TOTAL_DATA의 행 수 검증(C03)을 깨는 상황을 막을 수 있습니다.
- 위치:
scripts/test-data/generate_test_data_csv.py(1397-1402) - 변경:
zip(..., strict=True)로 수정
🧰 Tools
🪛 Ruff (0.15.14)
[warning] 1397-1402: zip() without an explicit strict= parameter
Add explicit value for parameter strict=
(B905)
🤖 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 `@scripts/test-data/generate_test_data_csv.py` around lines 1397 - 1402, The
zip over chosen_apps, individual_app_usage_parts, shared_app_usage_parts, and
qos_app_usage_parts should be made strict to enforce equal lengths; update the
zip call in the loop that iterates "for app_id, individual_usage,
shared_app_usage, qos_usage in zip(...)" to pass strict=True (i.e.,
zip(chosen_apps, individual_app_usage_parts, shared_app_usage_parts,
qos_app_usage_parts, strict=True)) so mismatched list lengths raise immediately
instead of silently truncating.
…e sync batch'를 진행하도록 로직 변경
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/test/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetricsTest.java (1)
29-73: 🧹 Nitpick | 🔵 Trivial | 🏗️ Heavy lift테스트 간 중복 코드 제거 및 경계값 테스트 보강 권장
현재 두 테스트 메서드에서
SimpleMeterRegistry,LineDailyBatchJobServicemock,Clock설정이 중복됩니다. 또한2.0,3.0,1800.0같은 매직 넘버가 하드코딩되어 있어 테스트 의도 파악이 어렵습니다.개선 제안:
@BeforeEach에서 공통 fixture 설정- 매직 넘버를 상수로 추출 (예:
EXPECTED_FAILED_COUNT,RUNNING_STATUS_CODE,EXPECTED_DURATION_SECONDS)- 경계값 테스트 추가:
findLatestUsageSyncBatch()가null반환 시failedCount가0일 때runStartedAt이null일 때 (non-RUNNING 상태)♻️ 테스트 구조 개선 예시
class LineDailyUsageSyncBatchMetricsTest { private static final ZoneId KST = ZoneId.of("Asia/Seoul"); private static final LocalDate USAGE_DATE = LocalDate.of(2026, 5, 25); private static final long EXPECTED_FAILED_COUNT = 2L; private static final double RUNNING_STATUS_CODE = 2.0; private static final double COMPLETED_STATUS_CODE = 3.0; private static final double EXPECTED_DURATION_SECONDS = 1800.0; private SimpleMeterRegistry meterRegistry; private LineDailyBatchJobService batchJobService; private Clock clock; `@BeforeEach` void setUp() { meterRegistry = new SimpleMeterRegistry(); batchJobService = org.mockito.Mockito.mock(LineDailyBatchJobService.class); clock = Clock.fixed(Instant.parse("2026-05-25T21:30:00Z"), KST); } `@Test` `@DisplayName`("RUNNING 상태 배치의 failed_count와 경과 시간을 gauge로 노출한다") void exposesRunningBatchMetrics() { // given when(batchJobService.findLatestUsageSyncBatch()).thenReturn( createBatchJob(LineDailyBatchStatus.RUNNING, EXPECTED_FAILED_COUNT, LocalDateTime.of(2026, 5, 26, 6, 0))); // when LineDailyUsageSyncBatchMetrics metrics = new LineDailyUsageSyncBatchMetrics(meterRegistry, batchJobService, clock); metrics.init(); // then assertThat(meterRegistry.get("batch_daily_usage_sync_failed_count").gauge().value()) .isEqualTo((double) EXPECTED_FAILED_COUNT); assertThat(meterRegistry.get("batch_daily_usage_sync_status").gauge().value()) .isEqualTo(RUNNING_STATUS_CODE); assertThat(meterRegistry.get("batch_daily_usage_sync_run_duration_seconds").gauge().value()) .isEqualTo(EXPECTED_DURATION_SECONDS); } private LineDailyBatchJob createBatchJob(LineDailyBatchStatus status, long failedCount, LocalDateTime runStartedAt) { return LineDailyBatchJob.builder() .id(10L) .batchName(BatchName.LINE_DAILY_USAGE_SYNC_BATCH) .usageDate(USAGE_DATE) .status(status) .runStartedAt(runStartedAt) .failedCount(failedCount) .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/test/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetricsTest.java` around lines 29 - 73, The two tests in LineDailyUsageSyncBatchMetricsTest duplicate fixture setup and hardcode magic numbers; move common setup (SimpleMeterRegistry, mocked LineDailyBatchJobService, Clock) into a `@BeforeEach` and extract magic numbers into constants (e.g. EXPECTED_FAILED_COUNT, RUNNING_STATUS_CODE, COMPLETED_STATUS_CODE, EXPECTED_DURATION_SECONDS), add a private helper createBatchJob(...) to build LineDailyBatchJob instances, and update exposesRunningBatchMetrics and preservesFailedCountAndResetsRunDurationWhenLatestBatchIsNotRunning to use those constants and the helper; also add boundary tests covering when batchJobService.findLatestUsageSyncBatch() returns null, when failedCount == 0, and when runStartedAt == null to assert expected gauges from LineDailyUsageSyncBatchMetrics.src/main/java/com/pooli/traffic/service/batch/LineDailyBatchWorkerScheduler.java (1)
41-43:⚠️ Potential issue | 🟠 Major | ⚡ Quick win동일 usageDate 중복 스케줄 루프를 차단해 주세요.
Line 41에서
startForUsageDate가 호출될 때마다 새 루프가 생성되어, 같은usageDate에 대해runStartCheckCycle이 병렬로 중복 실행될 수 있습니다. 트래픽 도메인 핵심 경로라 DB claim 폴링/worker 실행이 불필요하게 증폭될 수 있습니다.개선 예시 (usageDate 단위 단일 루프 보장)
+import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; ... public class LineDailyBatchWorkerScheduler { + private final ConcurrentMap<LocalDate, Boolean> scheduledUsageDates = new ConcurrentHashMap<>(); ... public void startForUsageDate(LocalDate usageDate) { - scheduleNextCheck(usageDate, 0L); + if (scheduledUsageDates.putIfAbsent(usageDate, Boolean.TRUE) == null) { + scheduleNextCheck(usageDate, 0L); + } } ... void runStartCheckCycle(LocalDate usageDate) { try { if (stopped) { + scheduledUsageDates.remove(usageDate); return; } ... if (runningBatch == null) { scheduleNextCheck(usageDate, START_CHECK_DELAY_MS + nextJitterMs()); return; } ... } catch (Throwable t) { log.error("Failed to execute worker cycle for usageDate: {}. Retrying after delay.", usageDate, t); scheduleNextCheck(usageDate, START_CHECK_DELAY_MS + nextJitterMs()); } }As per coding guidelines
src/main/java/com/pooli/traffic/**/*.java: "동시성(concurrency) 이슈가 발생할 수 있는 구간"을 추가 확인해야 합니다.Also applies to: 60-85
🤖 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/pooli/traffic/service/batch/LineDailyBatchWorkerScheduler.java` around lines 41 - 43, Avoid creating duplicate scheduling loops for the same usageDate: add a thread-safe registry (e.g., a ConcurrentHashMap or ConcurrentSkipListSet) of active usageDates in LineDailyBatchWorkerScheduler and check it in startForUsageDate before calling scheduleNextCheck; if the date is already present, return early. When the runStartCheckCycle completes or the loop is cancelled/terminated, remove the usageDate from the registry to allow future restarts. Ensure all accesses to the registry are thread-safe and reference the existing methods scheduleNextCheck(...) and runStartCheckCycle(...) so the guard integrates with current lifecycle and cleanup logic.
♻️ Duplicate comments (1)
docs/일별 사용량 동기화 배치 가이드.md (1)
41-46:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFenced code block 언어 태그를 명시해 문서 린트를 안정화해 주세요.
Line 41, 76, 86, 114, 139, 184 구간의 코드 블록에
text/sql/bash같은 언어 태그가 없어 MD040 경고가 재발할 수 있습니다. 블록 전후 빈 줄도 함께 맞춰 주세요.Also applies to: 76-78, 86-88, 114-133, 139-141, 184-187
🤖 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 `@docs/일별` 사용량 동기화 배치 가이드.md around lines 41 - 46, Update the fenced code blocks in the document so each triple-backtick block includes an appropriate language tag (e.g., text, sql, bash) and ensure there's a blank line before and after each fenced block to satisfy MD040; specifically update the blocks that show the flow "Spring 앱 (Micrometer Gauge) → Prometheus (metric scraping) → AlertManager (Docker 컨테이너) → Discord 웹훅" and the other fenced blocks containing SQL snippets and bash commands so they have correct language identifiers and surrounding blank lines.
🤖 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/pooli/traffic/service/batch/LineDailyUsageReadResult.java`:
- Around line 25-27: LineDailyUsageReadResult의 compact 생성자에서 appUsages를 그대로 할당하면
외부에서 리스트를 변경해 불변성이 깨지므로, appUsages 필드에 방어적 복사를 적용하세요: compact 생성자(구현부)에서
appUsages가 null이면 List.of()로 대체한 뒤 List.copyOf(...)로 복사하여 할당하도록 변경하여 호출자 변경으로부터
안전하게 만드세요; 관련 심볼: record LineDailyUsageReadResult, 파라미터/필드 appUsages, compact
constructor 내 할당 로직.
In `@src/test/java/com/pooli/traffic/config/TrafficSchedulingConfigTest.java`:
- Around line 23-33: Replace the FileSystemResource-based loading with a
ClassPathResource to avoid working-directory issues: in the test where
YamlPropertySourceLoader loader is used and you call loader.load("application",
new FileSystemResource(APPLICATION_YAML)), change to loader.load("application",
new ClassPathResource(APPLICATION_YAML)) so the YAML is resolved from the
classpath; also change the exception handling in the try/catch that catches
IOException (around YamlPropertySourceLoader loader / StandardEnvironment env /
propertySources.forEach(...)) to rethrow an AssertionError (or fail the test)
with the caught exception as the cause instead of wrapping it in
UncheckedIOException so test failures are reported as test assertion failures.
In
`@src/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java`:
- Around line 79-82: In LineDailyBatchTargetMapperSqlContractTest, the SQL block
extraction logic uses sql.indexOf(...) to compute start (e.g., for "<select
id=\"countNonTerminalByUsageDate\"") but lacks a start == -1 guard causing
StringIndexOutOfBoundsException; immediately after computing start add a check
like if (start == -1) fail("start tag not found: <select
id=\"countNonTerminalByUsageDate\"") (use a message that includes the searched
token) and refactor the repeated extraction logic into a small helper method
(e.g., extractBlock(String sql, String startToken, String endToken, String
failMsg)) and update all usages in LineDailyBatchTargetMapperSqlContractTest to
call that helper instead of duplicating indexOf/substring logic.
---
Outside diff comments:
In
`@src/main/java/com/pooli/traffic/service/batch/LineDailyBatchWorkerScheduler.java`:
- Around line 41-43: Avoid creating duplicate scheduling loops for the same
usageDate: add a thread-safe registry (e.g., a ConcurrentHashMap or
ConcurrentSkipListSet) of active usageDates in LineDailyBatchWorkerScheduler and
check it in startForUsageDate before calling scheduleNextCheck; if the date is
already present, return early. When the runStartCheckCycle completes or the loop
is cancelled/terminated, remove the usageDate from the registry to allow future
restarts. Ensure all accesses to the registry are thread-safe and reference the
existing methods scheduleNextCheck(...) and runStartCheckCycle(...) so the guard
integrates with current lifecycle and cleanup logic.
In
`@src/test/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetricsTest.java`:
- Around line 29-73: The two tests in LineDailyUsageSyncBatchMetricsTest
duplicate fixture setup and hardcode magic numbers; move common setup
(SimpleMeterRegistry, mocked LineDailyBatchJobService, Clock) into a `@BeforeEach`
and extract magic numbers into constants (e.g. EXPECTED_FAILED_COUNT,
RUNNING_STATUS_CODE, COMPLETED_STATUS_CODE, EXPECTED_DURATION_SECONDS), add a
private helper createBatchJob(...) to build LineDailyBatchJob instances, and
update exposesRunningBatchMetrics and
preservesFailedCountAndResetsRunDurationWhenLatestBatchIsNotRunning to use those
constants and the helper; also add boundary tests covering when
batchJobService.findLatestUsageSyncBatch() returns null, when failedCount == 0,
and when runStartedAt == null to assert expected gauges from
LineDailyUsageSyncBatchMetrics.
---
Duplicate comments:
In `@docs/일별` 사용량 동기화 배치 가이드.md:
- Around line 41-46: Update the fenced code blocks in the document so each
triple-backtick block includes an appropriate language tag (e.g., text, sql,
bash) and ensure there's a blank line before and after each fenced block to
satisfy MD040; specifically update the blocks that show the flow "Spring 앱
(Micrometer Gauge) → Prometheus (metric scraping) → AlertManager (Docker 컨테이너) →
Discord 웹훅" and the other fenced blocks containing SQL snippets and bash
commands so they have correct language identifiers and surrounding blank lines.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: b6186259-d30d-47ac-9089-d5b4a943cd8e
📒 Files selected for processing (22)
README.mddocs/일별 사용량 동기화 배치 가이드.mdsrc/main/java/com/pooli/data/service/impl/DataServiceImpl.javasrc/main/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetrics.javasrc/main/java/com/pooli/traffic/domain/batch/LineDailyBatchJobCreateResult.javasrc/main/java/com/pooli/traffic/domain/dto/response/LineDailyUsageSyncResumeResDto.javasrc/main/java/com/pooli/traffic/mapper/DailyAppUsageBatchInsertRow.javasrc/main/java/com/pooli/traffic/service/batch/DailyAppUsage.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchManagerService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchTargetClaimService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyBatchWorkerScheduler.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageReadResult.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncPersistenceService.javasrc/main/java/com/pooli/traffic/service/batch/LineDailyUsageSyncWorkerService.javasrc/main/java/com/pooli/traffic/service/decision/TrafficDeductOrchestratorService.javasrc/main/resources/application.yamlsrc/test/java/com/pooli/monitoring/metrics/LineDailyUsageSyncBatchMetricsTest.javasrc/test/java/com/pooli/traffic/config/TrafficSchedulingConfigTest.javasrc/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyBatchManagerServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyBatchTargetClaimServiceTest.javasrc/test/java/com/pooli/traffic/service/batch/LineDailyUsageSyncWorkerServiceTest.java
| LineDailyUsageReadResult { | ||
| appUsages = (appUsages != null) ? appUsages : List.of(); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
compact constructor에서 방어적 복사 누락
appUsages를 그대로 할당하면 호출자가 외부에서 리스트를 변경할 수 있어 불변성이 깨질 수 있습니다. record는 불변 데이터 컨테이너로 설계되므로, 가변 컬렉션을 받을 때는 방어적 복사를 권장합니다.
♻️ 불변성 보장을 위한 방어적 복사 제안
LineDailyUsageReadResult {
- appUsages = (appUsages != null) ? appUsages : List.of();
+ appUsages = (appUsages != null) ? List.copyOf(appUsages) : List.of();
}List.copyOf()는 얕은 복사본을 만들어 외부 수정으로부터 안전하게 보호하며, 이미 불변 리스트라면 그대로 반환해 오버헤드가 최소화됩니다.
🤖 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/pooli/traffic/service/batch/LineDailyUsageReadResult.java`
around lines 25 - 27, LineDailyUsageReadResult의 compact 생성자에서 appUsages를 그대로
할당하면 외부에서 리스트를 변경해 불변성이 깨지므로, appUsages 필드에 방어적 복사를 적용하세요: compact 생성자(구현부)에서
appUsages가 null이면 List.of()로 대체한 뒤 List.copyOf(...)로 복사하여 할당하도록 변경하여 호출자 변경으로부터
안전하게 만드세요; 관련 심볼: record LineDailyUsageReadResult, 파라미터/필드 appUsages, compact
constructor 내 할당 로직.
| YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); | ||
| try { | ||
| List<PropertySource<?>> propertySources = loader.load("application", new FileSystemResource(APPLICATION_YAML)); | ||
| StandardEnvironment env = new StandardEnvironment(); | ||
| propertySources.forEach(env.getPropertySources()::addFirst); | ||
|
|
||
| int poolSize = env.getProperty("spring.task.scheduling.pool.size", Integer.class, -1); | ||
| assertEquals(2, poolSize); | ||
| } catch (IOException e) { | ||
| throw new UncheckedIOException(e); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
FileSystemResource 대신 ClassPathResource 사용 권장
현재 FileSystemResource("src/main/resources/application.yaml")는 상대 경로에 의존하므로, 테스트 실행 디렉토리에 따라 파일을 찾지 못할 수 있습니다. IDE와 Gradle에서 working directory가 다를 수 있어 안정성이 떨어집니다.
♻️ ClassPathResource로 개선
class TrafficSchedulingConfigTest {
- private static final String APPLICATION_YAML = "src/main/resources/application.yaml";
-
`@Test`
`@DisplayName`("Spring TaskScheduler pool size는 2로 설정한다")
void taskSchedulerPoolSizeIsTwo() {
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
try {
- List<PropertySource<?>> propertySources = loader.load("application", new FileSystemResource(APPLICATION_YAML));
+ List<PropertySource<?>> propertySources = loader.load("application",
+ new ClassPathResource("application.yaml"));
StandardEnvironment env = new StandardEnvironment();
propertySources.forEach(env.getPropertySources()::addFirst);
int poolSize = env.getProperty("spring.task.scheduling.pool.size", Integer.class, -1);
assertEquals(2, poolSize);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
+ } catch (IOException e) {
+ throw new AssertionError("Failed to load application.yaml", e);
}
}
}ClassPathResource는 클래스패스 기준이므로 실행 위치와 무관하게 안정적이며, AssertionError로 래핑하면 테스트 실패 의도가 더 명확해집니다.
🤖 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/pooli/traffic/config/TrafficSchedulingConfigTest.java`
around lines 23 - 33, Replace the FileSystemResource-based loading with a
ClassPathResource to avoid working-directory issues: in the test where
YamlPropertySourceLoader loader is used and you call loader.load("application",
new FileSystemResource(APPLICATION_YAML)), change to loader.load("application",
new ClassPathResource(APPLICATION_YAML)) so the YAML is resolved from the
classpath; also change the exception handling in the try/catch that catches
IOException (around YamlPropertySourceLoader loader / StandardEnvironment env /
propertySources.forEach(...)) to rethrow an AssertionError (or fail the test)
with the caught exception as the cause instead of wrapping it in
UncheckedIOException so test failures are reported as test assertion failures.
| int start = sql.indexOf("<select id=\"countNonTerminalByUsageDate\""); | ||
| int end = sql.indexOf("</select>", start); | ||
| if (end == -1) fail("</select> not found"); | ||
| String selectSql = sql.substring(start, end + "</select>".length()); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java"
echo "== File =="
wc -l "$FILE"
echo
echo "== Lines 60-120 =="
nl -ba "$FILE" | sed -n '60,120p'
echo
echo "== Lines 120-220 =="
nl -ba "$FILE" | sed -n '120,220p'
echo
echo "== Lines 220-280 =="
nl -ba "$FILE" | sed -n '220,280p'Repository: Iwantcod/pooli-be
Length of output: 243
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java"
echo "== File =="
wc -l "$FILE"
echo
echo "== Lines 60-120 =="
nl -ba "$FILE" | sed -n '60,120p'
echo
echo "== Lines 120-220 =="
nl -ba "$FILE" | sed -n '120,220p'
echo
echo "== Lines 220-280 =="
nl -ba "$FILE" | sed -n '220,280p'Repository: Iwantcod/pooli-be
Length of output: 243
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java"
echo "== File =="
wc -l "$FILE"
echo
echo "== Lines 60-120 =="
nl -ba "$FILE" | sed -n '60,120p'
echo
echo "== Lines 120-220 =="
nl -ba "$FILE" | sed -n '120,220p'
echo
echo "== Lines 220-280 =="
nl -ba "$FILE" | sed -n '220,280p'Repository: Iwantcod/pooli-be
Length of output: 243
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java"
echo "== File =="
wc -l "$FILE"
echo
echo "== Lines 60-120 =="
nl -ba "$FILE" | sed -n '60,120p'
echo
echo "== Lines 120-220 =="
nl -ba "$FILE" | sed -n '120,220p'
echo
echo "== Lines 220-280 =="
nl -ba "$FILE" | sed -n '220,280p'Repository: Iwantcod/pooli-be
Length of output: 243
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/test/java/com/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java"
echo "== File =="
wc -l "$FILE"
echo
echo "== Lines 60-120 =="
cat -n "$FILE" | sed -n '60,120p'
echo
echo "== Lines 120-220 =="
cat -n "$FILE" | sed -n '120,220p'
echo
echo "== Lines 220-268 =="
cat -n "$FILE" | sed -n '220,268p'Repository: Iwantcod/pooli-be
Length of output: 11488
SQL 블록 추출 로직에 start == -1 실패 가드 추가 필요
현재 여러 테스트에서 sql.indexOf("<select|<insert|<update> id=...")의 start 값에 대해 start == -1 체크가 없어, 매칭이 없을 때 substring(start, ...)로 인해 StringIndexOutOfBoundsException이 나며 어느 계약(블록)이 깨졌는지 진단이 흐려집니다(예: 79-82, 96-99, 134-137, 150-153, 169-172, 186-189, 201-204, 220-223, 237-240).
start계산 직후if (start == -1) fail("start tag not found: ...");을 추가하세요.- 반복되는 추출 코드는 작은 헬퍼(예:
extractBlock(sql, startToken, endToken, failMsg))로 묶어 유지보수 비용을 줄이세요.
🤖 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/pooli/traffic/mapper/LineDailyBatchTargetMapperSqlContractTest.java`
around lines 79 - 82, In LineDailyBatchTargetMapperSqlContractTest, the SQL
block extraction logic uses sql.indexOf(...) to compute start (e.g., for
"<select id=\"countNonTerminalByUsageDate\"") but lacks a start == -1 guard
causing StringIndexOutOfBoundsException; immediately after computing start add a
check like if (start == -1) fail("start tag not found: <select
id=\"countNonTerminalByUsageDate\"") (use a message that includes the searched
token) and refactor the repeated extraction logic into a small helper method
(e.g., extractBlock(String sql, String startToken, String endToken, String
failMsg)) and update all usages in LineDailyBatchTargetMapperSqlContractTest to
call that helper instead of duplicating indexOf/substring logic.
…lti-value insert 하는 메서드 추가
개요
SKIP LOCKED기반의 다중 워커 병렬 처리 및 단일 트랜잭션 처리 구조 직접 구현으로 고가용성 및 데이터 정합성 확보관련 BackLog
Resolves: #TBD
PR 유형
💡 주요 변경 사항 및 동작 흐름
본 배치의 핵심 동작은 크게 1) 대상 준비 (Manager), 2) 데이터 동기화 (Worker 병렬 처리) 두 단계로 구분
sequenceDiagram autonumber participant Scheduler participant Manager participant Worker participant Redis participant DB Scheduler->>Redis: 03:00 KST 일제히 가동 및 락(Lock) 경쟁 Redis-->>Manager: 락 획득 (Manager 선출) Redis-->>Worker: 락 획득 실패 (Worker로 전환되어 대기) rect rgb(240, 248, 255) note right of Manager: [Target Insert 준비 단계] Manager->>DB: 메타데이터 생성 및 유저 타겟 데이터 Chunk Insert Manager->>DB: 타겟 준비 완료 후 "RUNNING" 상태 전환 end rect rgb(240, 255, 240) note right of Worker: [Usage Sync 병렬 동기화 단계] loop 잔여 타겟이 소진될 때까지 여러 워커가 동시 수행 Worker->>DB: 100건씩 타겟 안전 선점 (SKIP LOCKED) Worker->>Redis: 해당 유저의 3종 사용량 스냅샷 조회 Worker->>DB: 단일 트랜잭션 내에서 [사용량 Insert + 타겟 상태 완료 처리] end end Worker->>DB: 잔여 타겟 0건 시 최종 "COMPLETED" 전환MAX(line_id)기반 영리한 재개로 불필요한 전체 스캔 방지/resume(속행) 및/rerun(실패 건 재처리) API 제공을 통한 유연한 대응 환경 구축🗄️ DB 관련 변경사항 (중요)
DAILY_APP_TOTAL_DATA테이블 내 통합되어 있던total_usage_data컬럼을 3개의 구체적 컬럼(individual_usage_data,shared_usage_data,qos_usage_data)으로 분리하여 데이터 명확성 개선LINE_DAILY_BATCH_JOB: 전체 배치 진행 상태(PENDING/RUNNING/COMPLETED/FAILED) 및 타겟들의 성공/실패/스킵 카운트 집계LINE_DAILY_BATCH_TARGET: 사용자(LINE)별 처리 상태 촘촘히 기록. 결합도 완화를 위해batch_job_idFK 과감히 제외,usage_date기반으로 집합 식별INSERT IGNORE: 기존 데이터의 처리 상태 이력 보호 및 중복 생성 방어 쿼리 활용SELECT ... FOR UPDATE SKIP LOCKED: 다중 워커의 동시 접근 시 Lock 경합 및 교착상태(Deadlock) 방지, 미점유 빈 타겟만의 안정적 선점 제어LINE_DAILY_TARGET_INSERT_BATCH(대상 준비) 작업의 완전한(100%) 완료 전까지LINE_DAILY_USAGE_SYNC_BATCH(동기화) 작업 상태 PENDING 강제 유지, Worker 진입 원천 차단/resume또는/rerunAPI 호출을 통한 이어달리기 강제application.yaml내 TaskSchedulerpool.size2로 확장. 03:00 정각 락 시도 Manager 스레드와 Worker 지연 폴링 스레드 간 자원 병목 현상 방지batch_daily_usage_sync_failed_count등) 추가 구성. 데드라인(06:00 KST) 초과 및 단 1건의 실패 발생 시 AlertManager 기반 Discord 알림 즉각 발송 체계 마련PR Checklist