From a661693ea94ea21ab5ab1cb439775f9ff64e5d4f Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 10 Apr 2026 14:30:11 +0900 Subject: [PATCH] =?UTF-8?q?docs(agent):=20=EC=97=90=EC=9D=B4=EC=A0=84?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=95=EC=82=B0=20QA=20=EA=B8=B0=EB=A1=9D?= =?UTF-8?q?=EC=97=90=20total=20projection=20=EB=A6=AC=ED=8C=A9=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/20260410_에이전트정산기능QA.md | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/20260410_에이전트정산기능QA.md b/docs/20260410_에이전트정산기능QA.md index 7fb61c14..57166c5f 100644 --- a/docs/20260410_에이전트정산기능QA.md +++ b/docs/20260410_에이전트정산기능QA.md @@ -17,6 +17,8 @@ - [x] calculate empty-result/snapshot pagination/finalized immutability 회귀 테스트를 추가한다. - [x] ratio 목록 응답을 member 단위 current/history 구조로 확장한다. - [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] 관련 테스트, 진단, 수동 검증 결과를 문서에 반영한다. @@ -101,7 +103,8 @@ - 핵심 관찰: 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`). - 차선책: DB query 분리가 바로 어렵다면, 최소한 total 계산에서 `.toMergedResponseItems()`를 제거하고 row를 단일 `fold`로 누적해 creator별 `groupBy`/중간 리스트 생성을 없앨 수 있다. 다만 이 방법은 기간 전체 row 적재 자체는 남기므로 임시 완화책으로만 본다. - - 구현 결과: 이번 단계에서는 `List.toResponseTotal()` 전용 경로를 추가해 generic 4종 total 계산에서 creator별 `groupBy`와 중간 `GetAgentSettlementByCreatorItem` 리스트 병합을 제거했다. DB total query 분리는 Spring Boot 2.7 + Querydsl JPA 제약을 고려한 후속 최적화 후보로 남긴다. + - 구현 결과(1차): `List.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`). - 구현 방안: finalize 내부에서는 creator별 재-groupBy 없는 전용 merge 로직을 두고, row 변환 결과를 summary/detail 양쪽에서 재사용하도록 draft 구조를 조정한다. - 구현 결과: `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.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`까지 함께 통과한 것으로 컴파일 진단을 대체했다. + +### 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 결과를 타입/컴파일 진단 근거로 사용했다.