Skip to content

[Cosmos] Reduce Change Feed Processor lease write response overhead - #50697

Open
Kushagra Thapar (kushagraThapar) wants to merge 5 commits into
mainfrom
kushagrathapar-cfp-lease-disable-content-response
Open

Kushagra Thapar (kushagraThapar) wants to merge 5 commits into
mainfrom
kushagrathapar-cfp-lease-disable-content-response

Conversation

@kushagraThapar

@kushagraThapar Kushagra Thapar (kushagraThapar) commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Description

Disable response bodies for CFP lease creates/replaces in both PK and EPK implementations. CFP retains the submitted lease and applies the response-header ETag, preserving conditional-write behavior without deserializing a returned lease body.

This reduces response bandwidth and client processing, not RU/write. Feed operations and CFP's at-least-once contract are unchanged. Related: Azure/azure-cosmos-dotnet-v3#6147.

Changes

  • Disable content response per lease write, regardless of the lease client's setting.
  • Use header ETags for bodyless creates/replaces and initialization locks; preserve body-present compatibility and existing conflict/recovery behavior.
  • Accept lease clients configured with content response disabled; update tests and changelog.

Benchmark: million-RU feed (2026-10-29)

Six-panel A/B comparison of lease response bytes, throughput, CPU, sampled allocation, RU per write, and individual paired changes

Blue = baseline; teal = this PR. Bars start at zero; diamonds show median within-pair changes, not confidence intervals.

Setup

Tested dimension Measured configuration
Feed provisioned throughput 1,000,000 RU/s
Lease container provisioned throughput 10,000 RU/s
Observed feed/physical partition ranges 100
Persisted CFP partition leases 100
Distinct logical partition keys 65,536, not 65,536 leases
Immutable dataset 63,355,852 documents, 512-byte user payload
Measurement Three A/B pairs; approximately 180 seconds per arm, after at least 60 seconds with every lease owned/checkpointed
Successful lease replacements Approximately 928-954/second across the 100 leases

Dedicated D16s_v5 VM (16 vCPU / 64 GiB), East US 2; JDK 17, 2 GiB heap/G1GC, Direct mode, Session consistency, single region. PK-range handleChanges, batch 100, checkpoint per batch; lease renew/acquire/expiration 17/13/60 seconds. Alternating order: B/T, T/B, B/T. No concurrent producer; every range stayed active throughout measurement. EPK processing was not separately benchmarked.

Baseline: 17341a305be1739bd899736c4057e493230c80b3; treatment: 8eea6f11f06d8de30f2646e2a49c7ea04aaf2329. The 59 external JARs were identical.

Results

Metric Observation
Lease response body Approximately 503 -> 0 bytes per successful replacement
Lease RNTBD response Approximately 1,029 -> 522 bytes, 49.3% smaller
RU per successful lease replacement Unchanged, approximately 11.0478 RU
Throughput Approximately 95,000 documents/s; median paired change +0.14%, pair range -0.21% to +2.40%
Whole-JVM CPU/document Median paired change -2.60%, range -5.07% to +1.95%; one pair increased
Sample-weight allocation/document Median paired change -2.38%, range -2.65% to -2.19%; lower in all three pairs

Bandwidth savings were measured directly, using successful Direct transport responses. The 49.3% reduction applies to lease-write RNTBD responses, not total application traffic or TLS/TCP/IP overhead.

Performance limitation: leases recorded 35,428-43,823 transport 429s/arm; feed 429s were zero and CPU used approximately 2.1/16 cores. Three pairs do not establish throughput/CPU gains. CPU is whole-JVM; allocation figures are JFR sampling estimates.

Large-customer scenarios: extrapolation, not measured results

Logical keys are not CFP leases: our 65,536 keys mapped to 100 physical/feed ranges and 100 partition leases. The scenarios below model 50k-100k owned leases across a fleet, not merely that many logical keys or capacity on one VM.

Avoided response bytes/s ~= successful lease writes/s * 507 bytes/write

The approximately 507-byte difference saved roughly 0.48 MB/s at our observed approximately 950 replacements/s. Assuming similar response sizes, stable ownership and sustained stated write cadences:

Assumed activity 50,000 owned leases 100,000 owned leases
Renewals only: approximately one successful write per lease every 17 seconds 1.49 MB/s, approximately 129 GB/day avoided 2.98 MB/s, approximately 258 GB/day avoided
Checkpoints only: one successful write per lease every second, excluding renewals 25.35 MB/s, approximately 2.19 TB/day avoided 50.70 MB/s, approximately 4.38 TB/day avoided

These are avoided bytes, not guaranteed cost savings (decimal units). Each row excludes the other write type. Actual savings depend on lease size, activity and checkpoint/renewal cadence.

Hypothesis: high-write-rate fleets can avoid substantial redundant traffic and lease-body parsing/allocation. CPU/GC headroom at 50k-100k leases remains unvalidated; do not multiply the observed allocation percentage by partition count. RU/write, write count, request size, storage and lease-management work remain unchanged. This does not fix lease throttling or prove fewer VMs/pods are needed.

All six measured arms

Execution order below. CPU/allocation use measured-window documents, not full-drain counts.

Pair Arm Documents/s CPU us/doc Sample bytes/doc RNTBD bytes/replace RU/replace Lease transport 429
1 baseline 94,619.0 22.306 8,495.8 1,028.15 11.04777 39,140
1 treatment 94,755.9 22.740 8,271.0 522.01 11.04774 43,823
2 treatment 94,590.0 22.055 8,250.9 522.00 11.04780 38,534
2 baseline 94,790.1 22.645 8,452.3 1,028.96 11.04779 39,627
3 baseline 92,250.7 23.565 8,480.3 1,028.97 11.04782 35,428
3 treatment 94,468.1 22.371 8,294.3 522.00 11.04778 38,869

All six arms subsequently drained 63,355,852 unique documents, persisted/reacquired all 100 leases and observed zero duplicates, foreign IDs or restart replay. These checks were outside measurement and do not imply exactly-once delivery.

Exact six-run CSV

Transport counters include retry responses; RU excludes gateway-only metadata charges. Source hashes identify the retained original arm JSONs. Rejected calibration/seed attempts are excluded.

sequence,pair,arm,window_documents,window_seconds,documents_per_second,cpu_us_per_document,process_cpu_cores,allocation_sample_bytes_per_document,lease_payload_bytes_per_replace,lease_rntbd_bytes_per_replace,lease_ru_per_successful_replace,lease_replaces_per_second,lease_replace_p50_ms,lease_replace_p95_ms,lease_replace_p99_ms,lease_transport_429,lease_transport_412,lease_transport_449,lease_transport_404,lease_retry_count,lease_transport_ru,feed_transport_ru,gc_collections,gc_time_ms,successful_lease_replaces,lease_payload_bytes_total,lease_rntbd_bytes_total,cpu_seconds,allocation_sample_weight_bytes,allocation_sample_events,full_drain_documents,duplicates,foreign,feed_429,feed_304,feed_ranges,checkpoint_persisted,restart_leases,restart_replay,every_range_active_each_10s,warmup_after_all_leases_ready_seconds,source_sha256
1,1,baseline,17033000,180.016751499,94618.97216879074,22.305524569952446,2.1105258084946086,8495.759454705572,502.9598146819076,1028.1521463872891,11.04777384894081,952.0280672376872,4.6,515.3,1012.1,39140,1014,162,16207,39302,2082367.970003575,979253.75,86,359,171381,86197756,176205743,379.93,144708270792,25918,63355852,0,0,0,0,100,True,100,0,True,60.971497289,362070fc30f584e7ed8004fa4ea0f19464f2a0bf21a7c99c39ecb3cd6ea558a3
2,1,treatment,17056500,180.004548851,94755.93871863002,22.74030428282473,2.1547788790663396,8271.004636121126,0.0,522.0051786307212,11.047744353925545,953.6814546953395,4.5,512.8,1015.9,43823,1006,123,14498,43946,2087466.930003444,980743.0,85,335,171667,0,89611063,387.87,141074390576,25796,63355852,0,0,0,0,100,True,100,0,True,60.997134747,b98a71f83e4bbe28664c0dcd45eb46610466dac4b860d7b3e6b6d3dfa0f8b4b4
3,2,treatment,17027100,180.009444763,94590.03677511387,22.054841987185135,2.0861683146371672,8250.88928543322,0.0,522.0,11.047796021760439,952.0667108641983,4.6,516.3,1012.0,38534,1060,143,13335,38677,2082174.0700035742,979058.25,85,367,171381,0,89460882,375.53,140488716952,25645,63355852,0,0,0,0,100,True,100,0,True,60.979496133,040b3e5f9bb2698ae54885ca1a2d82dd4111ea7291ebc4f78b94f630b1c3537b
4,2,baseline,17062700,180.005118446,94790.082344901,22.644716252410227,2.1464945182428834,8452.292412806883,502.9578517500116,1028.962855944447,11.047786270245949,953.6173275622456,4.6,515.4,1014.4,39627,989,129,15859,39756,2085776.4800035807,981053.5,86,360,171656,86335733,176627648,386.38,144218929752,25931,63355852,0,0,0,0,100,True,100,0,True,61.060724196,8997f35e71593edc8ec2be07d943228a2cac1f4be93f17e89ea5897f89279186
5,3,baseline,16606900,180.019306563,92250.66087113389,23.564903744828957,2.173877943825129,8480.284468263191,502.96222553367204,1028.9675039049175,11.047816836957736,928.217107321888,4.7,517.2,1009.5,35428,1100,200,15117,35628,2029655.7900035088,954735.75,83,351,167097,84043479,171937383,391.34,140831236136,25220,63355852,0,0,0,0,100,True,100,0,True,61.345883217,4c61a8a7f78ca6034d24f1c5e520850456157f7fe14497be2b73a0f8704e2b1d
6,3,treatment,17006200,180.020521974,94468.11848738097,22.37125283720055,2.1133701637358193,8294.266078489021,0.0,522.0,11.047777609023909,950.7693796417278,4.6,516.4,1009.8,38869,1013,161,13414,39030,2079532.6000035664,977856.5,84,346,171158,0,89344476,380.45,141053947784,25823,63355852,0,0,0,0,100,True,100,0,True,61.029544195,d992664532c4d0df4a1e30f75d9985c7c9829cae856f8d2018b614b43661bc42

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

New tests

PK/EPK coverage includes header-ETag chaining, body-present compatibility, conflicts/recovery, initialization and builder acceptance. Integration scenarios cover checkpoint persistence and restart; they were compiled but not executed in the local validation below.

Validation

Passed: Cosmos library/test-source compilation and 120 change-feed unit tests, zero failures/errors/skips (2026-10-28). Checkstyle passed; full CI is not claimed green.

Exact commands, environment and exclusions

After merging origin/main at 17341a305be1739bd899736c4057e493230c80b3 and resolving the changelog conflict: Maven 3.9.10, JDK 17.0.12. Set JAVA_HOME/PATH to JDK 17 and export MAVEN_SKIP_RC=true to prevent local Maven startup overrides. Run from the repository root.

mvn -B -ntp -f sdk/cosmos/azure-cosmos/pom.xml install -DskipTests -Dgpg.skip -Dmaven.javadoc.skip=true -Dcodesnippet.skip=true -Drevapi.skip=true -Djacoco.skip=true

Passed: BUILD SUCCESS, library packaging/install and test-source compilation; zero Checkstyle violations.

mvn -B -ntp -f sdk/cosmos/azure-cosmos-tests/pom.xml verify -Punit '-Dit.test=com.azure.cosmos.implementation.changefeed.**.*Test*' -Dfailsafe.failIfNoTests=true -Dgpg.skip -Dmaven.javadoc.skip=true -Dcodesnippet.skip=true -Drevapi.skip=true -Djacoco.skip=true

Passed: BUILD SUCCESS; 120 tests, 0 failures, 0 errors, 0 skipped.

Not run in this local validation: emulator/live integration suites, full repository build, SpotBugs, Javadoc, snippet verification, API compatibility and coverage checks.

Change Feed Processor lease writes (lease replace, lease create, store
marker and initialization lock) now always disable content response on
write. The cached lease keeps its submitted state and the new ETag is
taken from the response headers, so optimistic concurrency keeps working.
The lease container client no longer needs contentResponseOnWrite enabled.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae1fde61-d6d9-4ea8-9e25-9442e4cab19a
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae1fde61-d6d9-4ea8-9e25-9442e4cab19a
@kushagraThapar

Copy link
Copy Markdown
Member Author

/azp run java - cosmos - tests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@github-actions github-actions Bot added the Cosmos label Oct 8, 2026
@kushagraThapar

Copy link
Copy Markdown
Member Author

/azp run java - cosmos - tests

@azure-pipelines

azure-pipelines Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
33 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@kushagraThapar

Copy link
Copy Markdown
Member Author

/azp run java - cosmos - tests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Remove alwaysRun from @BeforeClass so the test class does not build clients
or create resources in unrelated live suites, and use the shared test
database like the other CFP tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ae1fde61-d6d9-4ea8-9e25-9442e4cab19a
@kushagraThapar

Copy link
Copy Markdown
Member Author

/azp run java - cosmos - tests

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Resolve the Cosmos changelog conflict while retaining upstream updates and documenting bandwidth and client-processing benefits without RU savings.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@kushagraThapar Kushagra Thapar (kushagraThapar) changed the title [Cosmos] Change Feed Processor: disable content response on write for lease writes [Cosmos] Reduce Change Feed Processor lease write response overhead Oct 28, 2026
@kushagraThapar
Kushagra Thapar (kushagraThapar) marked this pull request as ready for review October 29, 2026 15:50
Copilot AI balanced review requested due to automatic review settings October 29, 2026 15:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The emulator/live scenarios were not run, leaving lease concurrency, restart continuity, header ETag propagation, and conditional lock release unverified against actual bodyless service responses.

No open issues 🎉

What changed in this PR

Reduces Azure Cosmos DB Change Feed Processor lease-write response overhead across PK and EPK implementations, without changing RU charges or the at-least-once delivery contract.

Changes:

  • Disables response bodies for lease creates and replaces, preserving local lease state and updating ETags from response headers.
  • Allows lease clients configured with content responses disabled.
  • Adds unit and integration coverage for ETag chaining, recovery, initialization, and checkpoint resume; documents the efficiency improvement.
File Description
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​pkversion/​LeaseStoreManagerImpl.java Handles bodyless PK lease creation using header ETags.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​pkversion/​LeaseStoreImpl.java Disables initialization response bodies and captures lock ETags.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​pkversion/​IncrementalChangeFeedProcessorImpl.java Removes the requirement for enabled lease-client response content.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​pkversion/​DocumentServiceLeaseUpdaterImpl.java Preserves PK lease state and refreshes ETags after bodyless replaces.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​epkversion/​LeaseStoreManagerImpl.java Handles bodyless EPK lease creation using header ETags.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​epkversion/​LeaseStoreImpl.java Applies bodyless initialization writes and header-based lock ETags.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​epkversion/​DocumentServiceLeaseUpdaterImpl.java Preserves EPK lease state and refreshes ETags after bodyless replaces.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​epkversion/​ChangeFeedProcessorImplBase.java Accepts lease clients with response content disabled.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​changefeed/​common/​ChangeFeedHelper.java Adds a shared helper to disable write response content.
sdk/​cosmos/​azure-cosmos/​CHANGELOG.md Documents reduced response overhead and unchanged RU charges.
sdk/​cosmos/​azure-cosmos-tests/​src/​test/​java/​com/​azure/​cosmos/​rx/​changefeed/​ChangeFeedProcessorLeaseWithoutContentResponseTest.java Adds end-to-end checkpoint and resume scenarios across processor modes.
sdk/​cosmos/​azure-cosmos-tests/​src/​test/​java/​com/​azure/​cosmos/​implementation/​changefeed/​pkversion/​LeaseWritesWithoutContentResponseTests.java Covers PK bodyless writes, ETag recovery, initialization, and builder acceptance.
sdk/​cosmos/​azure-cosmos-tests/​src/​test/​java/​com/​azure/​cosmos/​implementation/​changefeed/​epkversion/​LeaseWritesWithoutContentResponseTests.java Covers EPK bodyless writes, ETag recovery, initialization, and handler acceptance.

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants