Skip to content

[Perf] Worker 수·실행 Slot별 전체 인덱싱 수평 확장 Benchmark 추가 - #140

Merged
Gimini-3 merged 7 commits into
developfrom
feature/138
Aug 10, 2026
Merged

[Perf] Worker 수·실행 Slot별 전체 인덱싱 수평 확장 Benchmark 추가#140
Gimini-3 merged 7 commits into
developfrom
feature/138

Conversation

@Gimini-3

@Gimini-3 Gimini-3 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

🔍️ 작업 내용

  • Closes [Perf] 자동 Worker 수·실행 슬롯별 전체 인덱싱 수평 확장 Benchmark 추가 #138
  • 실제 PostgreSQL 17·pgvector·MinIO·BGE-M3에서 Worker 수와 Worker별 실행 Slot 조합의 수평 확장성을 측정하는 전용 Benchmark를 추가했습니다.
  • HTTP Coordinator와 독립 Worker Application Context를 분리하고, 모든 Worker가 같은 PENDING Queue를 Production Claim 경계로 경쟁하게 했습니다.
  • 처리량·Queue/처리/E2E 지연·Worker별 성공 Attempt 분포·Speedup·Scaling Efficiency를 JSON으로 기록합니다.
  • Job·Attempt·Event·Chunk·Embedding·vector(1024) 정합성을 Profile마다 함께 검증합니다.

✨ 상세 설명

Profile

Profile Worker Worker별 Slot 전체 Slot
w1-s1 1 1 1
w1-s2 1 2 2
w2-s1 2 1 2
w2-s2 2 2 4
w4-s2 4 2 8

로컬 정식 측정 결과

16문서, 문서당 8 Chunk·Embedding, Profile별 2회 반복 중앙값입니다.

Profile 문서/분 Speedup Slot 효율 참여 Worker
w1-s1 19.475 1.000x 1.000 1
w1-s2 21.862 1.123x 0.561 1
w2-s1 21.576 1.108x 0.554 2
w2-s2 21.577 1.108x 0.277 2
w4-s2 21.677 1.113x 0.139 4

다중 Worker는 본 측정에서 실제 Job을 균등 분담했습니다. 전체 Slot 2개 이후 처리량은 약 21.6문서/분에서 포화되고 처리 p95가 증가해, 현재 단일 Host CPU BGE-M3가 공유 병목임을 확인했습니다.

실행 경계

docker compose up -d postgres minio embedding-server
DB_SSLMODE=disable ./gradlew workerHorizontalScalingTest

일반 test에서는 worker-horizontal-scaling Tag를 제외하며 전용 Task는 단일 Fork와 Build Cache 비활성화로 실행됩니다.

✅ 검증

  • ./gradlew test --tests com.opensource.docgrid.e2e.WorkerHorizontalScalingStatisticsTest
  • 작은 w1-s1, w2-s1 실제 인프라 Smoke
  • 2·4 Worker 실제 인프라 Smoke
  • DB_SSLMODE=disable ./gradlew workerHorizontalScalingTest
    • BUILD SUCCESSFUL in 8m 12s
    • 기본 5개 Profile, 총 10개 본 측정 완료
  • 테스트 전용 환경값을 사용한 ./gradlew test
    • 전체 일반 회귀 성공
  • 테스트 전용 환경값을 사용한 ./gradlew build
    • 패키징·check 성공

상세 결과: docs/test-results/gimin-#138-worker-horizontal-scaling-benchmark.md

🛠️ 추후 리팩토링 및 고도화 계획

  • GPU 또는 독립 BGE-M3 Instance를 Worker별로 분리해 Embedding 병목을 제거한 뒤 같은 Profile 재측정
  • 여러 물리 Host·Container 환경에서 Network와 Process 비용을 포함한 재측정
  • Queue 적체·Backpressure 정책 Benchmark와 단계별 Metrics 연계

📸 스크린샷 (선택)

  • 해당 없음. 구조화된 JSON과 Markdown 결과를 첨부했습니다.

💬 리뷰 요구사항

  • Worker Context마다 실제 배포 형태의 Servlet Application을 임의 Port로 시작하고, 기동 시간은 측정에서 제외하는 경계를 확인해 주세요.
  • 다중 Worker 참여는 예열이 아니라 16문서 본 측정에만 강제한 점을 확인해 주세요.
  • 처리량이 선형 증가해야 통과시키지 않고 정합성만 합격 조건으로 둔 성능 판정 경계를 확인해 주세요.

Summary by CodeRabbit

  • 새로운 기능

    • Worker 수와 실행 슬롯 조합별 인덱싱 수평 확장성 벤치마크를 추가했습니다.
    • 처리량, 지연 시간, Speedup, 확장 효율, 작업 분배 및 데이터 정합성을 측정합니다.
    • 측정 결과를 JSON 보고서로 저장합니다.
  • 문서

    • 벤치마크 설계, 실행 방법, 환경 및 결과를 문서화했습니다.
  • 테스트

    • 벤치마크 프로필 검증과 통계 계산에 대한 자동 테스트를 추가했습니다.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 48 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 104cb650-d7fb-4f4c-b683-f8458067f378

📥 Commits

Reviewing files that changed from the base of the PR and between 9fa7e13 and f13910a.

📒 Files selected for processing (3)
  • docs/design/gimin-#138-worker-horizontal-scaling-benchmark.md
  • docs/test-results/gimin-#138-worker-horizontal-scaling-benchmark.md
  • src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java
📝 Walkthrough

Walkthrough

Worker 수와 실행 슬롯 조합을 측정하는 전용 통합 Benchmark를 추가했습니다. 독립 Worker Context에서 실제 인덱싱 Pipeline을 실행하고, 처리량·지연·Worker 분포·정합성을 검증한 뒤 JSON과 결과 문서에 기록합니다.

Changes

Worker 수평 확장 Benchmark

Layer / File(s) Summary
Benchmark 계약과 실행 경계
build.gradle, src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingStatistics.java, src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingStatisticsTest.java, docs/design/...
Profile 파싱, 1x1 Baseline 검증, Speedup 및 Scaling Efficiency 계산을 추가했습니다. 기본 테스트에서 Benchmark를 제외하고 전용 workerHorizontalScalingTest 작업을 등록했습니다.
Worker Context와 인프라 수명주기
src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java, docs/design/...
Coordinator와 독립 Worker Context를 구성합니다. 테스트 Schema·Bucket을 격리하고 Worker 등록, Slot 용량, 시작·종료 상태를 검증합니다.
업로드, 인덱싱 측정과 정합성 검증
src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java, docs/design/...
문서를 병렬 업로드하고 Job 완료를 대기합니다. Chunk·Embedding·Vector·Attempt·Event 순서와 Worker 분포를 검증하며 처리량과 지연을 수집합니다.
통계 집계와 결과 문서화
src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java, docs/test-results/...
반복 결과의 중앙값과 Baseline 대비 지표를 계산합니다. Worker별 분포, 지연, 정합성 결과 및 실행 한계를 JSON과 결과 문서에 기록합니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BenchmarkTask
  participant WorkerHorizontalScalingBenchmark
  participant WorkerContext
  participant PostgreSQL
  participant MinIO
  participant BGEM3
  BenchmarkTask->>WorkerHorizontalScalingBenchmark: 전용 테스트 실행
  WorkerHorizontalScalingBenchmark->>WorkerContext: Worker Context 기동
  WorkerHorizontalScalingBenchmark->>MinIO: 문서 병렬 업로드
  WorkerContext->>PostgreSQL: Job Claim 및 상태 갱신
  WorkerContext->>MinIO: 문서 조회
  WorkerContext->>BGEM3: Embedding 생성
  WorkerContext->>PostgreSQL: Chunk·Embedding·Vector 저장
  WorkerHorizontalScalingBenchmark->>PostgreSQL: 정합성과 Worker 분포 검증
  WorkerHorizontalScalingBenchmark->>BenchmarkTask: JSON 결과 저장
Loading

Possibly related PRs

  • DocGrid/backend#53: FOR UPDATE SKIP LOCKED 기반 다중 Worker Job Claim 통합 테스트와 전용 Gradle 작업이 관련됩니다.
  • DocGrid/backend#91: Worker Job 분배와 FOR UPDATE SKIP LOCKED 동작을 직접 검증하는 변경입니다.
  • DocGrid/backend#135: 기존 Worker 인덱싱 Benchmark 인프라와 전용 Gradle 실행 패턴을 확장합니다.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 Worker 수와 실행 Slot별 전체 인덱싱 수평 확장 Benchmark 추가라는 주요 변경을 정확하고 간결하게 설명합니다.
Description check ✅ Passed 필수 섹션을 모두 포함하며 구현 범위, 측정 결과, 검증 방법, 후속 계획과 리뷰 요청사항을 구체적으로 설명합니다.
Linked Issues check ✅ Passed [#138]의 Context 분리, Profile 지원, 전체 인덱싱 측정, 정합성 검증, 전용 Gradle Task와 결과 문서 요구사항을 구현했습니다.
Out of Scope Changes check ✅ Passed 변경된 코드와 문서는 [#138]의 Benchmark 구현, 실행 경계, 결과 기록 및 설계 범위에 포함됩니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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/138

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (4)
src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java (3)

777-783: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

countqueryInteger가 완전히 같은 구현입니다.

두 메서드 모두 jdbcTemplate.queryForObject(sql, Integer.class, arguments)를 호출합니다. 반환 타입도 int로 동일합니다. 이름만 다릅니다. 호출 지점에서 어느 쪽을 써야 하는지 판단 기준이 없고, 실제로 Line 467과 Line 471은 같은 성격의 조회에 서로 다른 이름을 사용합니다.

하나로 합치는 편이 명확합니다. 다만 Line 502-509의 vector_dims 조회처럼 "개수"가 아닌 값을 읽는 지점이 있으므로, queryInteger 하나만 남기는 방향을 권장합니다.

🤖 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/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java`
around lines 777 - 783, Remove the duplicate count method and retain
queryInteger as the single integer-query helper, updating all count call sites
to use queryInteger while preserving the existing vector_dims and other
non-count lookups.

626-661: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

두 초기화 메서드의 TRUNCATE 대상 목록이 중복됩니다.

resetJobStateresetAllBenchmarkState는 8개 테이블 목록이 동일합니다. 차이는 worker_nodes 포함 여부 하나뿐입니다. 나중에 테이블이 추가되면 두 곳을 모두 고쳐야 하고, 한쪽을 빠뜨리면 Profile 간 데이터가 남습니다.

공통 목록을 상수로 추출하고 worker_nodes만 조건부로 덧붙이는 방식을 검토해 주세요.

🤖 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/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java`
around lines 626 - 661, Extract the shared eight-table TRUNCATE list from
resetJobState and resetAllBenchmarkState into a reusable constant or helper.
Build each statement from that shared list, adding worker_nodes only for
resetAllBenchmarkState, while preserving the existing reset behavior and
ordering.

452-560: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Job 정합성 검증이 문서 하나당 개별 쿼리 10건 이상을 실행합니다.

Line 463의 루프는 문서마다 상태, 재시도 수, Attempt 수, Chunk 수, Embedding 수, Vector 차원, Event 목록을 각각 별도 쿼리로 조회합니다. 기본 설정에서 16문서 × 5 Profile × 2 반복이면 검증 쿼리만 1,600건을 넘습니다.

이 구간은 처리량 측정 창(Line 363의 profileCompletedAt) 밖이므로 측정값은 왜곡되지 않습니다. 다만 전체 실행 시간이 늘어납니다. document-count를 크게 올려 실행할 계획이 있으면 문서 목록 단위 집계 쿼리로 묶는 방안을 검토해 주세요. 현재 기본 규모에서는 그대로 두어도 무방합니다.

🤖 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/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java`
around lines 452 - 560, assertProfileInvariants의 문서별 검증 쿼리를 문서 목록 단위 집계 쿼리로 통합해
실행 횟수를 줄이세요. 상태·retry·attempt·chunk·embedding·vector 차원·이벤트 검증에 필요한 결과를 여러 업로드
ID에 대해 한 번에 조회하고, 기존의 문서별 불변식과 실패 조건은 동일하게 유지하세요. 기본 규모 동작과 Profile 결과 생성은 변경하지
마세요.
src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingStatisticsTest.java (1)

17-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

기본값 폴백과 경계 입력 검증을 추가해 주세요.

현재 테스트는 정상 파싱, 중복, Baseline 누락, 계산 세 경로만 다룹니다. 다음 경로는 검증되지 않습니다.

  • rawValuenull 또는 공백일 때 defaults를 그대로 반환하는 경로. 이 경로는 System Property를 지정하지 않는 기본 실행 경로입니다.
  • 형식 오류 입력("1", "1x2x3") 거부.
  • WorkerProfileworkerCount·slotsPerWorker 0 이하 거부.
  • speedup의 Baseline 0 이하 거부와 scalingEfficiencytotalSlots 0 이하 거부.
🧪 추가 테스트 예시
`@Test`
`@DisplayName`("Profile 입력이 비어 있으면 기본 Profile 목록을 사용한다")
void fallsBackToDefaultProfiles() {
    List<WorkerProfile> defaults = List.of(new WorkerProfile(1, 1), new WorkerProfile(2, 2));

    assertThat(WorkerHorizontalScalingStatistics.parseProfiles(null, defaults))
        .isEqualTo(defaults);
    assertThat(WorkerHorizontalScalingStatistics.parseProfiles("  ", defaults))
        .isEqualTo(defaults);
}

`@Test`
`@DisplayName`("형식과 값 경계를 벗어난 입력을 거부한다")
void rejectsMalformedAndNonPositiveInputs() {
    assertThatThrownBy(() -> WorkerHorizontalScalingStatistics.parseProfiles("1", List.of()))
        .isInstanceOf(IllegalArgumentException.class);
    assertThatThrownBy(() -> WorkerHorizontalScalingStatistics.parseProfiles("1x2x3", List.of()))
        .isInstanceOf(IllegalArgumentException.class);
    assertThatThrownBy(() -> WorkerHorizontalScalingStatistics.parseProfiles("0x1", List.of()))
        .isInstanceOf(IllegalArgumentException.class);
    assertThatThrownBy(() -> WorkerHorizontalScalingStatistics.speedup(0.0, 1.0))
        .isInstanceOf(IllegalArgumentException.class);
    assertThatThrownBy(() -> WorkerHorizontalScalingStatistics.scalingEfficiency(1.0, 0))
        .isInstanceOf(IllegalArgumentException.class);
}
🤖 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/opensource/docgrid/e2e/WorkerHorizontalScalingStatisticsTest.java`
around lines 17 - 59, Update WorkerHorizontalScalingStatistics.parseProfiles to
return the exact defaults list when rawValue is null or blank, reject malformed
profile formats and non-positive workerCount or slotsPerWorker values, and
preserve existing duplicate/baseline validation. Add boundary validation in
speedup for non-positive baseline throughput and in scalingEfficiency for
non-positive totalSlots, throwing IllegalArgumentException.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/design/gimin-`#138-worker-horizontal-scaling-benchmark.md:
- Around line 106-119: Update the System Property list in the benchmark
documentation to include worker.horizontal.scaling.document-characters and
worker.horizontal.scaling.status-polling-ms. Extend the profile requirements to
state that the profile list must include 1x1 as the speedup baseline, while
preserving the existing validation requirements.

In `@docs/test-results/gimin-`#138-worker-horizontal-scaling-benchmark.md:
- Around line 150-154: Update the conclusion in section 10 so “균등 분담” is
explicitly framed as an observation from this benchmark run, not a guaranteed
behavior or invariant. Preserve the existing claims about participation,
indexing, and vector invariants, while avoiding wording that implies the
benchmark enforces balanced Worker distribution.

In
`@src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java`:
- Around line 314-333: Update the try/catch cleanup around WorkerCluster
creation so assertion failures from awaitCondition and the assertThat checks
also trigger closeContexts(contexts). Catch the appropriate broader throwable
type while preserving propagation of the original failure and existing
InterruptedException behavior.
- Around line 171-184: Update cleanUpInfrastructure so the TEST_SCHEMA drop
executes in a finally block after minioBucket cleanup, ensuring JDBC schema
removal occurs even when minioBucket.close() throws. Preserve the existing
currentCluster cleanup and bucket cleanup behavior.
- Around line 210-215: Update the cleanup flow surrounding awaitWorkersStopped
in the finally block of the benchmark method to preserve any exception from the
try body when shutdown verification also fails. Catch the pending cleanup
AssertionError and attach it to the original failure via addSuppressed, or
otherwise ensure cleanup failure cannot replace the primary exception; retain
the existing close and worker-stop behavior.
- Around line 765-775: Update awaitCondition to accept a Supplier<String> and
evaluate it only when the timeout AssertionError is created, then add the
java.util.function.Supplier import. Convert the three affected call sites around
the job/worker snapshot messages to lambdas so jobSnapshot(uploads) and
workerSnapshot(cluster) run at timeout rather than before waiting; also wrap the
constant message call around lines 314-317 in a lambda.

---

Nitpick comments:
In
`@src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java`:
- Around line 777-783: Remove the duplicate count method and retain queryInteger
as the single integer-query helper, updating all count call sites to use
queryInteger while preserving the existing vector_dims and other non-count
lookups.
- Around line 626-661: Extract the shared eight-table TRUNCATE list from
resetJobState and resetAllBenchmarkState into a reusable constant or helper.
Build each statement from that shared list, adding worker_nodes only for
resetAllBenchmarkState, while preserving the existing reset behavior and
ordering.
- Around line 452-560: assertProfileInvariants의 문서별 검증 쿼리를 문서 목록 단위 집계 쿼리로 통합해
실행 횟수를 줄이세요. 상태·retry·attempt·chunk·embedding·vector 차원·이벤트 검증에 필요한 결과를 여러 업로드
ID에 대해 한 번에 조회하고, 기존의 문서별 불변식과 실패 조건은 동일하게 유지하세요. 기본 규모 동작과 Profile 결과 생성은 변경하지
마세요.

In
`@src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingStatisticsTest.java`:
- Around line 17-59: Update WorkerHorizontalScalingStatistics.parseProfiles to
return the exact defaults list when rawValue is null or blank, reject malformed
profile formats and non-positive workerCount or slotsPerWorker values, and
preserve existing duplicate/baseline validation. Add boundary validation in
speedup for non-positive baseline throughput and in scalingEfficiency for
non-positive totalSlots, throwing IllegalArgumentException.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 62c14357-46ac-499a-a48a-f07389a5757c

📥 Commits

Reviewing files that changed from the base of the PR and between 2b1483f and 9fa7e13.

📒 Files selected for processing (6)
  • build.gradle
  • docs/design/gimin-#138-worker-horizontal-scaling-benchmark.md
  • docs/test-results/gimin-#138-worker-horizontal-scaling-benchmark.md
  • src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java
  • src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingStatistics.java
  • src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingStatisticsTest.java

Comment thread docs/design/gimin-#138-worker-horizontal-scaling-benchmark.md Outdated
Comment thread docs/test-results/gimin-#138-worker-horizontal-scaling-benchmark.md Outdated
Comment thread src/test/java/com/opensource/docgrid/e2e/WorkerHorizontalScalingBenchmark.java Outdated
@Gimini-3
Gimini-3 merged commit 3c6fec3 into develop Aug 10, 2026
1 check passed
@Gimini-3 Gimini-3 self-assigned this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Perf] 자동 Worker 수·실행 슬롯별 전체 인덱싱 수평 확장 Benchmark 추가

1 participant