docs(agent): 에이전트 정산 QA 기록에 total projection 리팩터링 검증을 추가한다
This commit is contained in:
@@ -17,6 +17,8 @@
|
|||||||
- [x] calculate empty-result/snapshot pagination/finalized immutability 회귀 테스트를 추가한다.
|
- [x] calculate empty-result/snapshot pagination/finalized immutability 회귀 테스트를 추가한다.
|
||||||
- [x] ratio 목록 응답을 member 단위 current/history 구조로 확장한다.
|
- [x] ratio 목록 응답을 member 단위 current/history 구조로 확장한다.
|
||||||
- [x] generic 4종 calculate total 경로를 전용 total 계산 경로로 분리한다.
|
- [x] generic 4종 calculate total 경로를 전용 total 계산 경로로 분리한다.
|
||||||
|
- [x] generic 4종 calculate total 경로를 DB total projection 전용 쿼리로 재리팩터링한다.
|
||||||
|
- [x] channel donation total 경로를 DB total projection 전용 쿼리로 재리팩터링한다.
|
||||||
- [x] finalize snapshot 생성 경로의 중복 groupBy/row 변환을 줄인다.
|
- [x] finalize snapshot 생성 경로의 중복 groupBy/row 변환을 줄인다.
|
||||||
- [x] 관련 테스트, 진단, 수동 검증 결과를 문서에 반영한다.
|
- [x] 관련 테스트, 진단, 수동 검증 결과를 문서에 반영한다.
|
||||||
|
|
||||||
@@ -101,7 +103,8 @@
|
|||||||
- 핵심 관찰: total 계산에서 creator별 병합 자체는 필수가 아니다. 현재 로직은 row → creator 병합 → grand total 순서지만, 최종 total 값은 row별 계산 결과를 그대로 모두 더한 값과 동일하다. 즉 total 전용 경로는 creator 응답 shape를 만들 필요 없이 “현재 row granularity의 정산 결과 총합”만 DB에서 반환하면 된다.
|
- 핵심 관찰: total 계산에서 creator별 병합 자체는 필수가 아니다. 현재 로직은 row → creator 병합 → grand total 순서지만, 최종 total 값은 row별 계산 결과를 그대로 모두 더한 값과 동일하다. 즉 total 전용 경로는 creator 응답 shape를 만들 필요 없이 “현재 row granularity의 정산 결과 총합”만 DB에서 반환하면 된다.
|
||||||
- 권장 구현 방안: `AgentCalculateQueryRepository`에 generic 4종용 `getCalculate*Total()` 전용 query를 추가하고, `GetAgentSettlementByCreatorTotal`에 대응하는 total projection을 `fetchOne()`으로 바로 반환한다. 이때 현재 반올림/비율 적용 semantics를 유지하려면, 기존 grouped row granularity를 유지한 뒤 그 row 단위 정산 금액을 다시 합산하는 형태가 필요하다. Spring Boot 2.7 + Querydsl JPA 제약을 고려하면, 1차 권장은 파생 테이블(native SQL 또는 Querydsl SQL/JPASQLQuery fallback) 기반 total query이고, 로컬 선례는 total을 목록 쿼리와 분리한 `AdminChannelDonationCalculateQueryRepository.getChannelDonationSettlementTotal()` / `CreatorAdminChannelDonationCalculateQueryRepository.getChannelDonationSettlementTotal()`이다 (`src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/channelDonation/AdminChannelDonationCalculateQueryRepository.kt:33-54`, `src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/channelDonation/CreatorAdminChannelDonationCalculateQueryRepository.kt:18-38`).
|
- 권장 구현 방안: `AgentCalculateQueryRepository`에 generic 4종용 `getCalculate*Total()` 전용 query를 추가하고, `GetAgentSettlementByCreatorTotal`에 대응하는 total projection을 `fetchOne()`으로 바로 반환한다. 이때 현재 반올림/비율 적용 semantics를 유지하려면, 기존 grouped row granularity를 유지한 뒤 그 row 단위 정산 금액을 다시 합산하는 형태가 필요하다. Spring Boot 2.7 + Querydsl JPA 제약을 고려하면, 1차 권장은 파생 테이블(native SQL 또는 Querydsl SQL/JPASQLQuery fallback) 기반 total query이고, 로컬 선례는 total을 목록 쿼리와 분리한 `AdminChannelDonationCalculateQueryRepository.getChannelDonationSettlementTotal()` / `CreatorAdminChannelDonationCalculateQueryRepository.getChannelDonationSettlementTotal()`이다 (`src/main/kotlin/kr/co/vividnext/sodalive/admin/calculate/channelDonation/AdminChannelDonationCalculateQueryRepository.kt:33-54`, `src/main/kotlin/kr/co/vividnext/sodalive/creator/admin/calculate/channelDonation/CreatorAdminChannelDonationCalculateQueryRepository.kt:18-38`).
|
||||||
- 차선책: DB query 분리가 바로 어렵다면, 최소한 total 계산에서 `.toMergedResponseItems()`를 제거하고 row를 단일 `fold`로 누적해 creator별 `groupBy`/중간 리스트 생성을 없앨 수 있다. 다만 이 방법은 기간 전체 row 적재 자체는 남기므로 임시 완화책으로만 본다.
|
- 차선책: DB query 분리가 바로 어렵다면, 최소한 total 계산에서 `.toMergedResponseItems()`를 제거하고 row를 단일 `fold`로 누적해 creator별 `groupBy`/중간 리스트 생성을 없앨 수 있다. 다만 이 방법은 기간 전체 row 적재 자체는 남기므로 임시 완화책으로만 본다.
|
||||||
- 구현 결과: 이번 단계에서는 `List<GetAgentCreatorSettlementSummaryQueryData>.toResponseTotal()` 전용 경로를 추가해 generic 4종 total 계산에서 creator별 `groupBy`와 중간 `GetAgentSettlementByCreatorItem` 리스트 병합을 제거했다. DB total query 분리는 Spring Boot 2.7 + Querydsl JPA 제약을 고려한 후속 최적화 후보로 남긴다.
|
- 구현 결과(1차): `List<GetAgentCreatorSettlementSummaryQueryData>.toResponseTotal()` 전용 경로를 추가해 generic 4종 total 계산에서 creator별 `groupBy`와 중간 `GetAgentSettlementByCreatorItem` 리스트 병합을 제거했다.
|
||||||
|
- 구현 결과(2차): `AgentCalculateQueryRepository`에 generic 4종용 `getCalculate*ByCreatorTotal()` native SQL derived-table query를 추가해 total을 DB에서 바로 계산하도록 바꿨다. 서비스는 더 이상 full row list를 total 계산용으로 읽지 않고, paged item 조회에만 기존 Querydsl row query를 사용한다.
|
||||||
- `AdminAgentSettlementSnapshotService.finalizeSnapshots()`는 DB가 assignment/ratio 단위로 이미 집계한 row를 creator별 snapshot 1건으로 다시 합산하는데, 이 합산 자체는 필요하지만 `rows.groupBy { creatorId }` 뒤에 `toMergedResponseItems()`가 다시 같은 key로 groupBy를 수행해 불필요한 재-groupBy가 한 번 더 발생한다. 반면 source detail은 query row 1건을 detail 1건으로 보존하므로 추가 집계는 없고, 동일 row의 `toResponseItem()` 변환만 summary/detail에서 각각 반복된다 (`src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.kt:131-179`, `182-253`).
|
- `AdminAgentSettlementSnapshotService.finalizeSnapshots()`는 DB가 assignment/ratio 단위로 이미 집계한 row를 creator별 snapshot 1건으로 다시 합산하는데, 이 합산 자체는 필요하지만 `rows.groupBy { creatorId }` 뒤에 `toMergedResponseItems()`가 다시 같은 key로 groupBy를 수행해 불필요한 재-groupBy가 한 번 더 발생한다. 반면 source detail은 query row 1건을 detail 1건으로 보존하므로 추가 집계는 없고, 동일 row의 `toResponseItem()` 변환만 summary/detail에서 각각 반복된다 (`src/main/kotlin/kr/co/vividnext/sodalive/admin/partner/agent/settlement/AdminAgentSettlementSnapshotService.kt:131-179`, `182-253`).
|
||||||
- 구현 방안: finalize 내부에서는 creator별 재-groupBy 없는 전용 merge 로직을 두고, row 변환 결과를 summary/detail 양쪽에서 재사용하도록 draft 구조를 조정한다.
|
- 구현 방안: finalize 내부에서는 creator별 재-groupBy 없는 전용 merge 로직을 두고, row 변환 결과를 summary/detail 양쪽에서 재사용하도록 draft 구조를 조정한다.
|
||||||
- 구현 결과: `SnapshotAggregateDraft` 단일 패스 누적 구조로 summary/source detail 수치를 함께 합산하도록 바꿔 creator별 재-groupBy와 중복 `toResponseItem()` 변환을 제거했다.
|
- 구현 결과: `SnapshotAggregateDraft` 단일 패스 누적 구조로 summary/source detail 수치를 함께 합산하도록 바꿔 creator별 재-groupBy와 중복 `toResponseItem()` 변환을 제거했다.
|
||||||
@@ -169,3 +172,29 @@
|
|||||||
- 성공: `./gradlew test --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AdminAgentSettlementRatioControllerTest` → BUILD SUCCESSFUL
|
- 성공: `./gradlew test --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AdminAgentSettlementRatioControllerTest` → BUILD SUCCESSFUL
|
||||||
- 성공: `./gradlew test --tests kr.co.vividnext.sodalive.admin.partner.agent.assignment.AdminAgentCreatorServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.settlement.AdminAgentSettlementSnapshotServiceTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AdminAgentSettlementRatioControllerTest` → BUILD SUCCESSFUL
|
- 성공: `./gradlew test --tests kr.co.vividnext.sodalive.admin.partner.agent.assignment.AdminAgentCreatorServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.settlement.AdminAgentSettlementSnapshotServiceTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AgentSettlementRatioServiceTest --tests kr.co.vividnext.sodalive.admin.partner.agent.ratio.AdminAgentSettlementRatioControllerTest` → BUILD SUCCESSFUL
|
||||||
- 참고: `lsp_diagnostics`는 현재 환경에 Kotlin LSP가 없어 사용할 수 없었고, 대신 위 Gradle 실행에서 `compileKotlin`/`compileTestKotlin`까지 함께 통과한 것으로 컴파일 진단을 대체했다.
|
- 참고: `lsp_diagnostics`는 현재 환경에 Kotlin LSP가 없어 사용할 수 없었고, 대신 위 Gradle 실행에서 `compileKotlin`/`compileTestKotlin`까지 함께 통과한 것으로 컴파일 진단을 대체했다.
|
||||||
|
|
||||||
|
### 6차 generic total DB projection 리팩터링
|
||||||
|
- 무엇을:
|
||||||
|
- generic 4종(LIVE/CONTENT/COMMUNITY/CONTENT_DONATION) total 계산을 full row load 기반 Kotlin 합산에서 DB total projection 전용 쿼리로 교체했다.
|
||||||
|
- 서비스 테스트와 DataJpa parity 테스트를 추가해 새 DB total이 기존 Kotlin total과 동일한지 고정했다.
|
||||||
|
- Oracle 리뷰에서 지적된 `CONTENT` total grouping drift(`explicit 70` vs `null -> fallback 70`)를 보정하고 전용 회귀 테스트를 추가했다.
|
||||||
|
- 왜:
|
||||||
|
- 1차 완화 이후에도 total 계산은 기간 전체 grouped row를 메모리로 읽어야 했고, 이 비용은 기간이 커질수록 그대로 남았다. 요청한 방향대로 total projection을 DB로 내리면서도 row-level rounding semantics를 유지하려면 parity 테스트와 함께 옮겨야 했다.
|
||||||
|
- 어떻게:
|
||||||
|
- 성공: `./gradlew test --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest` → BUILD SUCCESSFUL
|
||||||
|
- 성공: `./gradlew build` → BUILD SUCCESSFUL
|
||||||
|
- 성공(수동 확인): `./gradlew test --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldMatchDbTotalProjectionForContentRowsSplitByEffectiveSettlementRatio --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldMatchDbTotalProjectionAcrossAllGenericCategoriesWhenAgentRatioHistorySplitsRows` → BUILD SUCCESSFUL
|
||||||
|
- 성공(Oracle 후속 보정): `./gradlew test --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldMatchDbTotalProjectionWhenExplicitAndFallbackSeventyMustStaySeparated --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest` → BUILD SUCCESSFUL
|
||||||
|
- 성공(Oracle 후속 보정): `./gradlew build` → BUILD SUCCESSFUL
|
||||||
|
- 참고: Kotlin LSP 부재로 `lsp_diagnostics`는 여전히 실행 불가했고, 이번 단계도 `compileKotlin`/`compileTestKotlin` 포함 Gradle 결과를 타입/컴파일 진단 근거로 사용했다.
|
||||||
|
|
||||||
|
### 7차 channel donation total DB projection 리팩터링
|
||||||
|
- 무엇을:
|
||||||
|
- `AgentCalculateService.getChannelDonationByCreator()`의 total 계산을 full row load 기반 Kotlin 합산에서 DB total projection 전용 쿼리로 교체했다.
|
||||||
|
- split `useCanCalculate`와 agent 비율 이력이 섞인 채널후원에서도 새 DB total이 기존 Kotlin total과 같은지 서비스/Repository 테스트로 고정했다.
|
||||||
|
- 왜:
|
||||||
|
- generic 4종 total만 DB projection으로 내려가고 채널후원 total은 여전히 전체 row를 읽고 있었기 때문에, 동일한 최적화 방향을 채널후원에도 적용해 total 계산용 메모리 적재를 제거할 필요가 있었다.
|
||||||
|
- 어떻게:
|
||||||
|
- 성공: `./gradlew test --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateServiceTest --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest` → BUILD SUCCESSFUL
|
||||||
|
- 성공: `./gradlew build && ./gradlew test --tests kr.co.vividnext.sodalive.partner.agent.calculate.AgentCalculateQueryRepositoryTest.shouldMatchDbTotalProjectionForChannelDonationWithSplitCalculatesAndRatioHistory` → BUILD SUCCESSFUL
|
||||||
|
- 참고: Kotlin LSP 부재로 `lsp_diagnostics`는 실행 불가했고, 이번 단계도 `compileKotlin`/`compileTestKotlin` 포함 Gradle 결과를 타입/컴파일 진단 근거로 사용했다.
|
||||||
|
|||||||
Reference in New Issue
Block a user