Repository: etcd-io/etcd

Issue:
Flaky `go.etcd.io/etcd/server/v3/etcdserver/api/v3compactor: TestPeriodicHourly `
### Which Github Action / Prow Jobs are flaking?

https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/etcd-io_etcd/19426/pull-etcd-coverage-report/1895067242123497472

```

{Failed  === RUN   TestPeriodicHourly
    logger.go:146: 2025-02-27T11:08:18.530Z	INFO	starting auto periodic compaction	{"revision": 1, "compact-period": "2h0m0s"}
    logger.go:146: 2025-02-27T11:08:18.530Z	INFO	completed auto periodic compaction	{"revision": 1, "compact-period": "2h0m0s", "took": "0s"}
    logger.go:146: 2025-02-27T11:08:18.850Z	INFO	starting auto periodic compaction	{"revision": 10, "compact-period": "2h0m0s"}
    logger.go:146: 2025-02-27T11:08:18.851Z	INFO	completed auto periodic compaction	{"revision": 10, "compact-period": "2h0m0s", "took": "0s"}
    logger.go:146: 2025-02-27T11:08:19.032Z	INFO	starting auto periodic compaction	{"revision": 20, "compact-period": "2h0m0s"}
    logger.go:146: 2025-02-27T11:08:19.032Z	INFO	completed auto periodic compaction	{"revision": 20, "compact-period": "2h0m0s", "took": "0s"}
    logger.go:146: 2025-02-27T11:08:19.281Z	INFO	starting auto periodic compaction	{"revision": 30, "compact-period": "2h0m0s"}
    periodic_test.go:72: len(actions) = 0, expected >= 1
--- FAIL: TestPeriodicHourly (1.49s)
}

```

FYI. Previously I raised a similar issue https://github.com/etcd-io/etcd/issues/17054, which was resolved in https://github.com/etcd-io/etcd/pull/18608. It seems the fix still has flaw.

### Which tests are flaking?

.

### Github Action / Prow Job link

_No response_

### Reason for failure (if possible)

_No response_

### Anything else we need to know?

_No response_

Hints from issue comments:
@ivanvc: Discussed during the biweekly triage meeting. Open to a contributor that can help verify the flake using `stress`. Then, we also want the fix for the flake.

https://github.com/etcd-io/etcd/blob/05d57531e44a398463f929248a8427ea3a295dbe/CONTRIBUTING.md#check-for-flaky-tests@joshjms: /assign@joshjms: /unassign

I don't think I have the necessary skills to fix this (I tried for the past week with no results). Would love to keep in touch with this issue and whoever will take this up. Thanks!@amosehiguese: Hey @ahrtr

I would love to take this up, if you don't mind.
Please assign to me@amosehiguese: I've analyzed the flaky `TestPeriodicHourly` test and have implemented a fix that addresses the root cause of the flakiness.

#### Analysis of the Problem

After investigating the issue, I found that the test failure occurs because the test relies on precise timing. In CI environments, timing can be unpredictable, causing the test to fail when actions aren't recorded within the expected 10ms timeout window.

I was able to reliably reproduce the failure by following the instruction from @tjungblu as described in issue #17054, adding an 11ms sleep on line 125 of `periodic.go`. This simulates the slight delays that can occur in CI environments and consistently reproduced the issue.

#### Solution Approach

Rather than simply increasing the timeout (which would slow down the tests), I implemented an exponential backoff retry mechanism. This approach:

1. Attempts to retrieve the actions
2. If unsuccessful, waits for an incrementally longer period 
3. Retries up to a configurable number of times

This maintains fast test execution in the normal case while being robust to occasional timing variations.

#### Validation

I stress-tested the solution with 1000 runs under the artificially constrained conditions (11ms and 100ms delays), and the tests passed consistently. The exponential backoff approach with 5 retries proved to be highly resilient to timing variations between 1 - 200ms. 

Above 200ms on the other hand would fail if 5 retries were the configuration set

I've also applied this approach consistently to all similar test functions that broke while initiating an 11ms sleep 

- TestPeriodicHourly
- TestPeriodicMinutes (failed at 11ms sleep time) 
- TestPeriodicPause (failed at 11ms sleep time) 
- TestPeriodicSkipRevNotChange (failed at 20ms sleep time) 

This should resolve the flakiness while maintaining the test's correctness guarantees.

I will be sending a PR soon so that you can review the code @ahrtr 

Solution patch:
diff --git a/.github/workflows/antithesis-test.yml b/.github/workflows/antithesis-test.yml
new file mode 100644
index 000000000..fd2ff48e4
--- /dev/null
+++ b/.github/workflows/antithesis-test.yml
@@ -0,0 +1,99 @@
+---
+name: Build and trigger Antithesis exploration
+
+on:
+  # Disabled as discussed in https://github.com/etcd-io/etcd/pull/19750#issuecomment-2809840402
+  # pull_request:
+  #   branches: [main]
+  # schedule:
+  #   - cron: "0 0 * * *" # run every day at midnight
+  workflow_dispatch:
+    inputs:
+      test:
+        description: 'Test name'
+        required: false
+        default: 'etcd nightly antithesis run'
+        type: string
+      config_image:
+        description: 'Config image'
+        required: true
+        default: us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-config:latest
+        type: string
+      images:
+        description: 'System images (separate with ;)'
+        required: true
+        default: us-central1-docker.pkg.dev/molten-verve-216720/linuxfoundation-repository/etcd-client:latest;gcr.io/etcd-development/etcd:v3.5.21
+        type: string
+      duration:
+        description: 'Duration (exploration hours)'
+        required: true
+        type: int
+        default: 12
+      description:
+        description: 'Description (avoid quotes, please!)'
+        required: true
+        type: string
+        default: "etcd nightly antithesis run"
+      email:
+        description: 'Additional email notification recipient (separate with ;)'
+        required: true
+        type: string
+        default: ""
+
+# Declare default permissions as read only.
+permissions: read-all
+
+env:
+  REGISTRY: us-central1-docker.pkg.dev
+  REPOSITORY: molten-verve-216720/linuxfoundation-repository
+
+jobs:
+  build-and-push-and-test:
+    runs-on: ubuntu-latest
+    environment: Antithesis
+    steps:
+      - name: Checkout the code
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+      - name: Login to Antithesis Docker Registry
+        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: _json_key
+          password: ${{ secrets.ANTITHESIS_CONTAINER_REGISTRY_TOKEN }}
+
+      - name: Build and push config image
+        uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
+        with:
+          context: ./tests/antithesis
+          file: ./tests/antithesis/Dockerfile.config
+          push: true
+          tags: |
+            ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-config:latest,
+            ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-config:${{ github.sha }}
+
+      - name: Build and push client image
+        uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
+        with:
+          context: ./tests/antithesis/test-template
+          file: ./tests/antithesis/test-template/Dockerfile.client
+          push: true
+          tags: |
+            ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-client:latest,
+            ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/etcd-client:${{ github.sha }}
+
+      - name: Run Antithesis Tests
+        uses: antithesishq/antithesis-trigger-action@b7d0c9d1d9316bd4de73a44144c56636ea3a64ba # main commit on Mar 13, 2025
+        with:
+          notebook_name: etcd
+          tenant: linuxfoundation
+          username: ${{ secrets.ANTITHESIS_WEBHOOK_USERNAME }}
+          password: ${{ secrets.ANTITHESIS_WEBHOOK_PASSWORD }}
+          github_token: ${{ secrets.GH_PAT }}
+          config_image: ${{ inputs.config_image }}
+          images: ${{ inputs.images }}
+          description: ${{ inputs.description }}
+          email_recipients: ${{ inputs.email }}
+          test_name: ${{ inputs.test }}
+          additional_parameters: |-
+            custom.duration = ${{ inputs.duration }}
diff --git a/CHANGELOG/CHANGELOG-2.3.md b/CHANGELOG/CHANGELOG-2.3.md
index 0b54062b1..3d809290d 100644
--- a/CHANGELOG/CHANGELOG-2.3.md
+++ b/CHANGELOG/CHANGELOG-2.3.md
@@ -1,6 +1,6 @@
 
 
-<hr>
+---
 
 
 ## [v2.3.8](https://github.com/etcd-io/etcd/releases/tag/v2.3.8) (2017-02-17)
@@ -12,5 +12,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v2.3.7...v2.3.8).
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
diff --git a/CHANGELOG/CHANGELOG-3.0.md b/CHANGELOG/CHANGELOG-3.0.md
index bc11c80a5..19a032fc8 100644
--- a/CHANGELOG/CHANGELOG-3.0.md
+++ b/CHANGELOG/CHANGELOG-3.0.md
@@ -1,6 +1,6 @@
 
 
-<hr>
+---
 
 
 ## [v3.0.16](https://github.com/etcd-io/etcd/releases/tag/v3.0.16) (2016-11-13)
@@ -14,7 +14,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.15...v3.0.16) an
 - Compile with [*Go 1.6.4*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.15](https://github.com/etcd-io/etcd/releases/tag/v3.0.15) (2016-11-11)
@@ -32,7 +32,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.14...v3.0.15) an
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.14](https://github.com/etcd-io/etcd/releases/tag/v3.0.14) (2016-11-04)
@@ -50,7 +50,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.13...v3.0.14) an
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.13](https://github.com/etcd-io/etcd/releases/tag/v3.0.13) (2016-10-24)
@@ -64,7 +64,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.12...v3.0.13) an
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.12](https://github.com/etcd-io/etcd/releases/tag/v3.0.12) (2016-10-07)
@@ -78,7 +78,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.11...v3.0.12) an
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.11](https://github.com/etcd-io/etcd/releases/tag/v3.0.11) (2016-10-07)
@@ -98,7 +98,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.10...v3.0.11) an
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.10](https://github.com/etcd-io/etcd/releases/tag/v3.0.10) (2016-09-23)
@@ -112,7 +112,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.9...v3.0.10) and
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.9](https://github.com/etcd-io/etcd/releases/tag/v3.0.9) (2016-09-15)
@@ -130,7 +130,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.8...v3.0.9) and
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.8](https://github.com/etcd-io/etcd/releases/tag/v3.0.8) (2016-09-09)
@@ -148,7 +148,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.7...v3.0.8) and
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.7](https://github.com/etcd-io/etcd/releases/tag/v3.0.7) (2016-08-31)
@@ -166,7 +166,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.6...v3.0.7) and
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.6](https://github.com/etcd-io/etcd/releases/tag/v3.0.6) (2016-08-19)
@@ -180,7 +180,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.5...v3.0.6) and
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.5](https://github.com/etcd-io/etcd/releases/tag/v3.0.5) (2016-08-19)
@@ -198,7 +198,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.4...v3.0.5) and
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.4](https://github.com/etcd-io/etcd/releases/tag/v3.0.4) (2016-07-27)
@@ -221,7 +221,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.3...v3.0.4) and
 - Compile with [*Go 1.6.3*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.3](https://github.com/etcd-io/etcd/releases/tag/v3.0.3) (2016-07-15)
@@ -241,7 +241,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.2...v3.0.3) and
 - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.2](https://github.com/etcd-io/etcd/releases/tag/v3.0.2) (2016-07-08)
@@ -259,7 +259,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.1...v3.0.2) and
 - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.1](https://github.com/etcd-io/etcd/releases/tag/v3.0.1) (2016-07-01)
@@ -273,7 +273,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.0.0...v3.0.1) and
 - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
 
 ## [v3.0.0](https://github.com/etcd-io/etcd/releases/tag/v3.0.0) (2016-06-30)
@@ -287,5 +287,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v2.3.0...v3.0.0) and
 - Compile with [*Go 1.6.2*](https://golang.org/doc/devel/release.html#go1.6).
 
 
-<hr>
+---
 
diff --git a/CHANGELOG/CHANGELOG-3.1.md b/CHANGELOG/CHANGELOG-3.1.md
index 0c97517a7..e7d9a1460 100644
--- a/CHANGELOG/CHANGELOG-3.1.md
+++ b/CHANGELOG/CHANGELOG-3.1.md
@@ -2,7 +2,7 @@
 
 Previous change logs can be found at [CHANGELOG-3.0](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.0.md).
 
-<hr>
+---
 
 ## [v3.1.21](https://github.com/etcd-io/etcd/releases/tag/v3.1.21) (2019-TBD)
 
@@ -23,7 +23,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 
 - Fix bug where [db_compaction_total_duration_milliseconds metric incorrectly measured duration as 0](https://github.com/etcd-io/etcd/pull/10646).
 
-<hr>
+---
 
 ## [v3.1.20](https://github.com/etcd-io/etcd/releases/tag/v3.1.20) (2018-10-10)
 
@@ -69,7 +69,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.19](https://github.com/etcd-io/etcd/releases/tag/v3.1.19) (2018-07-24)
@@ -115,7 +115,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.18](https://github.com/etcd-io/etcd/releases/tag/v3.1.18) (2018-06-15)
@@ -138,7 +138,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.17](https://github.com/etcd-io/etcd/releases/tag/v3.1.17) (2018-06-06)
@@ -159,7 +159,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.16...v3.1.17) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.16](https://github.com/etcd-io/etcd/releases/tag/v3.1.16) (2018-05-31)
@@ -179,7 +179,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.15...v3.1.16) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.15](https://github.com/etcd-io/etcd/releases/tag/v3.1.15) (2018-05-09)
@@ -199,7 +199,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.14...v3.1.15) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.14](https://github.com/etcd-io/etcd/releases/tag/v3.1.14) (2018-04-24)
@@ -233,7 +233,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.13](https://github.com/etcd-io/etcd/releases/tag/v3.1.13) (2018-03-29)
@@ -261,7 +261,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.12](https://github.com/etcd-io/etcd/releases/tag/v3.1.12) (2018-03-08)
@@ -284,7 +284,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.11...v3.1.12) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.11](https://github.com/etcd-io/etcd/releases/tag/v3.1.11) (2017-11-28)
@@ -303,7 +303,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.10...v3.1.11) an
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.1.10](https://github.com/etcd-io/etcd/releases/tag/v3.1.10) (2017-07-14)
@@ -323,7 +323,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.9...v3.1.10) and
   - Fix panic on `net/http.CloseNotify`
 
 
-<hr>
+---
 
 
 ## [v3.1.9](https://github.com/etcd-io/etcd/releases/tag/v3.1.9) (2017-06-09)
@@ -341,7 +341,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.8...v3.1.9) and
 - Compile with [*Go 1.7.6*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.8](https://github.com/etcd-io/etcd/releases/tag/v3.1.8) (2017-05-19)
@@ -355,7 +355,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.7...v3.1.8) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.7](https://github.com/etcd-io/etcd/releases/tag/v3.1.7) (2017-04-28)
@@ -369,7 +369,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.6...v3.1.7) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.6](https://github.com/etcd-io/etcd/releases/tag/v3.1.6) (2017-04-19)
@@ -388,7 +388,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.5...v3.1.6) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.5](https://github.com/etcd-io/etcd/releases/tag/v3.1.5) (2017-03-27)
@@ -411,7 +411,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.4...v3.1.5) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.4](https://github.com/etcd-io/etcd/releases/tag/v3.1.4) (2017-03-22)
@@ -425,7 +425,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.3...v3.1.4) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.3](https://github.com/etcd-io/etcd/releases/tag/v3.1.3) (2017-03-10)
@@ -452,7 +452,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.2...v3.1.3) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.2](https://github.com/etcd-io/etcd/releases/tag/v3.1.2) (2017-02-24)
@@ -474,7 +474,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.1...v3.1.2) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.1](https://github.com/etcd-io/etcd/releases/tag/v3.1.1) (2017-02-17)
@@ -488,7 +488,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.1.0...v3.1.1) and
 - Compile with [*Go 1.7.5*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
 
 ## [v3.1.0](https://github.com/etcd-io/etcd/releases/tag/v3.1.0) (2017-01-20)
@@ -570,5 +570,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
 - Compile with [*Go 1.7.4*](https://golang.org/doc/devel/release.html#go1.7).
 
 
-<hr>
+---
 
diff --git a/CHANGELOG/CHANGELOG-3.2.md b/CHANGELOG/CHANGELOG-3.2.md
index 095ff6e9f..42bdc53a0 100644
--- a/CHANGELOG/CHANGELOG-3.2.md
+++ b/CHANGELOG/CHANGELOG-3.2.md
@@ -5,7 +5,7 @@ Previous change logs can be found at [CHANGELOG-3.1](https://github.com/etcd-io/
 
 ## v3.2.33 (TBD)
 
-<hr>
+---
 
 ## [v3.2.32](https://github.com/etcd-io/etcd/releases/tag/v3.2.32) (2021-03-28)
 See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.31...v3.2.32) and [v3.2 upgrade guide](https://etcd.io/docs/latest/upgrades/upgrade_3_2/) for any breaking changes.
@@ -23,7 +23,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.31...v3.2.32) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.2.31](https://github.com/etcd-io/etcd/releases/tag/v3.2.31) (2020-08-18)
@@ -49,7 +49,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.30...v3.2.31) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.2.30](https://github.com/etcd-io/etcd/releases/tag/v3.2.30) (2020-04-01)
@@ -69,7 +69,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.29...v3.2.30) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.2.29](https://github.com/etcd-io/etcd/releases/tag/v3.2.29) (2020-03-18)
@@ -97,7 +97,7 @@ See [List of metrics](https://github.com/etcd-io/etcd/tree/main/Documentation/me
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.28](https://github.com/etcd-io/etcd/releases/tag/v3.2.28) (2019-11-10)
@@ -125,7 +125,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.27](https://github.com/etcd-io/etcd/releases/tag/v3.2.27) (2019-09-17)
@@ -161,7 +161,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.26](https://github.com/etcd-io/etcd/releases/tag/v3.2.26) (2019-01-11)
@@ -183,7 +183,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.25...v3.2.26) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.25](https://github.com/etcd-io/etcd/releases/tag/v3.2.25) (2018-10-10)
@@ -230,7 +230,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.24](https://github.com/etcd-io/etcd/releases/tag/v3.2.24) (2018-07-24)
@@ -286,7 +286,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.23](https://github.com/etcd-io/etcd/releases/tag/v3.2.23) (2018-06-15)
@@ -317,7 +317,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.22](https://github.com/etcd-io/etcd/releases/tag/v3.2.22) (2018-06-06)
@@ -339,7 +339,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.21...v3.2.22) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.21](https://github.com/etcd-io/etcd/releases/tag/v3.2.21) (2018-05-31)
@@ -360,7 +360,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.20...v3.2.21) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.20](https://github.com/etcd-io/etcd/releases/tag/v3.2.20) (2018-05-09)
@@ -380,7 +380,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.19...v3.2.20) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.19](https://github.com/etcd-io/etcd/releases/tag/v3.2.19) (2018-04-24)
@@ -423,7 +423,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.18](https://github.com/etcd-io/etcd/releases/tag/v3.2.18) (2018-03-29)
@@ -451,7 +451,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.17](https://github.com/etcd-io/etcd/releases/tag/v3.2.17) (2018-03-08)
@@ -481,7 +481,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.16...v3.2.17) an
 - Compile with [*Go 1.8.7*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.16](https://github.com/etcd-io/etcd/releases/tag/v3.2.16) (2018-02-12)
@@ -504,7 +504,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.15...v3.2.16) an
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.15](https://github.com/etcd-io/etcd/releases/tag/v3.2.15) (2018-01-22)
@@ -523,7 +523,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.14...v3.2.15) an
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.14](https://github.com/etcd-io/etcd/releases/tag/v3.2.14) (2018-01-11)
@@ -545,7 +545,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.13...v3.2.14) an
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.13](https://github.com/etcd-io/etcd/releases/tag/v3.2.13) (2018-01-02)
@@ -564,7 +564,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.12...v3.2.13) an
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.12](https://github.com/etcd-io/etcd/releases/tag/v3.2.12) (2017-12-20)
@@ -596,7 +596,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.11...v3.2.12) an
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.11](https://github.com/etcd-io/etcd/releases/tag/v3.2.11) (2017-12-05)
@@ -629,7 +629,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.10](https://github.com/etcd-io/etcd/releases/tag/v3.2.10) (2017-11-16)
@@ -663,7 +663,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
 - Compile with [*Go 1.8.5*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.9](https://github.com/etcd-io/etcd/releases/tag/v3.2.9) (2017-10-06)
@@ -685,7 +685,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
 - Compile with [*Go 1.8.4*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.8](https://github.com/etcd-io/etcd/releases/tag/v3.2.8) (2017-09-29)
@@ -707,7 +707,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.7...v3.2.8) and
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.7](https://github.com/etcd-io/etcd/releases/tag/v3.2.7) (2017-09-01)
@@ -730,7 +730,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.6...v3.2.7) and
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.6](https://github.com/etcd-io/etcd/releases/tag/v3.2.6) (2017-08-21)
@@ -758,7 +758,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.5](https://github.com/etcd-io/etcd/releases/tag/v3.2.5) (2017-08-04)
@@ -798,7 +798,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.4](https://github.com/etcd-io/etcd/releases/tag/v3.2.4) (2017-07-19)
@@ -820,7 +820,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.3...v3.2.4) and
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.3](https://github.com/etcd-io/etcd/releases/tag/v3.2.3) (2017-07-14)
@@ -843,7 +843,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.2.2...v3.2.3) and
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.2](https://github.com/etcd-io/etcd/releases/tag/v3.2.2) (2017-07-07)
@@ -879,7 +879,7 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.1](https://github.com/etcd-io/etcd/releases/tag/v3.2.1) (2017-06-23)
@@ -909,7 +909,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
 
 ## [v3.2.0](https://github.com/etcd-io/etcd/releases/tag/v3.2.0) (2017-06-09)
@@ -1017,5 +1017,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
 - Compile with [*Go 1.8.3*](https://golang.org/doc/devel/release.html#go1.8).
 
 
-<hr>
+---
 
diff --git a/CHANGELOG/CHANGELOG-3.3.md b/CHANGELOG/CHANGELOG-3.3.md
index 8addba112..825883ab4 100644
--- a/CHANGELOG/CHANGELOG-3.3.md
+++ b/CHANGELOG/CHANGELOG-3.3.md
@@ -2,7 +2,7 @@
 
 Previous change logs can be found at [CHANGELOG-3.2](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.2.md).
 
-<hr>
+---
 
 ## v3.3.27 (2021-10-15)
 
@@ -16,7 +16,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.26...v3.3.27) an
   - [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp
   - [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads.
 
-<hr>
+---
 
 ## v3.3.26 (2021-10-03)
 
@@ -34,7 +34,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.25...v3.3.26) an
 
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
-<hr>
+---
 
 ## v3.3.25 (2020-08-24)
 
@@ -68,7 +68,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.23...v3.3.24) an
 
 
 
-<hr>
+---
 
 
 
@@ -96,7 +96,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.22...v3.3.23) an
 
 
 
-<hr>
+---
 
 
 ## [v3.3.22](https://github.com/etcd-io/etcd/releases/tag/v3.3.22) (2020-05-20)
@@ -113,7 +113,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.21...v3.3.22) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.3.21](https://github.com/etcd-io/etcd/releases/tag/v3.3.21) (2020-05-18)
@@ -150,7 +150,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.20...v3.3.21) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.3.20](https://github.com/etcd-io/etcd/releases/tag/v3.3.20) (2020-04-01)
@@ -170,7 +170,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.19...v3.3.20) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.3.19](https://github.com/etcd-io/etcd/releases/tag/v3.3.19) (2020-03-18)
@@ -206,7 +206,7 @@ See [List of metrics](https://github.com/etcd-io/etcd/tree/main/Documentation/me
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.3.18](https://github.com/etcd-io/etcd/releases/tag/v3.3.18) (2019-11-26)
@@ -227,7 +227,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
   - Now, etcd makes sure the purge file loop exits before server signals stop of the raft node.
 
 
-<hr>
+---
 
 
 ## [v3.3.17](https://github.com/etcd-io/etcd/releases/tag/v3.3.17) (2019-10-11)
@@ -241,7 +241,7 @@ This release replaces 3.3.16.
 Due to the etcd 3.3.16 release being incorrectly released (see details below), please use this release instead.
 
 
-<hr>
+---
 
 
 ## [v3.3.16](https://github.com/etcd-io/etcd/releases/tag/v3.3.16) (2019-10-10)
@@ -290,7 +290,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
   - Fix ["1.16: etcd client does not parse IPv6 addresses correctly when members are joining" (kubernetes#83550)](https://github.com/kubernetes/kubernetes/issues/83550).
 
 
-<hr>
+---
 
 
 ## [v3.3.15](https://github.com/etcd-io/etcd/releases/tag/v3.3.15) (2019-08-19)
@@ -313,7 +313,7 @@ NOTE: This patch release had to include some new features from 3.4, while trying
 - Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes.
 
 
-<hr>
+---
 
 
 ## [v3.3.14](https://github.com/etcd-io/etcd/releases/tag/v3.3.14) (2019-08-16)
@@ -416,7 +416,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes.
 
 
-<hr>
+---
 
 
 ## [v3.3.13](https://github.com/etcd-io/etcd/releases/tag/v3.3.13) (2019-05-02)
@@ -456,7 +456,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.10.8*](https://golang.org/doc/devel/release.html#go1.10).
 
 
-<hr>
+---
 
 
 ## [v3.3.12](https://github.com/etcd-io/etcd/releases/tag/v3.3.12) (2019-02-07)
@@ -474,7 +474,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.11...v3.3.12) an
 - Compile with [*Go 1.10.8*](https://golang.org/doc/devel/release.html#go1.10).
 
 
-<hr>
+---
 
 
 ## [v3.3.11](https://github.com/etcd-io/etcd/releases/tag/v3.3.11) (2019-01-11)
@@ -496,7 +496,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.10...v3.3.11) an
 - Compile with [*Go 1.10.7*](https://golang.org/doc/devel/release.html#go1.10).
 
 
-<hr>
+---
 
 
 ## [v3.3.10](https://github.com/etcd-io/etcd/releases/tag/v3.3.10) (2018-10-10)
@@ -542,7 +542,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.10.4*](https://golang.org/doc/devel/release.html#go1.10).
 
 
-<hr>
+---
 
 
 ## [v3.3.9](https://github.com/etcd-io/etcd/releases/tag/v3.3.9) (2018-07-24)
@@ -597,7 +597,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.10.3*](https://golang.org/doc/devel/release.html#go1.10).
 
 
-<hr>
+---
 
 
 ## [v3.3.8](https://github.com/etcd-io/etcd/releases/tag/v3.3.8) (2018-06-15)
@@ -619,7 +619,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.7...v3.3.8) and
 - Compile with [*Go 1.9.7*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.7](https://github.com/etcd-io/etcd/releases/tag/v3.3.7) (2018-06-06)
@@ -645,7 +645,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.6...v3.3.7) and
 - Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.6](https://github.com/etcd-io/etcd/releases/tag/v3.3.6) (2018-05-31)
@@ -668,7 +668,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.5...v3.3.6) and
 - Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.5](https://github.com/etcd-io/etcd/releases/tag/v3.3.5) (2018-05-09)
@@ -687,7 +687,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.4...v3.3.5) and
 - Compile with [*Go 1.9.6*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.4](https://github.com/etcd-io/etcd/releases/tag/v3.3.4) (2018-04-24)
@@ -735,7 +735,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.9.5*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.3](https://github.com/etcd-io/etcd/releases/tag/v3.3.3) (2018-03-29)
@@ -772,7 +772,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.9.5*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.2](https://github.com/etcd-io/etcd/releases/tag/v3.3.2) (2018-03-08)
@@ -805,7 +805,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.1...v3.3.2) and
 - Compile with [*Go 1.9.4*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.1](https://github.com/etcd-io/etcd/releases/tag/v3.3.1) (2018-02-12)
@@ -833,7 +833,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.3.0...v3.3.1) and
 - Compile with [*Go 1.9.4*](https://golang.org/doc/devel/release.html#go1.9).
 
 
-<hr>
+---
 
 
 ## [v3.3.0](https://github.com/etcd-io/etcd/releases/tag/v3.3.0) (2018-02-01)
@@ -1117,5 +1117,5 @@ See [security doc](https://etcd.io/docs/latest/op-guide/security/) for more deta
 - Deprecate [`golang.org/x/net/context`](https://github.com/etcd-io/etcd/pull/8511).
 
 
-<hr>
+---
 
diff --git a/CHANGELOG/CHANGELOG-3.4.md b/CHANGELOG/CHANGELOG-3.4.md
index 5818b680f..4e427170c 100644
--- a/CHANGELOG/CHANGELOG-3.4.md
+++ b/CHANGELOG/CHANGELOG-3.4.md
@@ -2,15 +2,19 @@
 
 Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.3.md).
 
-<hr>
+---
 
-## v3.4.37 (TBC)
+## v3.4.38 (TBC)
+
+---
+
+## v3.4.37 (2025-04-15)
 
 ### Dependencies
 - Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19529).
 - Compile binaries using [go 1.23.8](https://github.com/etcd-io/etcd/pull/19726)
 
-<hr>
+---
 
 ## v3.4.36 (2025-02-25)
 
@@ -26,7 +30,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 - Bump golang.org/x/crypto to v0.35.0 to address [CVE-2024-45337](https://github.com/etcd-io/etcd/pull/19197) and [CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19477).
 - Bump golang.org/x/net to v0.34.0 to address [CVE-2024-45338](https://github.com/etcd-io/etcd/pull/19197).
 
-<hr>
+---
 
 ## v3.4.35 (2024-11-12)
 
@@ -38,7 +42,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Dependencies
 - Compile binaries using [go 1.22.9](https://github.com/etcd-io/etcd/pull/18850).
 
-<hr>
+---
 
 ## v3.4.34 (2024-09-11)
 
@@ -53,7 +57,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 - Compile binaries using [go 1.22.7](https://github.com/etcd-io/etcd/pull/18549).
 - Upgrade [bbolt to 1.3.11](https://github.com/etcd-io/etcd/pull/18488).
 
-<hr>
+---
 
 ## v3.4.33 (2024-06-13)
 
@@ -64,7 +68,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 - Compile binaries using go [1.21.11](https://github.com/etcd-io/etcd/pull/18130).
 - Upgrade [bbolt to 1.3.10](https://github.com/etcd-io/etcd/pull/17945).
 
-<hr>
+---
 
 ## v3.4.32 (2024-04-25)
 
@@ -83,7 +87,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Dependencies
 - Compile binaries using [go 1.21.9](https://github.com/etcd-io/etcd/pull/17709).
 
-<hr>
+---
 
 ## v3.4.31 (2024-03-21)
 
@@ -106,7 +110,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Others
 - [Make CGO_ENABLED configurable](https://github.com/etcd-io/etcd/pull/17422).
 
-<hr>
+---
 
 ## v3.4.30 (2024-01-31)
 
@@ -117,7 +121,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 - Compile binaries using go [1.20.13](https://github.com/etcd-io/etcd/pull/17276).
 - Upgrade [golang.org/x/crypto to v0.17+ to address CVE-2023-48795](https://github.com/etcd-io/etcd/pull/17347).
 
-<hr>
+---
 
 ## v3.4.29 (2024-01-09)
 
@@ -131,7 +135,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Dependencies
 - Compile binaries using go [1.20.12](https://github.com/etcd-io/etcd/pull/17076).
 
-<hr>
+---
 
 ## v3.4.28 (2023-11-23)
 
@@ -153,7 +157,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 - Upgrade [bbolt to 1.3.8](https://github.com/etcd-io/etcd/pull/16834).
 - Upgrade gRPC to 1.58.3 in https://github.com/etcd-io/etcd/pull/16997 and https://github.com/etcd-io/etcd/pull/16999. Note that gRPC server will reject requests with connection header (refer to https://github.com/grpc/grpc-go/pull/4803).
 
-<hr>
+---
 
 ## v3.4.27 (2023-07-11)
 
@@ -168,7 +172,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Dependencies
 - Compile binaries using [go 1.19.10](https://github.com/etcd-io/etcd/pull/16038).
 
-<hr>
+---
 
 ## v3.4.26 (2023-05-12)
 
@@ -179,7 +183,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Dependencies
 - Compile binaries using [go 1.19.9](https://github.com/etcd-io/etcd/pull/15823)
 
-<hr>
+---
 
 ## v3.4.25 (2023-04-14)
 
@@ -206,7 +210,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Docker image
 - Fix [etcd docker images all tagged with amd64 architecture](https://github.com/etcd-io/etcd/pull/15681)
 
-<hr>
+---
 
 ## v3.4.24 (2023-02-16)
 
@@ -228,7 +232,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Docker image
 - Updated [base image from base-debian11 to static-debian11 and removed dependency on busybox](https://github.com/etcd-io/etcd/pull/15038).
 
-<hr>
+---
 
 ## v3.4.23 (2022-12-21)
 
@@ -247,7 +251,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### Docker image
 - Use [distroless base image](https://github.com/etcd-io/etcd/pull/15017) to address critical Vulnerabilities.
 
-<hr>
+---
 
 ## v3.4.22 (2022-11-02)
 
@@ -266,7 +270,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 ### etcd grpc-proxy
 - Add [`etcd grpc-proxy start --listen-cipher-suites`](https://github.com/etcd-io/etcd/pull/14601) flag to support adding configurable cipher list.
 
-<hr>
+---
 
 ## v3.4.21 (2022-09-15)
 
@@ -279,7 +283,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 
 - Fix [etcdctl move-leader may fail for multiple endpoints](https://github.com/etcd-io/etcd/pull/14441)
 
-<hr>
+---
 
 ## v3.4.20 (2022-08-06)
 
@@ -299,7 +303,7 @@ Previous change logs can be found at [CHANGELOG-3.3](https://github.com/etcd-io/
 - Fix [Grant lease with negative ID can possibly cause db out of sync](https://github.com/etcd-io/etcd/pull/14239)
 - Fix [Allow non mutating requests pass through quotaKVServer when NOSPACE](https://github.com/etcd-io/etcd/pull/14254)
 
-<hr>
+---
 
 ## v3.4.19 (2022-07-12)
 
@@ -327,7 +331,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.18...v3.4.19) an
 - Compile with [Go 1.16+](https://go.dev/doc/devel/release#go1.16).
 - etcd uses [go modules](https://github.com/etcd-io/etcd/pull/14136) (instead of vendor dir) to track dependencies.
 
-<hr>
+---
 
 ## v3.4.18 (2021-10-15)
 
@@ -347,7 +351,7 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per
   - [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp
   - [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads.
 
-<hr>
+---
 
 ## v3.4.17 (2021-10-03)
 
@@ -369,7 +373,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.16...v3.4.17) an
 
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
-<hr>
+---
 
 ## v3.4.16 (2021-05-11)
 
@@ -391,7 +395,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.15...v3.4.16) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.15](https://github.com/etcd-io/etcd/releases/tag/v3.4.15) (2021-02-26)
@@ -416,7 +420,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.14...v3.4.15) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.14](https://github.com/etcd-io/etcd/releases/tag/v3.4.14) (2020-11-25)
@@ -446,7 +450,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.13...v3.4.14) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.13](https://github.com/etcd-io/etcd/releases/tag/v3.4.13) (2020-8-24)
@@ -462,7 +466,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.12...v3.4.13) an
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.12](https://github.com/etcd-io/etcd/releases/tag/v3.4.12) (2020-08-19)
@@ -480,7 +484,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.11...v3.4.12) an
 
 
 
-<hr>
+---
 
 
 
@@ -514,7 +518,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.10...v3.4.11) an
 
 
 
-<hr>
+---
 
 
 
@@ -543,7 +547,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.9...v3.4.10) and
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.9](https://github.com/etcd-io/etcd/releases/tag/v3.4.9) (2020-05-20)
@@ -560,7 +564,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.8...v3.4.9) and
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.8](https://github.com/etcd-io/etcd/releases/tag/v3.4.8) (2020-05-18)
@@ -597,7 +601,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.7...v3.4.8) and
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.7](https://github.com/etcd-io/etcd/releases/tag/v3.4.7) (2020-04-01)
@@ -621,7 +625,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.6...v3.4.7) and
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.6](https://github.com/etcd-io/etcd/releases/tag/v3.4.6) (2020-03-29)
@@ -639,7 +643,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.5...v3.4.6) and
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.5](https://github.com/etcd-io/etcd/releases/tag/v3.4.5) (2020-03-18)
@@ -676,7 +680,7 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per
 - Compile with [*Go 1.12.17*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.4](https://github.com/etcd-io/etcd/releases/tag/v3.4.4) (2020-02-24)
@@ -709,7 +713,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Fix bug where [some auth related messages are logged at wrong level](https://github.com/etcd-io/etcd/pull/11586)
 
 
-<hr>
+---
 
 
 ## [v3.4.3](https://github.com/etcd-io/etcd/releases/tag/v3.4.3) (2019-10-24)
@@ -731,7 +735,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.12.12*](https://golang.org/doc/devel/release.html#go1.12).
 
 
-<hr>
+---
 
 
 ## [v3.4.2](https://github.com/etcd-io/etcd/releases/tag/v3.4.2) (2019-10-11)
@@ -760,7 +764,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.4.1...v3.4.2) and
   - Fix ["1.16: etcd client does not parse IPv6 addresses correctly when members are joining" (kubernetes#83550)](https://github.com/kubernetes/kubernetes/issues/83550).
 
 
-<hr>
+---
 
 
 ## [v3.4.1](https://github.com/etcd-io/etcd/releases/tag/v3.4.1) (2019-09-17)
@@ -796,7 +800,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - Compile with [*Go 1.12.9*](https://golang.org/doc/devel/release.html#go1.12) including [*Go 1.12.8*](https://groups.google.com/d/msg/golang-announce/65QixT3tcmg/DrFiG6vvCwAJ) security fixes.
 
 
-<hr>
+---
 
 
 ## v3.4.0 (2019-08-30)
@@ -1401,5 +1405,5 @@ Note: **v3.5 will deprecate `etcd --log-package-levels` flag for `capnslog`**; `
 
 - [Rebase etcd image from Alpine to Debian](https://github.com/etcd-io/etcd/pull/10805) to improve security and maintenance effort for etcd release.
 
-<hr>
+---
 
diff --git a/CHANGELOG/CHANGELOG-3.5.md b/CHANGELOG/CHANGELOG-3.5.md
index 1c2dd469a..27970688b 100644
--- a/CHANGELOG/CHANGELOG-3.5.md
+++ b/CHANGELOG/CHANGELOG-3.5.md
@@ -1,15 +1,20 @@
 
 
 Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.4.md).
-<hr>
+
+---
 
 ## v3.5.22 (TBC)
 
+### Package `clientv3`
+
+- [Replace `resolver.State.Addresses` with `resolver.State.Endpoint.Addresses`](https://github.com/etcd-io/etcd/pull/19783).
+
 ### Dependencies
 
 - Compile binaries using [go 1.23.8](https://github.com/etcd-io/etcd/pull/19725)
 
-<hr>
+---
 
 ## v3.5.21 (2025-03-27)
 
@@ -18,7 +23,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Bump [github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 to address CVE-2025-30204](https://github.com/etcd-io/etcd/pull/19646).
 - Bump [bump golang.org/x/net from v0.36.0 to v0.38.0 to address CVE-2025-22870 and CVE-2025-22872](https://github.com/etcd-io/etcd/pull/19686).
 
-<hr>
+---
 
 ## v3.5.20 (2025-03-21)
 
@@ -35,7 +40,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 
 - Fix [grpcproxy can get stuck in and endless loop causing high CPU usage](https://github.com/etcd-io/etcd/pull/19562)
 
-<hr>
+---
 
 ## v3.5.19 (2025-03-05)
 
@@ -51,7 +56,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Bump [golang.org/x/crypto to v0.35.0 to address CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19478).
 - Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19530).
 
-<hr>
+---
 
 ## v3.5.18 (2025-01-24)
 
@@ -75,7 +80,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Bump [golang.org/x/crypto to 0.32.0 to address CVE-2024-45337](https://github.com/etcd-io/etcd/pull/19154).
 - Bump [golang.org/x/net to 0.34.0 to address CVE-2024-45338](https://github.com/etcd-io/etcd/pull/19158).
 
-<hr>
+---
 
 ## v3.5.17 (2024-11-12)
 
@@ -88,7 +93,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 ### Dependencies
 - Compile binaries using [go 1.22.9](https://github.com/etcd-io/etcd/pull/18849).
 
-<hr>
+---
 
 ## v3.5.16 (2024-09-10)
 
@@ -101,7 +106,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Compile binaries using [go 1.22.7](https://github.com/etcd-io/etcd/pull/18550).
 - Upgrade [bbolt to v1.3.11](https://github.com/etcd-io/etcd/pull/18489).
 
-<hr>
+---
 
 ## v3.5.15 (2024-07-19)
 
@@ -140,7 +145,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Compile binaries using [go 1.21.10](https://github.com/etcd-io/etcd/pull/17980).
 - Upgrade [bbolt to v1.3.10](https://github.com/etcd-io/etcd/pull/17943).
 
-<hr>
+---
 
 ## v3.5.13 (2024-03-29)
 
@@ -169,7 +174,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 ### Others
 - [Make CGO_ENABLED configurable](https://github.com/etcd-io/etcd/pull/17421).
 
-<hr>
+---
 
 ## v3.5.12 (2024-01-31)
 
@@ -196,7 +201,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Compile binaries using [go 1.20.12](https://github.com/etcd-io/etcd/pull/17077)
 - Fix [CVE-2023-47108](https://github.com/advisories/GHSA-8pgv-569h-w5rw) by [bumping go.opentelemetry.io/otel to 1.20.0 and go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc to 0.46.0](https://github.com/etcd-io/etcd/pull/16946).
 
-<hr>
+---
 
 ## v3.5.10 (2023-10-27)
 
@@ -227,7 +232,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Upgrade gRPC to 1.58.3 in https://github.com/etcd-io/etcd/pull/16625, https://github.com/etcd-io/etcd/pull/16781 and https://github.com/etcd-io/etcd/pull/16790. Note that gRPC server will reject requests with connection header (refer to https://github.com/grpc/grpc-go/pull/4803).
 - Upgrade [bbolt to v1.3.8](https://github.com/etcd-io/etcd/pull/16833)
 
-<hr>
+---
 
 ## v3.5.9 (2023-05-11)
 
@@ -237,7 +242,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 ### Dependencies
 - Compile binaries using [go 1.19.9](https://github.com/etcd-io/etcd/pull/15822).
 
-<hr>
+---
 
 ## v3.5.8 (2023-04-13)
 
@@ -270,7 +275,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - [Remove nsswitch.conf from docker image](https://github.com/etcd-io/etcd/pull/15161)
 - Fix [etcd docker images all tagged with amd64 architecture](https://github.com/etcd-io/etcd/pull/15612)
 
-<hr>
+---
 
 ## v3.5.7 (2023-01-20)
 
@@ -293,7 +298,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - Use [distroless base image](https://github.com/etcd-io/etcd/pull/15016) to address critical Vulnerabilities.
 - Updated [base image from base-debian11 to static-debian11 and removed dependency on busybox](https://github.com/etcd-io/etcd/pull/15037).
 
-<hr>
+---
 
 ## v3.5.6 (2022-11-21)
 
@@ -313,7 +318,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 ### etcd grpc-proxy
 - Add [`etcd grpc-proxy start --listen-cipher-suites`](https://github.com/etcd-io/etcd/pull/14500) flag to support adding configurable cipher list.
 
-<hr>
+---
 
 ## v3.5.5 (2022-09-15)
 
@@ -348,7 +353,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - [Bump golang.org/x/crypto to latest version](https://github.com/etcd-io/etcd/pull/13996) to address [CVE-2022-27191](https://github.com/advisories/GHSA-8c26-wmh5-6g9v).
 - [Bump OpenTelemetry to 1.0.1 and gRPC to 1.41.0](https://github.com/etcd-io/etcd/pull/14312).
 
-<hr>
+---
 
 ## v3.5.4 (2022-04-24)
 
@@ -360,7 +365,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 - [Revert the change of trimming the trailing dot from SRV.Target](https://github.com/etcd-io/etcd/pull/13950) returned by DNS lookup
 
 
-<hr>
+---
 
 ## v3.5.3 (2022-04-13)
 
@@ -381,7 +386,7 @@ Previous change logs can be found at [CHANGELOG-3.4](https://github.com/etcd-io/
 
 - [Always print the raft_term in decimal](https://github.com/etcd-io/etcd/pull/13727) when displaying member list in json.
 
-<hr>
+---
 
 ## [v3.5.2](https://github.com/etcd-io/etcd/releases/tag/v3.5.2) (2022-02-01)
 
@@ -394,7 +399,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.1...v3.5.2) and
 - Fix [assertion failed due to tx closed when recovering v3 backend from a snapshot db](https://github.com/etcd-io/etcd/pull/13501)
 - Fix [segmentation violation(SIGSEGV) error due to premature unlocking of watchableStore](https://github.com/etcd-io/etcd/pull/13541)
 
-<hr>
+---
 
 ## [v3.5.1](https://github.com/etcd-io/etcd/releases/tag/v3.5.1) (2021-10-15)
 
@@ -421,7 +426,7 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.5.1) and
   - [CVE-2019-9893](https://nvd.nist.gov/vuln/detail/CVE-2019-9893): incorrect syscall argument generation in libseccomp
   - [CVE-2021-36159](https://nvd.nist.gov/vuln/detail/CVE-2021-36159): libfetch in apk-tools mishandles numeric strings in FTP and HTTP protocols to allow out of bound reads.
 
-<hr>
+---
 
 ## v3.5.0 (2021-06)
 
@@ -718,5 +723,5 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change.
 - The etcd team has added, a well defined and openly discussed, project [governance](https://github.com/etcd-io/etcd/pull/11175).
 
 
-<hr>
+---
 
diff --git a/CHANGELOG/CHANGELOG-3.6.md b/CHANGELOG/CHANGELOG-3.6.md
index 30a33d741..52cbeed81 100644
--- a/CHANGELOG/CHANGELOG-3.6.md
+++ b/CHANGELOG/CHANGELOG-3.6.md
@@ -2,13 +2,21 @@
 
 Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.5.md).
 
-<hr>
+---
 
 ## v3.6.0 (TBD)
 
-<hr>
+---
 
-## v3.6.0-rc.4 (TBC)
+## v3.6.0-rc.5 (tbd)
+
+### Package `clientv3`
+
+- [Replace `resolver.State.Addresses` with `resolver.State.Endpoint.Addresses`](https://github.com/etcd-io/etcd/pull/19782).
+
+---
+
+## v3.6.0-rc.4 (2025-04-15)
 
 ### etcd server
 
@@ -18,7 +26,7 @@ Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/
 
 - Compile binaries using [go 1.23.8](https://github.com/etcd-io/etcd/pull/19724)
 
-<hr>
+---
 
 ## v3.6.0-rc.3 (2025-03-27)
 
@@ -37,7 +45,7 @@ Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/
 - Bump [github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 to address CVE-2025-30204](https://github.com/etcd-io/etcd/pull/19647).
 - Bump [bump golang.org/x/net from v0.37.0 to v0.38.0 to address CVE-2025-22872](https://github.com/etcd-io/etcd/pull/19687).
 
-<hr>
+---
 
 ## v3.6.0-rc.2 (2025-03-05)
 
@@ -51,7 +59,7 @@ Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/
 - Bump [golang.org/x/net to v0.36.0 to address CVE-2025-22870](https://github.com/etcd-io/etcd/pull/19531).
 - Bump [github.com/grpc-ecosystem/grpc-gateway/v2 to v2.26.3 to fix the issue of etcdserver crashing on receiving REST watch stream requests](https://github.com/etcd-io/etcd/pull/19522).
 
-<hr>
+---
 
 ## v3.6.0-rc.1 (2025-02-25)
 
@@ -67,7 +75,7 @@ Previous change logs can be found at [CHANGELOG-3.5](https://github.com/etcd-io/
 
 - Bump [golang.org/x/crypto to v0.35.0 to address CVE-2025-22869](https://github.com/etcd-io/etcd/pull/19480).
 
-<hr>
+---
 
 ## v3.6.0-rc.0 (2025-02-13)
 
@@ -87,7 +95,6 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v3.6.0).
 - Removed [etcdctl defrag --data-dir](https://github.com/etcd-io/etcd/pull/13793).
 - Removed [etcdctl snapshot status](https://github.com/etcd-io/etcd/pull/13809).
 - Removed [etcdctl snapshot restore](https://github.com/etcd-io/etcd/pull/13809).
-- Removed [etcdutl snapshot save](https://github.com/etcd-io/etcd/pull/13809).
 - Removed [NewZapCoreLoggerBuilder in server/embed](https://github.com/etcd-io/etcd/pull/19404)
 
 ### etcdctl v3
@@ -175,4 +182,4 @@ See [List of metrics](https://etcd.io/docs/latest/metrics/) for all metrics per
 - [Upgrade grpc-gateway from v1 to v2](https://github.com/etcd-io/etcd/pull/16595).
 - [Switch from grpc-ecosystem/go-grpc-prometheus to grpc-ecosystem/go-grpc-middleware/providers/prometheus](https://github.com/etcd-io/etcd/pull/19195).
 
-<hr>
+---
diff --git a/CHANGELOG/CHANGELOG-3.7.md b/CHANGELOG/CHANGELOG-3.7.md
index 6f50b529d..770e7d1dd 100644
--- a/CHANGELOG/CHANGELOG-3.7.md
+++ b/CHANGELOG/CHANGELOG-3.7.md
@@ -2,7 +2,7 @@
 
 Previous change logs can be found at [CHANGELOG-3.6](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.6.md).
 
-<hr>
+---
 
 ## v3.7.0 (TBD)
 
diff --git a/CHANGELOG/CHANGELOG-4.0.md b/CHANGELOG/CHANGELOG-4.0.md
index 860e5efd0..f07bb34fd 100644
--- a/CHANGELOG/CHANGELOG-4.0.md
+++ b/CHANGELOG/CHANGELOG-4.0.md
@@ -2,7 +2,7 @@
 
 Previous change logs can be found at [CHANGELOG-3.x](https://github.com/etcd-io/etcd/blob/main/CHANGELOG/CHANGELOG-3.x.md).
 
-<hr>
+---
 
 ## v4.0.0 (TBD)
 
@@ -40,5 +40,5 @@ See [code changes](https://github.com/etcd-io/etcd/compare/v3.5.0...v4.0.0) and
 - Require [*Go 2*](https://blog.golang.org/go2draft).
 
 
-<hr>
+---
 
diff --git a/Documentation/contributor-guide/roadmap.md b/Documentation/contributor-guide/roadmap.md
index c036e2eb4..d443ea0a4 100644
--- a/Documentation/contributor-guide/roadmap.md
+++ b/Documentation/contributor-guide/roadmap.md
@@ -16,9 +16,9 @@ For a full list of tasks in `v3.6.0`, please see [milestone etcd-v3.6](https://g
 |--------------------------------------------------------------------------------------------------------------------|----------|-------------|--------------------------------------------------------------------------------------------------------------|
 | [Support downgrade](https://github.com/etcd-io/etcd/issues/11716)                                                  | priority/important-soon | In progress | etcd will support downgrade starting from 3.6.0. But it will also support offline downgrade from 3.5 to 3.4. |
 | [StoreV2 deprecation](https://github.com/etcd-io/etcd/issues/12913)                                                | priority/important-soon | In progress | This task will be covered in both 3.6 and 3.7.                                                               |
-| [Release raft 3.6.0](https://github.com/etcd-io/raft/issues/89)                                                    | priority/important-soon | Not started | etcd 3.6.0 will depends on raft 3.6.0                                                                        |
-| [Release bbolt 1.4.0](https://github.com/etcd-io/bbolt/issues/553)                                                 | priority/important-soon | Not started | etcd 3.6.0 will depends on bbolt 1.4.0                                                                       |
-| [Support /livez and /readyz endpoints](https://github.com/etcd-io/etcd/issues/16007)                               | priority/important-longterm | In progress | It provides clearer APIs, and can also work around the stalled writes issue                                   |
+| [Release raft 3.6.0](https://github.com/etcd-io/raft/issues/89)                                                    | priority/important-soon | Completed | etcd 3.6.0 will depends on raft 3.6.0                                                                        |
+| [Release bbolt 1.4.0](https://github.com/etcd-io/bbolt/issues/553)                                                 | priority/important-soon | Completed | etcd 3.6.0 will depends on bbolt 1.4.0                                                                       |
+| [Support /livez and /readyz endpoints](https://github.com/etcd-io/etcd/issues/16007)                               | priority/important-longterm | Completed | It provides clearer APIs, and can also work around the stalled writes issue                                   |
 | [Bump gRPC](https://github.com/etcd-io/etcd/issues/16290)                                                          | priority/important-longterm | Completed   | It isn't guaranteed to be resolved in 3.6, and might be postponed to 3.7 depending on the effort and risk.   |
 | [Deprecate grpc-gateway or bump it](https://github.com/etcd-io/etcd/issues/14499)                                  | priority/important-longterm | Completed   | It isn't guaranteed to be resolved in 3.6, and might be postponed to 3.7 depending on the effort and risk.   |
 | [bbolt: Add logger into bbolt](https://github.com/etcd-io/bbolt/issues/509)                                        | priority/important-longterm | Completed   | It's important to diagnose bbolt issues                                                                      |
diff --git a/api/go.mod b/api/go.mod
index 18f795c5e..f9b123768 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
 	github.com/stretchr/testify v1.10.0
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.72.0
 	google.golang.org/protobuf v1.36.6
 )
 
@@ -21,7 +21,7 @@ require (
 	github.com/rogpeppe/go-internal v1.14.1 // indirect
 	go.opentelemetry.io/otel v1.35.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.35.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
diff --git a/api/go.sum b/api/go.sum
index c3012dff6..c341fdd12 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -51,8 +51,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -77,8 +77,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/bill-of-materials.json b/bill-of-materials.json
index 584a53d42..d77bd6a83 100644
--- a/bill-of-materials.json
+++ b/bill-of-materials.json
@@ -260,33 +260,6 @@
 			}
 		]
 	},
-	{
-		"project": "github.com/klauspost/compress",
-		"licenses": [
-			{
-				"type": "Apache License 2.0",
-				"confidence": 0.9376299376299376
-			}
-		]
-	},
-	{
-		"project": "github.com/klauspost/compress/internal/snapref",
-		"licenses": [
-			{
-				"type": "BSD 3-clause \"New\" or \"Revised\" License",
-				"confidence": 0.9663865546218487
-			}
-		]
-	},
-	{
-		"project": "github.com/klauspost/compress/zstd/internal/xxhash",
-		"licenses": [
-			{
-				"type": "MIT License",
-				"confidence": 1
-			}
-		]
-	},
 	{
 		"project": "github.com/mattn/go-colorable",
 		"licenses": [
diff --git a/client/v3/go.mod b/client/v3/go.mod
index d94b7d4df..0b155cf31 100644
--- a/client/v3/go.mod
+++ b/client/v3/go.mod
@@ -8,12 +8,12 @@ require (
 	github.com/coreos/go-semver v0.3.1
 	github.com/dustin/go-humanize v1.0.1
 	github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
-	github.com/prometheus/client_golang v1.21.1
+	github.com/prometheus/client_golang v1.22.0
 	github.com/stretchr/testify v1.10.0
 	go.etcd.io/etcd/api/v3 v3.6.0-alpha.0
 	go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
 	go.uber.org/zap v1.27.0
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.72.0
 	sigs.k8s.io/yaml v1.4.0
 )
 
@@ -26,16 +26,15 @@ require (
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
-	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_model v0.6.1 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.63.0 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
 	go.opentelemetry.io/otel/metric v1.35.0 // indirect
 	go.opentelemetry.io/otel/trace v1.35.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
diff --git a/client/v3/go.sum b/client/v3/go.sum
index e0d840e25..923fa810c 100644
--- a/client/v3/go.sum
+++ b/client/v3/go.sum
@@ -44,10 +44,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@@ -85,8 +85,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -111,8 +111,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/client/v3/internal/resolver/resolver.go b/client/v3/internal/resolver/resolver.go
index b5c9de007..c7f9fb1ae 100644
--- a/client/v3/internal/resolver/resolver.go
+++ b/client/v3/internal/resolver/resolver.go
@@ -60,16 +60,28 @@ func (r *EtcdManualResolver) SetEndpoints(endpoints []string) {
 }
 
 func (r EtcdManualResolver) updateState() {
-	if r.CC != nil {
-		addresses := make([]resolver.Address, len(r.endpoints))
+	if getCC(r) != nil {
+		eps := make([]resolver.Endpoint, len(r.endpoints))
 		for i, ep := range r.endpoints {
 			addr, serverName := endpoint.Interpret(ep)
-			addresses[i] = resolver.Address{Addr: addr, ServerName: serverName}
+			eps[i] = resolver.Endpoint{Addresses: []resolver.Address{
+				{Addr: addr, ServerName: serverName},
+			}}
 		}
 		state := resolver.State{
-			Addresses:     addresses,
+			Endpoints:     eps,
 			ServiceConfig: r.serviceConfig,
 		}
 		r.UpdateState(state)
 	}
 }
+
+func getCC(r EtcdManualResolver) (cc resolver.ClientConn) {
+	defer func() {
+		if rec := recover(); rec != nil {
+			cc = nil
+		}
+	}()
+
+	return r.CC()
+}
diff --git a/client/v3/naming/resolver/resolver.go b/client/v3/naming/resolver/resolver.go
index f42b2b0b6..f8629aaaa 100644
--- a/client/v3/naming/resolver/resolver.go
+++ b/client/v3/naming/resolver/resolver.go
@@ -100,22 +100,26 @@ func (r *resolver) watch() {
 				}
 			}
 
-			addrs := convertToGRPCAddress(allUps)
-			r.cc.UpdateState(gresolver.State{Addresses: addrs})
+			eps := convertToGRPCEndpoint(allUps)
+			r.cc.UpdateState(gresolver.State{Endpoints: eps})
 		}
 	}
 }
 
-func convertToGRPCAddress(ups map[string]*endpoints.Update) []gresolver.Address {
-	var addrs []gresolver.Address
+func convertToGRPCEndpoint(ups map[string]*endpoints.Update) []gresolver.Endpoint {
+	var eps []gresolver.Endpoint
 	for _, up := range ups {
-		addr := gresolver.Address{
-			Addr:     up.Endpoint.Addr,
-			Metadata: up.Endpoint.Metadata,
+		ep := gresolver.Endpoint{
+			Addresses: []gresolver.Address{
+				{
+					Addr:     up.Endpoint.Addr,
+					Metadata: up.Endpoint.Metadata,
+				},
+			},
 		}
-		addrs = append(addrs, addr)
+		eps = append(eps, ep)
 	}
-	return addrs
+	return eps
 }
 
 // ResolveNow is a no-op here.
diff --git a/etcdctl/go.mod b/etcdctl/go.mod
index a4de5b848..7b1660cec 100644
--- a/etcdctl/go.mod
+++ b/etcdctl/go.mod
@@ -18,7 +18,7 @@ require (
 	go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0
 	go.uber.org/zap v1.27.0
 	golang.org/x/time v0.11.0
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.72.0
 )
 
 require (
@@ -37,7 +37,7 @@ require (
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
diff --git a/etcdctl/go.sum b/etcdctl/go.sum
index e70850016..6e14c88f7 100644
--- a/etcdctl/go.sum
+++ b/etcdctl/go.sum
@@ -42,8 +42,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
-github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -61,10 +59,10 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@@ -110,8 +108,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -139,8 +137,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/etcdutl/go.mod b/etcdutl/go.mod
index 4ad48c367..8fc98f941 100644
--- a/etcdutl/go.mod
+++ b/etcdutl/go.mod
@@ -57,12 +57,11 @@ require (
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jonboulle/clockwork v0.5.0 // indirect
-	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_golang v1.21.1 // indirect
-	github.com/prometheus/client_model v0.6.1 // indirect
+	github.com/prometheus/client_golang v1.22.0 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.63.0 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
@@ -83,13 +82,13 @@ require (
 	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	golang.org/x/crypto v0.37.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	golang.org/x/time v0.11.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
-	google.golang.org/grpc v1.71.1 // indirect
+	google.golang.org/grpc v1.72.0 // indirect
 	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/etcdutl/go.sum b/etcdutl/go.sum
index 4602bb98b..55c6d8ed9 100644
--- a/etcdutl/go.sum
+++ b/etcdutl/go.sum
@@ -66,10 +66,10 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@@ -140,8 +140,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -171,8 +171,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/go.mod b/go.mod
index 27d3b8a8d..e6f18019b 100644
--- a/go.mod
+++ b/go.mod
@@ -36,7 +36,7 @@ require (
 	go.etcd.io/raft/v3 v3.6.0
 	go.uber.org/zap v1.27.0
 	golang.org/x/time v0.11.0
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.72.0
 	google.golang.org/protobuf v1.36.6
 )
 
@@ -64,15 +64,14 @@ require (
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jonboulle/clockwork v0.5.0 // indirect
-	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/mattn/go-colorable v0.1.14 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/olekukonko/tablewriter v0.0.5 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_golang v1.21.1 // indirect
-	github.com/prometheus/client_model v0.6.1 // indirect
+	github.com/prometheus/client_golang v1.22.0 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.63.0 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
@@ -93,7 +92,7 @@ require (
 	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	golang.org/x/crypto v0.37.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
diff --git a/go.sum b/go.sum
index 88823b991..2c1ddde37 100644
--- a/go.sum
+++ b/go.sum
@@ -104,11 +104,11 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@@ -196,8 +196,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -247,8 +247,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/featuregate/feature_gate.go b/pkg/featuregate/feature_gate.go
index 343668fc3..2f3eaf558 100644
--- a/pkg/featuregate/feature_gate.go
+++ b/pkg/featuregate/feature_gate.go
@@ -166,7 +166,7 @@ func setUnsetBetaGates(known map[Feature]FeatureSpec, enabled map[Feature]bool,
 // Set, String, and Type implement pflag.Value
 var _ pflag.Value = &featureGate{}
 
-func New(name string, lg *zap.Logger) *featureGate {
+func New(name string, lg *zap.Logger) MutableFeatureGate {
 	if lg == nil {
 		lg = zap.NewNop()
 	}
diff --git a/pkg/go.mod b/pkg/go.mod
index ea78ef92f..3a3e3c3e4 100644
--- a/pkg/go.mod
+++ b/pkg/go.mod
@@ -12,7 +12,7 @@ require (
 	github.com/stretchr/testify v1.10.0
 	go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
 	go.uber.org/zap v1.27.0
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.72.0
 )
 
 require (
@@ -24,7 +24,7 @@ require (
 	go.opentelemetry.io/otel v1.35.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.35.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
diff --git a/pkg/go.sum b/pkg/go.sum
index b1082271f..8a914e749 100644
--- a/pkg/go.sum
+++ b/pkg/go.sum
@@ -53,16 +53,16 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
 golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
 golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/server/etcdserver/txn/txn.go b/server/etcdserver/txn/txn.go
index 51f70a06a..9e8d397cb 100644
--- a/server/etcdserver/txn/txn.go
+++ b/server/etcdserver/txn/txn.go
@@ -33,16 +33,10 @@ import (
 )
 
 func Put(ctx context.Context, lg *zap.Logger, lessor lease.Lessor, kv mvcc.KV, p *pb.PutRequest) (resp *pb.PutResponse, trace *traceutil.Trace, err error) {
-	trace = traceutil.Get(ctx)
-	// create put tracing if the trace in context is empty
-	if trace.IsEmpty() {
-		trace = traceutil.New("put",
-			lg,
-			traceutil.Field{Key: "key", Value: string(p.Key)},
-			traceutil.Field{Key: "req_size", Value: p.Size()},
-		)
-		ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace)
-	}
+	ctx, trace = ensureTrace(ctx, lg, "put",
+		traceutil.Field{Key: "key", Value: string(p.Key)},
+		traceutil.Field{Key: "req_size", Value: p.Size()},
+	)
 	leaseID := lease.LeaseID(p.Lease)
 	if leaseID != lease.NoLease {
 		if l := lessor.Lookup(leaseID); l == nil {
@@ -95,16 +89,10 @@ func put(ctx context.Context, txnWrite mvcc.TxnWrite, p *pb.PutRequest) (resp *p
 }
 
 func DeleteRange(ctx context.Context, lg *zap.Logger, kv mvcc.KV, dr *pb.DeleteRangeRequest) (resp *pb.DeleteRangeResponse, trace *traceutil.Trace, err error) {
-	trace = traceutil.Get(ctx)
-	// create delete tracing if the trace in context is empty
-	if trace.IsEmpty() {
-		trace = traceutil.New("delete_range",
-			lg,
-			traceutil.Field{Key: "key", Value: string(dr.Key)},
-			traceutil.Field{Key: "range_end", Value: string(dr.RangeEnd)},
-		)
-		ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace)
-	}
+	ctx, trace = ensureTrace(ctx, lg, "delete_range",
+		traceutil.Field{Key: "key", Value: string(dr.Key)},
+		traceutil.Field{Key: "range_end", Value: string(dr.RangeEnd)},
+	)
 	txnWrite := kv.Write(trace)
 	defer txnWrite.End()
 	resp, err = deleteRange(ctx, txnWrite, dr)
@@ -134,11 +122,7 @@ func deleteRange(ctx context.Context, txnWrite mvcc.TxnWrite, dr *pb.DeleteRange
 }
 
 func Range(ctx context.Context, lg *zap.Logger, kv mvcc.KV, r *pb.RangeRequest) (resp *pb.RangeResponse, trace *traceutil.Trace, err error) {
-	trace = traceutil.Get(ctx)
-	if trace.IsEmpty() {
-		trace = traceutil.New("range", lg)
-		ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace)
-	}
+	ctx, trace = ensureTrace(ctx, lg, "range")
 	defer func(start time.Time) {
 		success := err == nil
 		RangeSecObserve(success, time.Since(start))
@@ -249,12 +233,8 @@ func executeRange(ctx context.Context, lg *zap.Logger, txnRead mvcc.TxnRead, r *
 	return resp, nil
 }
 
-func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWithSharedBuffer bool, kv mvcc.KV, lessor lease.Lessor) (*pb.TxnResponse, *traceutil.Trace, error) {
-	trace := traceutil.Get(ctx)
-	if trace.IsEmpty() {
-		trace = traceutil.New("transaction", lg)
-		ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace)
-	}
+func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWithSharedBuffer bool, kv mvcc.KV, lessor lease.Lessor) (txnResp *pb.TxnResponse, trace *traceutil.Trace, err error) {
+	ctx, trace = ensureTrace(ctx, lg, "transaction")
 	isWrite := !IsTxnReadonly(rt)
 	// When the transaction contains write operations, we use ReadTx instead of
 	// ConcurrentReadTx to avoid extra overhead of copying buffer.
@@ -275,7 +255,7 @@ func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWit
 	if isWrite {
 		trace.AddField(traceutil.Field{Key: "read_only", Value: false})
 	}
-	_, err := checkTxn(txnRead, rt, lessor, txnPath)
+	_, err = checkTxn(txnRead, rt, lessor, txnPath)
 	if err != nil {
 		txnRead.End()
 		return nil, nil, err
@@ -292,7 +272,7 @@ func Txn(ctx context.Context, lg *zap.Logger, rt *pb.TxnRequest, txnModeWriteWit
 	} else {
 		txnWrite = mvcc.NewReadOnlyTxnWrite(txnRead)
 	}
-	txnResp, err := txn(ctx, lg, txnWrite, rt, isWrite, txnPath)
+	txnResp, err = txn(ctx, lg, txnWrite, rt, isWrite, txnPath)
 	txnWrite.End()
 
 	trace.AddField(
@@ -721,3 +701,15 @@ func checkTxnReqsPermission(as auth.AuthStore, ai *auth.AuthInfo, reqs []*pb.Req
 
 	return nil
 }
+
+func ensureTrace(ctx context.Context, lg *zap.Logger, operation string, fields ...traceutil.Field) (context.Context, *traceutil.Trace) {
+	trace := traceutil.Get(ctx)
+	if trace.IsEmpty() {
+		trace = traceutil.New(operation,
+			lg,
+			fields...,
+		)
+		ctx = context.WithValue(ctx, traceutil.TraceKey{}, trace)
+	}
+	return ctx, trace
+}
diff --git a/server/go.mod b/server/go.mod
index a825dc203..0f8720e07 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -18,8 +18,8 @@ require (
 	github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
 	github.com/jonboulle/clockwork v0.5.0
-	github.com/prometheus/client_golang v1.21.1
-	github.com/prometheus/client_model v0.6.1
+	github.com/prometheus/client_golang v1.22.0
+	github.com/prometheus/client_model v0.6.2
 	github.com/soheilhy/cmux v0.1.5
 	github.com/spf13/cobra v1.9.1
 	github.com/stretchr/testify v1.10.0
@@ -38,10 +38,10 @@ require (
 	go.opentelemetry.io/otel/sdk v1.35.0
 	go.uber.org/zap v1.27.0
 	golang.org/x/crypto v0.37.0
-	golang.org/x/net v0.38.0
+	golang.org/x/net v0.39.0
 	golang.org/x/time v0.11.0
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.72.0
 	google.golang.org/protobuf v1.36.6
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 	sigs.k8s.io/yaml v1.4.0
@@ -58,7 +58,6 @@ require (
 	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
-	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
diff --git a/server/go.sum b/server/go.sum
index fe7264fbd..5db908757 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -87,11 +87,11 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@@ -174,8 +174,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -224,8 +224,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/server/storage/mvcc/kvstore_compaction.go b/server/storage/mvcc/kvstore_compaction.go
index 5175e0927..9c555fce5 100644
--- a/server/storage/mvcc/kvstore_compaction.go
+++ b/server/storage/mvcc/kvstore_compaction.go
@@ -49,6 +49,7 @@ func (s *store) scheduleCompaction(compactMainRev, prevCompactRev int64) (KeyVal
 
 		tx := s.b.BatchTx()
 		tx.LockOutsideApply()
+		// gofail: var compactAfterAcquiredBatchTxLock struct{}
 		keys, values := tx.UnsafeRange(schema.Key, last, end, int64(batchNum))
 		for i := range keys {
 			rev = BytesToRev(keys[i])
@@ -63,6 +64,7 @@ func (s *store) scheduleCompaction(compactMainRev, prevCompactRev int64) (KeyVal
 			// gofail: var compactBeforeSetFinishedCompact struct{}
 			UnsafeSetFinishedCompact(tx, compactMainRev)
 			tx.Unlock()
+			dbCompactionPauseMs.Observe(float64(time.Since(start) / time.Millisecond))
 			// gofail: var compactAfterSetFinishedCompact struct{}
 			hash := h.Hash()
 			size, sizeInUse := s.b.Size(), s.b.SizeInUse()
diff --git a/server/storage/schema/membership.go b/server/storage/schema/membership.go
index c79ab8b76..726657ba9 100644
--- a/server/storage/schema/membership.go
+++ b/server/storage/schema/membership.go
@@ -37,6 +37,9 @@ type membershipBackend struct {
 	be backend.Backend
 }
 
+// NewMembershipBackend returns a new membership backend
+// Refer to https://github.com/etcd-io/etcd/pull/19343#discussion_r1958056718
+// revive:disable-next-line:unexported-return
 func NewMembershipBackend(lg *zap.Logger, be backend.Backend) *membershipBackend {
 	return &membershipBackend{
 		lg: lg,
diff --git a/tools/.golangci.yaml b/tools/.golangci.yaml
index a296a5ac4..d5a87964e 100644
--- a/tools/.golangci.yaml
+++ b/tools/.golangci.yaml
@@ -31,6 +31,7 @@ linters:
     - staticcheck
     - stylecheck
     - testifylint
+    - thelper
     - unconvert # Remove unnecessary type conversions
     - unparam
     - unused
@@ -102,5 +103,18 @@ linters-settings: # please keep this alphabetized
       # to always require f-functions for stretchr/testify, but not for golang standard lib.
       # Also refer to https://github.com/etcd-io/etcd/pull/18741#issuecomment-2422395914
       require-f-funcs: true
+  thelper:
+    test:
+      first: false
+      begin: false
+    benchmark:
+      first: false
+      begin: false
+    tb:
+      first: false
+      begin: false
+    fuzz:
+      first: false
+      begin: false
   usetesting:
     os-mkdir-temp: false
diff --git a/tools/.markdownlint.jsonc b/tools/.markdownlint.jsonc
index 9aae9cb7a..d6b36639c 100644
--- a/tools/.markdownlint.jsonc
+++ b/tools/.markdownlint.jsonc
@@ -184,12 +184,7 @@
     },
   
     // MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md041.md
-    "MD041": {
-      // Heading level
-      "level": 1,
-      // RegExp for matching title in front matter
-      "front_matter_title": "^\\s*title\\s*[:=]"
-    },
+    "MD041": false,
   
     // MD042/no-empty-links : No empty links : https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md042.md
     "MD042": true,
diff --git a/tools/mod/go.mod b/tools/mod/go.mod
index c90c5ca1f..e75bdd02c 100644
--- a/tools/mod/go.mod
+++ b/tools/mod/go.mod
@@ -5,7 +5,7 @@ go 1.24
 toolchain go1.24.2
 
 require (
-	github.com/alexfalkowski/gocovmerge v1.6.0
+	github.com/alexfalkowski/gocovmerge v1.7.0
 	github.com/appscodelabs/license-bill-of-materials v0.0.0-20220707232035-6018e0c5287c
 	github.com/chzchzchz/goword v0.0.0-20170907005317-a9744cb52b03
 	github.com/cloudflare/cfssl v1.6.5
@@ -119,7 +119,6 @@ require (
 	github.com/kisielk/errcheck v1.9.0 // indirect
 	github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 // indirect
 	github.com/kkHAIKE/contextcheck v1.1.6 // indirect
-	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/kulti/thelper v0.6.3 // indirect
 	github.com/kunwardeep/paralleltest v1.0.10 // indirect
 	github.com/lasiar/canonicalheader v1.1.2 // indirect
@@ -153,8 +152,8 @@ require (
 	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/polyfloyd/go-errorlint v1.7.1 // indirect
-	github.com/prometheus/client_golang v1.21.1 // indirect
-	github.com/prometheus/client_model v0.6.1 // indirect
+	github.com/prometheus/client_golang v1.22.0 // indirect
+	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.63.0 // indirect
 	github.com/prometheus/procfs v0.15.1 // indirect
 	github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
@@ -216,15 +215,15 @@ require (
 	golang.org/x/crypto v0.37.0 // indirect
 	golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
 	golang.org/x/mod v0.24.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sync v0.13.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/term v0.31.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
-	golang.org/x/tools v0.31.0 // indirect
+	golang.org/x/tools v0.32.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
-	google.golang.org/grpc v1.71.1 // indirect
+	google.golang.org/grpc v1.72.0 // indirect
 	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/tools/mod/go.sum b/tools/mod/go.sum
index cb8dccdc5..bdd7dd9ec 100644
--- a/tools/mod/go.sum
+++ b/tools/mod/go.sum
@@ -32,8 +32,8 @@ github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsr
 github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/alexfalkowski/gocovmerge v1.6.0 h1:ym50f/KroVYUKLUMI3GcnxBj77dxJP9G57Urrx63oO8=
-github.com/alexfalkowski/gocovmerge v1.6.0/go.mod h1:whLxUVgIW64kj63uTPFgjlFHLa3bDrDpzBbitIjqRs4=
+github.com/alexfalkowski/gocovmerge v1.7.0 h1:R/zzCbFZ91WDea7UcoQdEflfv406ARu94eeLCYcuP+M=
+github.com/alexfalkowski/gocovmerge v1.7.0/go.mod h1:pnseUyIoFOj8DWSE1n7lZyGceAf3Y/JM8KebVaQFAVA=
 github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU=
 github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU=
 github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
@@ -251,8 +251,6 @@ github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw
 github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
 github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
 github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
-github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
-github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -349,10 +347,10 @@ github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L
 github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8=
 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@@ -571,8 +569,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
 golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -654,8 +652,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
-golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
-golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
+golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
+golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -664,8 +662,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/tools/rw-heatmaps/cmd/root.go b/tools/rw-heatmaps/cmd/root.go
index 49b98a9fc..722bddb20 100644
--- a/tools/rw-heatmaps/cmd/root.go
+++ b/tools/rw-heatmaps/cmd/root.go
@@ -33,6 +33,8 @@ var (
 	ErrMissingInputFileArg = fmt.Errorf("missing input file argument")
 	// ErrInvalidOutputFormat is returned when the output format is invalid.
 	ErrInvalidOutputFormat = fmt.Errorf("invalid output format, must be one of png, jpg, jpeg, tiff")
+	// ErrInvalidChartTYpe is returned when the chart type is invalid.
+	ErrInvalidChartType = fmt.Errorf("invalid chart type, must be one of line, heatmap")
 )
 
 // NewRootCommand returns the root command for the rw-heatmaps tool.
@@ -56,6 +58,10 @@ func NewRootCommand() *cobra.Command {
 				}
 			}
 
+			if o.chartType == "line" {
+				return chart.PlotLineCharts(datasets, o.title, o.outputImageFile, o.outputFormat)
+			}
+
 			return chart.PlotHeatMaps(datasets, o.title, o.outputImageFile, o.outputFormat, o.zeroCentered)
 		},
 	}
@@ -70,6 +76,7 @@ type options struct {
 	outputImageFile string
 	outputFormat    string
 	zeroCentered    bool
+	chartType       string
 }
 
 // newOptions returns a new options for the command with the default values applied.
@@ -77,6 +84,7 @@ func newOptions() options {
 	return options{
 		outputFormat: "jpg",
 		zeroCentered: true,
+		chartType:    "heatmap",
 	}
 }
 
@@ -86,6 +94,7 @@ func (o *options) AddFlags(fs *pflag.FlagSet) {
 	fs.StringVarP(&o.outputImageFile, "output-image-file", "o", o.outputImageFile, "output image filename (required)")
 	fs.StringVarP(&o.outputFormat, "output-format", "f", o.outputFormat, "output image file format")
 	fs.BoolVar(&o.zeroCentered, "zero-centered", o.zeroCentered, "plot the improvement graph with white color represents 0.0")
+	fs.StringVarP(&o.chartType, "chart-type", "c", o.chartType, `type of chart to plot ["line", or "heatmap"]`)
 }
 
 // Validate returns an error if the options are invalid.
@@ -101,5 +110,10 @@ func (o *options) Validate() error {
 	default:
 		return ErrInvalidOutputFormat
 	}
+	switch o.chartType {
+	case "line", "heatmap":
+	default:
+		return ErrInvalidChartType
+	}
 	return nil
 }
diff --git a/tools/rw-heatmaps/pkg/chart/line_chart.go b/tools/rw-heatmaps/pkg/chart/line_chart.go
new file mode 100644
index 000000000..b7421433c
--- /dev/null
+++ b/tools/rw-heatmaps/pkg/chart/line_chart.go
@@ -0,0 +1,226 @@
+// Copyright 2025 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package chart
+
+import (
+	"cmp"
+	"fmt"
+	"image/color"
+	"slices"
+	"sort"
+
+	"gonum.org/v1/plot"
+	"gonum.org/v1/plot/font"
+	"gonum.org/v1/plot/plotter"
+	"gonum.org/v1/plot/plotutil"
+	"gonum.org/v1/plot/vg"
+	"gonum.org/v1/plot/vg/draw"
+	"gonum.org/v1/plot/vg/vgimg"
+
+	"go.etcd.io/etcd/tools/rw-heatmaps/v3/pkg/dataset"
+)
+
+// PlotLineCharts creates a new line chart.
+func PlotLineCharts(datasets []*dataset.DataSet, title, outputImageFile, outputFormat string) error {
+	plot.DefaultFont = font.Font{
+		Typeface: "Liberation",
+		Variant:  "Sans",
+	}
+
+	canvas := plotLineChart(datasets, title)
+	return saveCanvas(canvas, "readwrite", outputImageFile, outputFormat)
+}
+
+func plotLineChart(datasets []*dataset.DataSet, title string) *vgimg.Canvas {
+	ratiosLength := func() int {
+		max := slices.MaxFunc(datasets, func(a, b *dataset.DataSet) int {
+			return cmp.Compare(len(a.GetSortedRatios()), len(b.GetSortedRatios()))
+		})
+		return len(max.GetSortedRatios())
+	}()
+
+	// Make a nx1 grid of line charts.
+	const cols = 1
+	rows := ratiosLength
+
+	// Set the width and height of the canvas.
+	width, height := 30*vg.Centimeter, 15*font.Length(ratiosLength)*vg.Centimeter
+
+	canvas := vgimg.New(width, height)
+	dc := draw.New(canvas)
+
+	// Create a tiled layout for the plots.
+	t := draw.Tiles{
+		Rows:      rows,
+		Cols:      cols,
+		PadX:      vg.Millimeter * 4,
+		PadY:      vg.Millimeter * 4,
+		PadTop:    vg.Millimeter * 15,
+		PadBottom: vg.Millimeter * 2,
+		PadLeft:   vg.Millimeter * 2,
+		PadRight:  vg.Millimeter * 2,
+	}
+
+	plots := make([][]*plot.Plot, rows)
+	legends := make([]plot.Legend, rows)
+	for i := range plots {
+		plots[i] = make([]*plot.Plot, cols)
+	}
+
+	// Load records into the grid.
+	ratios := slices.MaxFunc(datasets, func(a, b *dataset.DataSet) int {
+		return cmp.Compare(len(a.GetSortedRatios()), len(b.GetSortedRatios()))
+	}).GetSortedRatios()
+
+	for row, ratio := range ratios {
+		var records [][]dataset.DataRecord
+		var fileNames []string
+		for _, d := range datasets {
+			records = append(records, d.Records[ratio])
+			fileNames = append(fileNames, d.FileName)
+		}
+		p, l := plotIndividualLineChart(fmt.Sprintf("R/W Ratio %0.04f", ratio), records, fileNames)
+		plots[row] = []*plot.Plot{p}
+		legends[row] = l
+	}
+
+	// Fill the canvas with the plots and legends.
+	canvases := plot.Align(plots, t, dc)
+	for i := 0; i < rows; i++ {
+		// Continue if there is no plot in the current cell (incomplete data).
+		if plots[i][0] == nil {
+			continue
+		}
+
+		l := legends[i]
+		r := l.Rectangle(canvases[i][0])
+		legendWidth := r.Max.X - r.Min.X
+		// Adjust the legend down a little.
+		l.YOffs = plots[i][0].Title.TextStyle.FontExtents().Height * 3
+		l.Draw(canvases[i][0])
+
+		c := draw.Crop(canvases[i][0], 0, -legendWidth-vg.Millimeter, 0, 0)
+		plots[i][0].Draw(c)
+	}
+
+	// Add the title and parameter legend.
+	l := plot.NewLegend()
+	l.Add(title)
+	for _, d := range datasets {
+		l.Add(fmt.Sprintf("%s: %s", d.FileName, d.Param))
+	}
+	l.Top = true
+	l.Left = true
+	l.Draw(dc)
+
+	return canvas
+}
+
+func plotIndividualLineChart(title string, records [][]dataset.DataRecord, fileNames []string) (*plot.Plot, plot.Legend) {
+	p := plot.New()
+	p.Title.Text = title
+	p.X.Label.Text = "Connections Amount"
+	p.X.Scale = plot.LogScale{}
+	p.X.Tick.Marker = pow2Ticks{}
+	p.Y.Label.Text = "QPS (Requests/sec)"
+	p.Y.Scale = plot.LogScale{}
+	p.Y.Tick.Marker = pow2Ticks{}
+
+	legend := plot.NewLegend()
+
+	values := getSortedValueSizes(records...)
+	for i, rs := range records {
+		rec := make(map[int64][]dataset.DataRecord)
+		for _, r := range rs {
+			rec[r.ValueSize] = append(rec[r.ValueSize], r)
+		}
+		if len(records) > 1 {
+			addValues(p, &legend, values, rec, i, fileNames[i])
+		} else {
+			addValues(p, &legend, values, rec, i, "")
+		}
+	}
+
+	return p, legend
+}
+
+func getSortedValueSizes(records ...[]dataset.DataRecord) []int {
+	valueMap := make(map[int64]struct{})
+	for _, rs := range records {
+		for _, r := range rs {
+			valueMap[r.ValueSize] = struct{}{}
+		}
+	}
+
+	var values []int
+	for v := range valueMap {
+		values = append(values, int(v))
+	}
+	sort.Ints(values)
+
+	return values
+}
+
+func addValues(p *plot.Plot, legend *plot.Legend, values []int, rec map[int64][]dataset.DataRecord, index int, fileName string) {
+	for i, value := range values {
+		r := rec[int64(value)]
+		readPts := make(plotter.XYs, len(r))
+		writePts := make(plotter.XYs, len(r))
+		for i, record := range r {
+			writePts[i].X = float64(record.ConnSize)
+			readPts[i].X = writePts[i].X
+			readPts[i].Y = record.AvgRead
+			writePts[i].Y = record.AvgWrite
+		}
+
+		readLine, s, err := plotter.NewLinePoints(readPts)
+		if err != nil {
+			panic(err)
+		}
+		if index == 0 {
+			readLine.Color = plotutil.Color(0)
+		} else {
+			readLine.Color = plotutil.Color(2)
+		}
+		readLine.Width = vg.Length(vg.Millimeter * 0.15 * vg.Length(i+1))
+		readLine.Dashes = []vg.Length{vg.Points(6), vg.Points(2)}
+		s.Color = readLine.Color
+		p.Add(readLine, s)
+
+		writeLine, s, err := plotter.NewLinePoints(writePts)
+		if err != nil {
+			panic(err)
+		}
+		if index == 0 {
+			writeLine.Color = plotutil.Color(0)
+		} else {
+			writeLine.Color = plotutil.Color(2)
+		}
+		writeLine.Width = vg.Length(vg.Millimeter * 0.15 * vg.Length(i+1))
+		s.Color = writeLine.Color
+		p.Add(writeLine, s)
+
+		if index == 0 {
+			l, _, _ := plotter.NewLinePoints(writePts)
+			l.Color = color.RGBA{0, 0, 0, 255}
+			l.Width = vg.Length(vg.Millimeter * 0.15 * vg.Length(i+1))
+			legend.Add(fmt.Sprintf("%d", value), plot.Thumbnailer(l))
+		}
+		if i == len(values)-1 {
+			legend.Add(fmt.Sprintf("read %s", fileName), plot.Thumbnailer(readLine))
+			legend.Add(fmt.Sprintf("write %s", fileName), plot.Thumbnailer(writeLine))
+		}
+	}
+}
diff --git a/tools/testgrid-analysis/go.mod b/tools/testgrid-analysis/go.mod
index d3c1d412f..8c017f3a8 100644
--- a/tools/testgrid-analysis/go.mod
+++ b/tools/testgrid-analysis/go.mod
@@ -18,9 +18,9 @@ require (
 	github.com/spf13/pflag v1.0.6 // indirect
 	go.opentelemetry.io/otel v1.35.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.35.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
-	google.golang.org/grpc v1.71.1 // indirect
+	google.golang.org/grpc v1.72.0 // indirect
 )
diff --git a/tools/testgrid-analysis/go.sum b/tools/testgrid-analysis/go.sum
index cdf143252..12d2bc8e6 100644
--- a/tools/testgrid-analysis/go.sum
+++ b/tools/testgrid-analysis/go.sum
@@ -1336,8 +1336,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1888,8 +1888,8 @@ google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGO
 google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=



Test patch:
diff --git a/pkg/adt/interval_tree_test.go b/pkg/adt/interval_tree_test.go
index 8eb0246ad..012e044e0 100644
--- a/pkg/adt/interval_tree_test.go
+++ b/pkg/adt/interval_tree_test.go
@@ -269,6 +269,36 @@ func TestIntervalTreeDelete(t *testing.T) {
 	require.Truef(t, reflect.DeepEqual(expectedAfterDelete11, visitsAfterDelete11), "level order after deleting '11' expected %v, got %v", expectedAfterDelete11, visitsAfterDelete11)
 }
 
+func TestIntervalTreeFind(t *testing.T) {
+	ivt := NewIntervalTree()
+	ivl1 := NewInt64Interval(3, 6)
+	val := 123
+	assert.Nilf(t, ivt.Find(ivl1), "find for %v expected nil on empty tree", ivl1)
+	// insert interval [3, 6) into tree
+	ivt.Insert(ivl1, val)
+	// check cases of expected find matches and non-matches
+	assert.NotNilf(t, ivt.Find(ivl1), "find expected not-nil on exact-matched interval %v", ivl1)
+	assert.Equalf(t, ivl1, ivt.Find(ivl1).Ivl, "find expected to return exact-matched interval %v", ivl1)
+	ivl2 := NewInt64Interval(3, 7)
+	assert.Nilf(t, ivt.Find(ivl2), "find expected nil on matched start, different end %v", ivl2)
+	ivl3 := NewInt64Interval(2, 6)
+	assert.Nilf(t, ivt.Find(ivl3), "find expected nil on different start, matched end %v", ivl3)
+	ivl4 := NewInt64Interval(10, 20)
+	assert.Nilf(t, ivt.Find(ivl4), "find expected nil on different start, different end %v", ivl4)
+	// insert the additional intervals into the tree, and check they can each be found.
+	ivls := []Interval{ivl2, ivl3, ivl4}
+	for _, ivl := range ivls {
+		ivt.Insert(ivl, val)
+		assert.NotNilf(t, ivt.Find(ivl), "find expected not-nil on exact-matched interval %v", ivl)
+		assert.Equalf(t, ivl, ivt.Find(ivl).Ivl, "find expected to return exact-matched interval %v", ivl)
+	}
+	// check additional intervals no longer found after deletion
+	for _, ivl := range ivls {
+		assert.Truef(t, ivt.Delete(ivl), "expected successful delete on %v", ivl)
+		assert.Nilf(t, ivt.Find(ivl), "find expected nil after deleted interval %v", ivl)
+	}
+}
+
 func TestIntervalTreeIntersects(t *testing.T) {
 	ivt := NewIntervalTree()
 	ivt.Insert(NewStringInterval("1", "3"), 123)
@@ -341,8 +371,14 @@ func TestIntervalTreeRandom(t *testing.T) {
 			require.NotEmptyf(t, ivt.Stab(NewInt64Point(v)), "expected %v stab non-zero for [%+v)", v, xy)
 			require.Truef(t, ivt.Intersects(NewInt64Point(v)), "did not get %d as expected for [%+v)", v, xy)
 		}
-		assert.Truef(t, ivt.Delete(NewInt64Interval(ab.x, ab.y)), "did not delete %v as expected", ab)
+		ivl := NewInt64Interval(ab.x, ab.y)
+		iv := ivt.Find(ivl)
+		assert.NotNilf(t, iv, "expected find non-nil on %v", ab)
+		assert.Equalf(t, ivl, iv.Ivl, "find did not get matched interval %v", ab)
+		assert.Truef(t, ivt.Delete(ivl), "did not delete %v as expected", ab)
 		delete(ivs, ab)
+		ivAfterDel := ivt.Find(ivl)
+		assert.Nilf(t, ivAfterDel, "expected find nil after deletion on %v", ab)
 	}
 
 	assert.Equalf(t, 0, ivt.Len(), "got ivt.Len() = %v, expected 0", ivt.Len())
diff --git a/pkg/featuregate/feature_gate_test.go b/pkg/featuregate/feature_gate_test.go
index 5dc5a8653..d70f20cf9 100644
--- a/pkg/featuregate/feature_gate_test.go
+++ b/pkg/featuregate/feature_gate_test.go
@@ -219,7 +219,7 @@ func TestFeatureGateFlag(t *testing.T) {
 				t.Errorf("%d: Parse() Expected nil, Got %v", i, err)
 			}
 			for k, v := range test.expect {
-				actual := f.enabled.Load().(map[Feature]bool)[k]
+				actual := f.Enabled(k)
 				assert.Equalf(t, actual, v, "%d: expected %s=%v, Got %v", i, k, v, actual)
 			}
 		})
diff --git a/server/etcdserver/api/membership/cluster_test.go b/server/etcdserver/api/membership/cluster_test.go
index 204c293ad..ff4171946 100644
--- a/server/etcdserver/api/membership/cluster_test.go
+++ b/server/etcdserver/api/membership/cluster_test.go
@@ -660,8 +660,8 @@ func TestNodeToMember(t *testing.T) {
 	}
 }
 
-func newTestCluster(t testing.TB, membs []*Member) *RaftCluster {
-	c := &RaftCluster{lg: zaptest.NewLogger(t), members: make(map[types.ID]*Member), removed: make(map[types.ID]bool)}
+func newTestCluster(tb testing.TB, membs []*Member) *RaftCluster {
+	c := &RaftCluster{lg: zaptest.NewLogger(tb), members: make(map[types.ID]*Member), removed: make(map[types.ID]bool)}
 	for _, m := range membs {
 		c.members[m.ID] = m
 	}
diff --git a/server/etcdserver/api/v2store/store_ttl_test.go b/server/etcdserver/api/v2store/store_ttl_test.go
index a4569cc31..aa98b4505 100644
--- a/server/etcdserver/api/v2store/store_ttl_test.go
+++ b/server/etcdserver/api/v2store/store_ttl_test.go
@@ -132,7 +132,7 @@ func TestStoreUpdateDirTTL(t *testing.T) {
 	assert.False(t, e.Node.Dir)
 	assert.Equal(t, eidx, e.EtcdIndex)
 	e, _ = s.Get("/foo/bar", false, false)
-	assert.Equal(t, "", *e.Node.Value)
+	assert.Empty(t, *e.Node.Value)
 	assert.Equal(t, eidx, e.EtcdIndex)
 
 	fc.Advance(600 * time.Millisecond)
diff --git a/server/etcdserver/api/v3compactor/periodic_test.go b/server/etcdserver/api/v3compactor/periodic_test.go
index 5604dabc5..e8658f420 100644
--- a/server/etcdserver/api/v3compactor/periodic_test.go
+++ b/server/etcdserver/api/v3compactor/periodic_test.go
@@ -16,6 +16,7 @@ package v3compactor
 
 import (
 	"errors"
+	"fmt"
 	"reflect"
 	"testing"
 	"time"
@@ -49,7 +50,7 @@ func TestPeriodicHourly(t *testing.T) {
 	}
 
 	// very first compaction
-	a, err := compactable.Wait(1)
+	a, err := waitWithRetry(t, compactable)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -67,7 +68,7 @@ func TestPeriodicHourly(t *testing.T) {
 			fc.Advance(tb.getRetryInterval())
 		}
 
-		a, err = compactable.Wait(1)
+		a, err = waitWithRetry(t, compactable)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -100,7 +101,7 @@ func TestPeriodicMinutes(t *testing.T) {
 	}
 
 	// very first compaction
-	a, err := compactable.Wait(1)
+	a, err := waitWithRetry(t, compactable)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -117,7 +118,7 @@ func TestPeriodicMinutes(t *testing.T) {
 			fc.Advance(tb.getRetryInterval())
 		}
 
-		a, err := compactable.Wait(1)
+		a, err := waitWithRetry(t, compactable)
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -162,7 +163,7 @@ func TestPeriodicPause(t *testing.T) {
 	fc.Advance(tb.getRetryInterval())
 
 	// T=3h6m
-	a, err := compactable.Wait(1)
+	a, err := waitWithRetry(t, compactable)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -197,7 +198,7 @@ func TestPeriodicSkipRevNotChange(t *testing.T) {
 	}
 
 	// very first compaction
-	a, err := compactable.Wait(1)
+	a, err := waitWithRetry(t, compactable)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -228,7 +229,7 @@ func TestPeriodicSkipRevNotChange(t *testing.T) {
 		fc.Advance(tb.getRetryInterval())
 	}
 
-	a, err = compactable.Wait(1)
+	a, err = waitWithRetry(t, compactable)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -244,3 +245,23 @@ func waitOneAction(t *testing.T, r testutil.Recorder) {
 		t.Errorf("expect 1 action, got %v instead", len(actions))
 	}
 }
+
+func waitWithRetry(t *testing.T, compactable *fakeCompactable) ([]testutil.Action, error) {
+	t.Helper()
+
+	var lastErr error
+	var actions []testutil.Action
+
+	expectedActions, maxRetries := 1, 5
+	for retry := 0; retry < maxRetries; retry++ {
+		actions, lastErr = compactable.Wait(expectedActions)
+		if lastErr == nil || len(actions) >= expectedActions {
+			return actions, nil
+		}
+		// Exponential backoff
+		backoffTime := time.Duration(10*(1<<retry)) * time.Millisecond
+		t.Logf("Retry %d: waiting %v before next attempt (last error: %v)", retry+1, backoffTime, lastErr)
+		time.Sleep(backoffTime)
+	}
+	return nil, fmt.Errorf("after %d retries, last error: %w", maxRetries, lastErr)
+}
diff --git a/server/etcdserver/server_test.go b/server/etcdserver/server_test.go
index d9caa34e4..bad869983 100644
--- a/server/etcdserver/server_test.go
+++ b/server/etcdserver/server_test.go
@@ -1514,12 +1514,12 @@ func (n *nodeConfChangeCommitterRecorder) ApplyConfChange(conf raftpb.ConfChange
 	return &raftpb.ConfState{}
 }
 
-func newTestCluster(t testing.TB) *membership.RaftCluster {
-	return membership.NewCluster(zaptest.NewLogger(t))
+func newTestCluster(tb testing.TB) *membership.RaftCluster {
+	return membership.NewCluster(zaptest.NewLogger(tb))
 }
 
-func newTestClusterWithBackend(t testing.TB, membs []*membership.Member, be backend.Backend) *membership.RaftCluster {
-	lg := zaptest.NewLogger(t)
+func newTestClusterWithBackend(tb testing.TB, membs []*membership.Member, be backend.Backend) *membership.RaftCluster {
+	lg := zaptest.NewLogger(tb)
 	c := membership.NewCluster(lg)
 	c.SetBackend(schema.NewMembershipBackend(lg, be))
 	for _, m := range membs {
diff --git a/server/lease/lessor_bench_test.go b/server/lease/lessor_bench_test.go
index 8e6ff791b..b28ba45c8 100644
--- a/server/lease/lessor_bench_test.go
+++ b/server/lease/lessor_bench_test.go
@@ -60,9 +60,9 @@ func demote(le *lessor) {
 }
 
 // return new lessor and tearDown to release resource
-func setUp(t testing.TB) (le *lessor, tearDown func()) {
+func setUp(tb testing.TB) (le *lessor, tearDown func()) {
 	lg := zap.NewNop()
-	be, _ := betesting.NewDefaultTmpBackend(t)
+	be, _ := betesting.NewDefaultTmpBackend(tb)
 	// MinLeaseTTL is negative, so we can grant expired lease in benchmark.
 	// ExpiredLeasesRetryInterval should small, so benchmark of findExpired will recheck expired lease.
 	le = newLessor(lg, be, nil, LessorConfig{MinLeaseTTL: -1000, ExpiredLeasesRetryInterval: 10 * time.Microsecond})
diff --git a/server/storage/backend/batch_tx_test.go b/server/storage/backend/batch_tx_test.go
index 279f199a5..25af69f11 100644
--- a/server/storage/backend/batch_tx_test.go
+++ b/server/storage/backend/batch_tx_test.go
@@ -393,9 +393,9 @@ func checkUnsafeForEach(t *testing.T, tx backend.UnsafeReader, expectedKeys, exp
 
 // runWriteback is used test the txWriteBuffer.writeback function, which is called inside tx.Unlock().
 // The parameters are chosen based on defaultBatchLimit = 10000
-func runWriteback(t testing.TB, kss, vss [][]string, isSeq bool) {
-	b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
-	defer betesting.Close(t, b)
+func runWriteback(tb testing.TB, kss, vss [][]string, isSeq bool) {
+	b, _ := betesting.NewTmpBackend(tb, time.Hour, 10000)
+	defer betesting.Close(tb, b)
 
 	tx := b.BatchTx()
 
diff --git a/server/storage/backend/hooks_test.go b/server/storage/backend/hooks_test.go
index 72bc26793..52b3ed8ec 100644
--- a/server/storage/backend/hooks_test.go
+++ b/server/storage/backend/hooks_test.go
@@ -94,11 +94,11 @@ func TestBackendAutoCommitBatchIntervalHook(t *testing.T) {
 	waitUntil(ctx, t, func() bool { return getCommitsKey(t, be) == ">ccc" })
 }
 
-func waitUntil(ctx context.Context, t testing.TB, f func() bool) {
+func waitUntil(ctx context.Context, tb testing.TB, f func() bool) {
 	for !f() {
 		select {
 		case <-ctx.Done():
-			t.Fatalf("Context cancelled/timedout without condition met: %v", ctx.Err())
+			tb.Fatalf("Context cancelled/timedout without condition met: %v", ctx.Err())
 		default:
 		}
 		time.Sleep(10 * time.Millisecond)
@@ -112,28 +112,28 @@ func prepareBuckenAndKey(tx backend.BatchTx) {
 	tx.UnsafePut(bucket, key, []byte(">"))
 }
 
-func newTestHooksBackend(t testing.TB, baseConfig backend.BackendConfig) backend.Backend {
+func newTestHooksBackend(tb testing.TB, baseConfig backend.BackendConfig) backend.Backend {
 	cfg := baseConfig
 	cfg.Hooks = backend.NewHooks(func(tx backend.UnsafeReadWriter) {
 		k, v := tx.UnsafeRange(bucket, key, nil, 1)
-		t.Logf("OnPreCommit executed: %v %v", string(k[0]), string(v[0]))
-		assert.Len(t, k, 1)
-		assert.Len(t, v, 1)
+		tb.Logf("OnPreCommit executed: %v %v", string(k[0]), string(v[0]))
+		assert.Len(tb, k, 1)
+		assert.Len(tb, v, 1)
 		tx.UnsafePut(bucket, key, append(v[0], byte('c')))
 	})
 
-	be, _ := betesting.NewTmpBackendFromCfg(t, cfg)
-	t.Cleanup(func() {
-		betesting.Close(t, be)
+	be, _ := betesting.NewTmpBackendFromCfg(tb, cfg)
+	tb.Cleanup(func() {
+		betesting.Close(tb, be)
 	})
 	return be
 }
 
-func getCommitsKey(t testing.TB, be backend.Backend) string {
+func getCommitsKey(tb testing.TB, be backend.Backend) string {
 	rtx := be.BatchTx()
 	rtx.Lock()
 	defer rtx.Unlock()
 	_, v := rtx.UnsafeRange(bucket, key, nil, 1)
-	assert.Len(t, v, 1)
+	assert.Len(tb, v, 1)
 	return string(v[0])
 }
diff --git a/server/storage/backend/testing/betesting.go b/server/storage/backend/testing/betesting.go
index e42908f93..eae54de9a 100644
--- a/server/storage/backend/testing/betesting.go
+++ b/server/storage/backend/testing/betesting.go
@@ -26,28 +26,28 @@ import (
 	"go.etcd.io/etcd/server/v3/storage/backend"
 )
 
-func NewTmpBackendFromCfg(t testing.TB, bcfg backend.BackendConfig) (backend.Backend, string) {
-	dir, err := os.MkdirTemp(t.TempDir(), "etcd_backend_test")
+func NewTmpBackendFromCfg(tb testing.TB, bcfg backend.BackendConfig) (backend.Backend, string) {
+	dir, err := os.MkdirTemp(tb.TempDir(), "etcd_backend_test")
 	if err != nil {
 		panic(err)
 	}
 	tmpPath := filepath.Join(dir, "database")
 	bcfg.Path = tmpPath
-	bcfg.Logger = zaptest.NewLogger(t)
+	bcfg.Logger = zaptest.NewLogger(tb)
 	return backend.New(bcfg), tmpPath
 }
 
 // NewTmpBackend creates a backend implementation for testing.
-func NewTmpBackend(t testing.TB, batchInterval time.Duration, batchLimit int) (backend.Backend, string) {
-	bcfg := backend.DefaultBackendConfig(zaptest.NewLogger(t))
+func NewTmpBackend(tb testing.TB, batchInterval time.Duration, batchLimit int) (backend.Backend, string) {
+	bcfg := backend.DefaultBackendConfig(zaptest.NewLogger(tb))
 	bcfg.BatchInterval, bcfg.BatchLimit = batchInterval, batchLimit
-	return NewTmpBackendFromCfg(t, bcfg)
+	return NewTmpBackendFromCfg(tb, bcfg)
 }
 
-func NewDefaultTmpBackend(t testing.TB) (backend.Backend, string) {
-	return NewTmpBackendFromCfg(t, backend.DefaultBackendConfig(zaptest.NewLogger(t)))
+func NewDefaultTmpBackend(tb testing.TB) (backend.Backend, string) {
+	return NewTmpBackendFromCfg(tb, backend.DefaultBackendConfig(zaptest.NewLogger(tb)))
 }
 
-func Close(t testing.TB, b backend.Backend) {
-	assert.NoError(t, b.Close())
+func Close(tb testing.TB, b backend.Backend) {
+	assert.NoError(tb, b.Close())
 }
diff --git a/server/storage/wal/testing/waltesting.go b/server/storage/wal/testing/waltesting.go
index bd1dbaade..7613a3f71 100644
--- a/server/storage/wal/testing/waltesting.go
+++ b/server/storage/wal/testing/waltesting.go
@@ -28,32 +28,32 @@ import (
 	"go.etcd.io/raft/v3/raftpb"
 )
 
-func NewTmpWAL(t testing.TB, reqs []etcdserverpb.InternalRaftRequest) (*wal.WAL, string) {
-	t.Helper()
-	dir, err := os.MkdirTemp(t.TempDir(), "etcd_wal_test")
+func NewTmpWAL(tb testing.TB, reqs []etcdserverpb.InternalRaftRequest) (*wal.WAL, string) {
+	tb.Helper()
+	dir, err := os.MkdirTemp(tb.TempDir(), "etcd_wal_test")
 	if err != nil {
 		panic(err)
 	}
 	tmpPath := filepath.Join(dir, "wal")
-	lg := zaptest.NewLogger(t)
+	lg := zaptest.NewLogger(tb)
 	w, err := wal.Create(lg, tmpPath, nil)
 	if err != nil {
-		t.Fatalf("Failed to create WAL: %v", err)
+		tb.Fatalf("Failed to create WAL: %v", err)
 	}
 	err = w.Close()
 	if err != nil {
-		t.Fatalf("Failed to close WAL: %v", err)
+		tb.Fatalf("Failed to close WAL: %v", err)
 	}
 	if len(reqs) != 0 {
 		w, err = wal.Open(lg, tmpPath, walpb.Snapshot{})
 		if err != nil {
-			t.Fatalf("Failed to open WAL: %v", err)
+			tb.Fatalf("Failed to open WAL: %v", err)
 		}
 
 		var state raftpb.HardState
 		_, state, _, err = w.ReadAll()
 		if err != nil {
-			t.Fatalf("Failed to read WAL: %v", err)
+			tb.Fatalf("Failed to read WAL: %v", err)
 		}
 		var entries []raftpb.Entry
 		for _, req := range reqs {
@@ -66,27 +66,27 @@ func NewTmpWAL(t testing.TB, reqs []etcdserverpb.InternalRaftRequest) (*wal.WAL,
 		}
 		err = w.Save(state, entries)
 		if err != nil {
-			t.Fatalf("Failed to save WAL: %v", err)
+			tb.Fatalf("Failed to save WAL: %v", err)
 		}
 		err = w.Close()
 		if err != nil {
-			t.Fatalf("Failed to close WAL: %v", err)
+			tb.Fatalf("Failed to close WAL: %v", err)
 		}
 	}
 
 	w, err = wal.OpenForRead(lg, tmpPath, walpb.Snapshot{})
 	if err != nil {
-		t.Fatalf("Failed to open WAL: %v", err)
+		tb.Fatalf("Failed to open WAL: %v", err)
 	}
 	return w, tmpPath
 }
 
-func Reopen(t testing.TB, walPath string) *wal.WAL {
-	t.Helper()
-	lg := zaptest.NewLogger(t)
+func Reopen(tb testing.TB, walPath string) *wal.WAL {
+	tb.Helper()
+	lg := zaptest.NewLogger(tb)
 	w, err := wal.OpenForRead(lg, walPath, walpb.Snapshot{})
 	if err != nil {
-		t.Fatalf("Failed to open WAL: %v", err)
+		tb.Fatalf("Failed to open WAL: %v", err)
 	}
 	return w
 }
diff --git a/tests/antithesis/README.md b/tests/antithesis/README.md
index 1c75e3937..c25b23fc3 100644
--- a/tests/antithesis/README.md
+++ b/tests/antithesis/README.md
@@ -7,7 +7,7 @@ This directory enables integration of Antithesis with etcd. There are 4 containe
 Run this command from the `antithesis/test-template` directory:
 
 ```bash
-docker build . -f Dockerfile.client -t etcd-client:latest
+docker build . -f Dockerfile -t etcd-client:latest
 ```
 
 ### 2. (Optional) Check the Image Locally
diff --git a/tests/antithesis/docker-compose.yaml b/tests/antithesis/docker-compose.yml
similarity index 96%
rename from tests/antithesis/docker-compose.yaml
rename to tests/antithesis/docker-compose.yml
index b0c7c0188..6cbcbfaf3 100644
--- a/tests/antithesis/docker-compose.yaml
+++ b/tests/antithesis/docker-compose.yml
@@ -46,4 +46,4 @@ services:
   client:
     image: 'etcd-client:latest'
     container_name: client
-    entrypoint: ["/entrypoint.py"]
+    entrypoint: ["/opt/antithesis/entrypoint/entrypoint"]
diff --git a/tests/antithesis/test-template/Dockerfile b/tests/antithesis/test-template/Dockerfile
new file mode 100644
index 000000000..25998eefd
--- /dev/null
+++ b/tests/antithesis/test-template/Dockerfile
@@ -0,0 +1,35 @@
+FROM ubuntu:latest
+
+# Install dependencies
+RUN apt-get update -y && apt-get install -y pip wget
+
+# GO:
+
+# Install go
+RUN wget https://golang.org/dl/go1.22.5.linux-amd64.tar.gz
+RUN tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
+ENV PATH=$PATH:/usr/local/go/bin 
+
+# Set working directory
+WORKDIR /opt/antithesis/entrypoint
+
+# Copy your entrypoint script
+COPY ./entrypoint/go.mod ./go.mod
+COPY ./entrypoint/go.sum ./go.sum
+COPY ./entrypoint/entrypoint.go ./entrypoint.go
+
+# Run go mod tidy to ensure the dependencies are correct
+RUN go mod tidy
+
+# Building go executable and putting it in Test Composer directory
+RUN go build -o ./entrypoint -race
+
+# Copying go script and building an executable into Test Composer directory
+WORKDIR /opt/antithesis/go-delete-keys
+
+COPY ./go-delete-keys/go.mod ./go.mod
+COPY ./go-delete-keys/go.sum ./go.sum
+COPY ./go-delete-keys/serial_driver_delete_keys.go ./serial_driver_delete_keys.go
+
+# Building go executable and putting it in Test Composer directory
+RUN go build -o /opt/antithesis/test/v1/main/serial_driver_delete_keys -race
\ No newline at end of file
diff --git a/tests/antithesis/test-template/Dockerfile.client b/tests/antithesis/test-template/Dockerfile.client
deleted file mode 100644
index 1ec3fcc53..000000000
--- a/tests/antithesis/test-template/Dockerfile.client
+++ /dev/null
@@ -1,14 +0,0 @@
-FROM ubuntu:latest
-
-# Update package index first, then install Python
-RUN apt-get update && \
-    apt-get install -y python3 python3-pip
-
-# Then install additional Python packages
-RUN apt-get install -y python3-etcd3 python3-numpy python3-protobuf python3-filelock
-
-# Install Antithesis Python SDK
-RUN pip install antithesis cffi --break-system-packages
-
-# Copy your entrypoint script
-COPY ./entrypoint/entrypoint.py /entrypoint.py
diff --git a/tests/antithesis/test-template/entrypoint/entrypoint.go b/tests/antithesis/test-template/entrypoint/entrypoint.go
new file mode 100644
index 000000000..f95c276c2
--- /dev/null
+++ b/tests/antithesis/test-template/entrypoint/entrypoint.go
@@ -0,0 +1,90 @@
+// Copyright 2025 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build cgo && amd64
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/antithesishq/antithesis-sdk-go/lifecycle"
+
+	clientv3 "go.etcd.io/etcd/client/v3"
+)
+
+// Sleep duration
+const SLEEP = 10
+
+// CheckHealth checks health of all etcd nodes
+func CheckHealth() bool {
+	nodeOptions := []string{"etcd0", "etcd1", "etcd2"}
+
+	// iterate over each node and check health
+	for _, node := range nodeOptions {
+		cli, err := clientv3.New(clientv3.Config{
+			Endpoints:   []string{fmt.Sprintf("http://%s:2379", node)},
+			DialTimeout: 5 * time.Second,
+		})
+		if err != nil {
+			fmt.Printf("Client [entrypoint]: connection failed with %s\n", node)
+			fmt.Printf("Client [entrypoint]: error: %v\n", err)
+			return false
+		}
+
+		defer func() {
+			if err := cli.Close(); err != nil {
+				fmt.Printf("Client [entrypoint]: error closing connection: %v\n", err)
+			}
+		}()
+
+		// fetch the key setting-up to confirm that the node is available
+		_, err = cli.Get(context.Background(), "setting-up")
+		if err != nil {
+			fmt.Printf("Client [entrypoint]: connection failed with %s\n", node)
+			fmt.Printf("Client [entrypoint]: error: %v\n", err)
+			return false
+		}
+
+		fmt.Printf("Client [entrypoint]: connection successful with %s\n", node)
+	}
+
+	return true
+}
+
+func main() {
+	fmt.Println("Client [entrypoint]: starting...")
+
+	// run loop until all nodes are healthy
+	for {
+		fmt.Println("Client [entrypoint]: checking cluster health...")
+		if CheckHealth() {
+			fmt.Println("Client [entrypoint]: cluster is healthy!")
+			break
+		}
+		fmt.Printf("Client [entrypoint]: cluster is not healthy. retrying in %d seconds...\n", SLEEP)
+		time.Sleep(SLEEP * time.Second)
+	}
+
+	// signal that the setup looks complete
+	lifecycle.SetupComplete(
+		map[string]string{
+			"Message": "ETCD cluster is healthy",
+		},
+	)
+
+	select {}
+}
diff --git a/tests/antithesis/test-template/entrypoint/entrypoint.py b/tests/antithesis/test-template/entrypoint/entrypoint.py
deleted file mode 100755
index d2bb8b342..000000000
--- a/tests/antithesis/test-template/entrypoint/entrypoint.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env -S python3 -u
-
-# This file serves as the client's entrypoint. It: 
-# 1. Confirms that all nodes in the cluster are available
-# 2. Signals "setupComplete" using the Antithesis SDK
-
-import etcd3, time
-
-from antithesis.lifecycle import (
-    setup_complete,
-)
-
-SLEEP = 10
-
-def check_health():
-
-    node_options = ["etcd0", "etcd1", "etcd2"]
-
-    for i in range(0, len(node_options)):
-        try:
-            c = etcd3.client(host=node_options[i], port=2379)
-            c.get('setting-up')
-            print(f"Client [entrypoint]: connection successful with {node_options[i]}")
-        except Exception as e:
-            print(f"Client [entrypoint]: connection failed with {node_options[i]}")
-            print(f"Client [entrypoint]: error: {e}")
-            return False
-    return True
-    
-print("Client [entrypoint]: starting...")
-
-while True:
-    print("Client [entrypoint]: checking cluster health...")
-    if check_health():
-        print("Client [entrypoint]: cluster is healthy!")
-        break
-    else:
-        print(f"Client [entrypoint]: cluster is not healthy. retrying in {SLEEP} seconds...")
-        time.sleep(SLEEP)
-
-
-# Here is the python format for setup_complete. At this point, our system is fully initialized and ready to test.
-setup_complete({"Message":"ETCD cluster is healthy"})
-
-# sleep infinity
-time.sleep(31536000)
diff --git a/tests/antithesis/test-template/entrypoint/go.mod b/tests/antithesis/test-template/entrypoint/go.mod
new file mode 100644
index 000000000..ed0393cbe
--- /dev/null
+++ b/tests/antithesis/test-template/entrypoint/go.mod
@@ -0,0 +1,30 @@
+module main
+
+go 1.24
+
+toolchain go1.24.2
+
+require (
+	github.com/antithesishq/antithesis-sdk-go v0.4.3
+	go.etcd.io/etcd/client/v3 v3.5.21
+)
+
+require (
+	github.com/coreos/go-semver v0.3.0 // indirect
+	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
+	go.etcd.io/etcd/api/v3 v3.5.21 // indirect
+	go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	go.uber.org/multierr v1.6.0 // indirect
+	go.uber.org/zap v1.17.0 // indirect
+	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/sys v0.31.0 // indirect
+	golang.org/x/text v0.23.0 // indirect
+	google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/grpc v1.59.0 // indirect
+	google.golang.org/protobuf v1.33.0 // indirect
+)
diff --git a/tests/antithesis/test-template/entrypoint/go.sum b/tests/antithesis/test-template/entrypoint/go.sum
new file mode 100644
index 000000000..5fdbc5360
--- /dev/null
+++ b/tests/antithesis/test-template/entrypoint/go.sum
@@ -0,0 +1,90 @@
+github.com/antithesishq/antithesis-sdk-go v0.4.3 h1:a2hGdDogClzHzFu20r1z0tzD6zwSWUipiaerAjZVP90=
+github.com/antithesishq/antithesis-sdk-go v0.4.3/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
+github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
+go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
+go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
+go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
+go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
+go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
+google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
+google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tests/antithesis/test-template/go-delete-keys/go.mod b/tests/antithesis/test-template/go-delete-keys/go.mod
new file mode 100644
index 000000000..089d48370
--- /dev/null
+++ b/tests/antithesis/test-template/go-delete-keys/go.mod
@@ -0,0 +1,28 @@
+module main
+
+go 1.24
+
+toolchain go1.24.2
+
+require go.etcd.io/etcd/client/v3 v3.5.21
+
+require (
+	github.com/antithesishq/antithesis-sdk-go v0.4.3
+	github.com/coreos/go-semver v0.3.0 // indirect
+	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
+	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
+	go.etcd.io/etcd/api/v3 v3.5.21 // indirect
+	go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
+	go.uber.org/atomic v1.7.0 // indirect
+	go.uber.org/multierr v1.6.0 // indirect
+	go.uber.org/zap v1.17.0 // indirect
+	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/sys v0.31.0 // indirect
+	golang.org/x/text v0.23.0 // indirect
+	google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/grpc v1.59.0 // indirect
+	google.golang.org/protobuf v1.33.0 // indirect
+)
diff --git a/tests/antithesis/test-template/go-delete-keys/go.sum b/tests/antithesis/test-template/go-delete-keys/go.sum
new file mode 100644
index 000000000..5fdbc5360
--- /dev/null
+++ b/tests/antithesis/test-template/go-delete-keys/go.sum
@@ -0,0 +1,90 @@
+github.com/antithesishq/antithesis-sdk-go v0.4.3 h1:a2hGdDogClzHzFu20r1z0tzD6zwSWUipiaerAjZVP90=
+github.com/antithesishq/antithesis-sdk-go v0.4.3/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
+github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
+go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
+go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
+go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
+go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
+go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
+golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
+google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
+google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/tests/antithesis/test-template/go-delete-keys/serial_driver_delete_keys.go b/tests/antithesis/test-template/go-delete-keys/serial_driver_delete_keys.go
new file mode 100644
index 000000000..918fe7d40
--- /dev/null
+++ b/tests/antithesis/test-template/go-delete-keys/serial_driver_delete_keys.go
@@ -0,0 +1,122 @@
+// Copyright 2025 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build cgo && amd64
+
+package main
+
+import (
+	"context"
+	"log"
+	"os"
+	"time"
+
+	"github.com/antithesishq/antithesis-sdk-go/assert"
+	"github.com/antithesishq/antithesis-sdk-go/random"
+	clientv3 "go.etcd.io/etcd/client/v3"
+)
+
+func Connect() *clientv3.Client {
+	// This function returns a client connection to an etcd node
+
+	hosts := [][]string{[]string{"etcd0:2379"}, []string{"etcd1:2379"}, []string{"etcd2:2379"}}
+	host := random.RandomChoice(hosts)
+	cli, err := clientv3.New(clientv3.Config{
+		Endpoints:   host,
+		DialTimeout: 5 * time.Second,
+	})
+	if err != nil {
+		log.Fatalf("Failed to connect to etcd: %v", err)
+		// Antithesis Assertion: client should always be able to connect to an etcd host
+		assert.Unreachable("Client failed to connect to an etcd host", map[string]interface{}{"host": host, "error": err})
+		os.Exit(1)
+	}
+	return cli
+}
+
+func DeleteKeys() {
+	// This function will:
+	// 1. Get all keys
+	// 2. Select half of the keys received
+	// 3. Attempt to delete the keys selected
+	// 4. Check that the keys were deleted
+
+	ctx := context.Background()
+
+	// Connect to an etcd node
+	cli := Connect()
+
+	// Get all keys
+	resp, err := cli.Get(ctx, "", clientv3.WithPrefix())
+
+	// Antithesis Assertion: sometimes get with prefix requests are successful. A failed request is OK since we expect them to happen.
+	assert.Sometimes(err == nil, "Client can make successful get all requests", map[string]interface{}{"error": err})
+	cli.Close()
+
+	if err != nil {
+		log.Printf("Client failed to get all keys: %v", err)
+		os.Exit(0)
+	}
+
+	// Choose half of the keys
+	var keys []string
+	for _, k := range resp.Kvs {
+		keys = append(keys, string(k.Key))
+	}
+	half := len(keys) / 2
+	halfKeys := keys[:half]
+
+	// Connect to a new etcd node
+	cli = Connect()
+
+	// Delete half of the keys chosen
+	var deletedKeys []string
+	for _, k := range halfKeys {
+		_, err := cli.Delete(ctx, k)
+		// Antithesis Assertion: sometimes delete requests are successful. A failed request is OK since we expect them to happen.
+		assert.Sometimes(err == nil, "Client can make successful delete requests", map[string]interface{}{"error": err})
+		if err != nil {
+			log.Printf("Failed to delete key %s: %v", k, err)
+		} else {
+			log.Printf("Successfully deleted key %v", k)
+			deletedKeys = append(deletedKeys, k)
+		}
+	}
+	cli.Close()
+
+	// Connect to a new etcd node
+	cli = Connect()
+
+	// Check to see if those keys were deleted / exist
+	for _, k := range deletedKeys {
+		resp, err := cli.Get(ctx, k)
+		// Antithesis Assertion: sometimes get requests are successful. A failed request is OK since we expect them to happen.
+		assert.Sometimes(err == nil, "Client can make successful get requests", map[string]interface{}{"error": err})
+		if err != nil {
+			log.Printf("Client failed to get key %s: %v", k, err)
+			continue
+		}
+		// Antithesis Assertion: if we deleted a key, we should not get a value
+		assert.Always(resp.Count == 0, "Key was deleted correctly", map[string]interface{}{"key": k})
+	}
+	cli.Close()
+
+	assert.Reachable("Completion of a key deleting check", nil)
+	log.Printf("Completion of a key deleting check")
+}
+
+func main() {
+	DeleteKeys()
+}
+
diff --git a/tests/e2e/metrics_test.go b/tests/e2e/metrics_test.go
index bac4ef909..9d730ed3a 100644
--- a/tests/e2e/metrics_test.go
+++ b/tests/e2e/metrics_test.go
@@ -15,17 +15,12 @@
 package e2e
 
 import (
-	"bytes"
 	"context"
 	"fmt"
-	"io"
-	"net/http"
 	"net/url"
 	"testing"
 	"time"
 
-	dto "github.com/prometheus/client_model/go"
-	"github.com/prometheus/common/expfmt"
 	"github.com/stretchr/testify/require"
 
 	"go.etcd.io/etcd/api/v3/version"
@@ -324,7 +319,7 @@ func TestNoMetricsMissing(t *testing.T) {
 			metricsURL, err := url.JoinPath(epc.Procs[0].Config().ClientURL, "metrics")
 			require.NoError(t, err)
 
-			mfs, err := getMetrics(metricsURL)
+			mfs, err := e2e.GetMetrics(metricsURL)
 			require.NoError(t, err)
 
 			var missingMetrics []string
@@ -341,23 +336,6 @@ func TestNoMetricsMissing(t *testing.T) {
 	}
 }
 
-func getMetrics(metricsURL string) (map[string]*dto.MetricFamily, error) {
-	httpClient := http.Client{Transport: &http.Transport{}}
-	resp, err := httpClient.Get(metricsURL)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-
-	data, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, err
-	}
-
-	var parser expfmt.TextParser
-	return parser.TextToMetricFamilies(bytes.NewReader(data))
-}
-
 // formatMetrics is only for test purpose
 /*func formatMetrics(metrics []string) string {
 	quoted := make([]string, len(metrics))
diff --git a/tests/e2e/reproduce_19406_test.go b/tests/e2e/reproduce_19406_test.go
new file mode 100644
index 000000000..be942aaba
--- /dev/null
+++ b/tests/e2e/reproduce_19406_test.go
@@ -0,0 +1,113 @@
+// Copyright 2025 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package e2e
+
+import (
+	"context"
+	"fmt"
+	"net/url"
+	"testing"
+	"time"
+
+	clientv3 "go.etcd.io/etcd/client/v3"
+	"go.etcd.io/etcd/pkg/v3/stringutil"
+	"go.etcd.io/etcd/tests/v3/framework/e2e"
+
+	"github.com/stretchr/testify/require"
+)
+
+// TestReproduce19406 reproduces the issue: https://github.com/etcd-io/etcd/issues/19406
+func TestReproduce19406(t *testing.T) {
+	e2e.BeforeTest(t)
+
+	compactionSleepInterval := 100 * time.Millisecond
+	ctx := context.TODO()
+
+	clus, cerr := e2e.NewEtcdProcessCluster(ctx, t,
+		e2e.WithClusterSize(1),
+		e2e.WithGoFailEnabled(true),
+		e2e.WithCompactionBatchLimit(1),
+		e2e.WithCompactionSleepInterval(compactionSleepInterval),
+	)
+	require.NoError(t, cerr)
+	t.Cleanup(func() { require.NoError(t, clus.Stop()) })
+
+	// Produce some data
+	cli := newClient(t, clus.EndpointsGRPC(), e2e.ClientConfig{})
+	valueSize := 10
+	var latestRevision int64
+
+	produceKeyNum := 20
+	for i := 0; i <= produceKeyNum; i++ {
+		resp, err := cli.Put(ctx, fmt.Sprintf("%d", i), stringutil.RandString(uint(valueSize)))
+		require.NoError(t, err)
+		latestRevision = resp.Header.Revision
+	}
+
+	// Sleep for PerCompactionInterationInterval to simulate a single iteration of compaction lasting at least this duration.
+	PerCompactionInterationInterval := compactionSleepInterval
+	require.NoError(t, clus.Procs[0].Failpoints().SetupHTTP(ctx, "compactAfterAcquiredBatchTxLock",
+		fmt.Sprintf(`sleep("%s")`, PerCompactionInterationInterval)))
+
+	// start compaction
+	t.Log("start compaction...")
+	_, err := cli.Compact(ctx, latestRevision, clientv3.WithCompactPhysical())
+	require.NoError(t, err)
+	t.Log("finished compaction...")
+
+	// Validate that total compaction sleep interval
+	// Compaction runs in batches. During each batch, it acquires a lock, releases it at the end,
+	// and then waits for a compactionSleepInterval before starting the next batch. This pause
+	// allows PUT requests to be processed.
+	// Therefore, the total compaction sleep interval larger or equal to
+	// (compaction iteration number - 1) * compactionSleepInterval
+	httpEndpoint := clus.EndpointsHTTP()[0]
+	totalKeys := produceKeyNum + 1
+	pauseDuration, totalDuration := getEtcdCompactionMetrics(t, httpEndpoint)
+	require.NoError(t, err)
+	actualSleepInterval := time.Duration(totalDuration-pauseDuration) * time.Millisecond
+	expectSleepInterval := compactionSleepInterval * time.Duration(totalKeys)
+	t.Logf("db_compaction_pause_duration: %.2f db_compaction_total_duration: %.2f, totalKeys: %d",
+		pauseDuration, totalDuration, totalKeys)
+	require.GreaterOrEqualf(t, actualSleepInterval, expectSleepInterval,
+		"expect total compact sleep interval larger than (%v) but got (%v)",
+		expectSleepInterval, actualSleepInterval)
+}
+
+func getEtcdCompactionMetrics(t *testing.T, httpEndpoint string) (pauseDuration, totalDuration float64) {
+	metricsURL, err := url.JoinPath(httpEndpoint, "metrics")
+	require.NoError(t, err)
+
+	// Fetch metrics from the endpoint
+	metricFamilies, err := e2e.GetMetrics(metricsURL)
+	require.NoError(t, err)
+
+	// Extract sum from histogram metric
+	getHistogramSum := func(name string) float64 {
+		mf, ok := metricFamilies[name]
+		require.Truef(t, ok, "metric %q not found", name)
+		require.NotEmptyf(t, mf.Metric, "metric %q has no data", name)
+
+		hist := mf.Metric[0].GetHistogram()
+		require.NotEmptyf(t, hist, "metric %q is not a histogram", name)
+
+		return hist.GetSampleSum()
+	}
+
+	pauseDuration = getHistogramSum("etcd_debugging_mvcc_db_compaction_pause_duration_milliseconds")
+	totalDuration = getHistogramSum("etcd_debugging_mvcc_db_compaction_total_duration_milliseconds")
+
+	return pauseDuration, totalDuration
+}
diff --git a/tests/e2e/utils.go b/tests/e2e/utils.go
index 8917bd807..3f4ca015b 100644
--- a/tests/e2e/utils.go
+++ b/tests/e2e/utils.go
@@ -69,13 +69,13 @@ func newClient(t *testing.T, entpoints []string, cfg e2e.ClientConfig) *clientv3
 }
 
 // tlsInfo follows the Client-to-server communication in https://etcd.io/docs/v3.6/op-guide/security/#basic-setup
-func tlsInfo(t testing.TB, cfg e2e.ClientConfig) (*transport.TLSInfo, error) {
+func tlsInfo(tb testing.TB, cfg e2e.ClientConfig) (*transport.TLSInfo, error) {
 	switch cfg.ConnectionType {
 	case e2e.ClientNonTLS, e2e.ClientTLSAndNonTLS:
 		return nil, nil
 	case e2e.ClientTLS:
 		if cfg.AutoTLS {
-			tls, err := transport.SelfCert(zap.NewNop(), t.TempDir(), []string{"localhost"}, 1)
+			tls, err := transport.SelfCert(zap.NewNop(), tb.TempDir(), []string{"localhost"}, 1)
 			if err != nil {
 				return nil, fmt.Errorf("failed to generate cert: %w", err)
 			}
diff --git a/tests/e2e/v2store_deprecation_test.go b/tests/e2e/v2store_deprecation_test.go
index 34225cb7b..e65b75bf6 100644
--- a/tests/e2e/v2store_deprecation_test.go
+++ b/tests/e2e/v2store_deprecation_test.go
@@ -39,13 +39,13 @@ import (
 	"go.etcd.io/etcd/tests/v3/framework/e2e"
 )
 
-func writeCustomV2Data(t testing.TB, epc *e2e.EtcdProcessCluster, count int) {
+func writeCustomV2Data(tb testing.TB, epc *e2e.EtcdProcessCluster, count int) {
 	for i := 0; i < count; i++ {
 		if err := e2e.CURLPut(epc, e2e.CURLReq{
 			Endpoint: "/v2/keys/foo", Value: "bar" + fmt.Sprint(i),
 			Expected: expect.ExpectedResponse{Value: `{"action":"set","node":{"key":"/foo","value":"bar` + fmt.Sprint(i)},
 		}); err != nil {
-			t.Fatalf("failed put with curl (%v)", err)
+			tb.Fatalf("failed put with curl (%v)", err)
 		}
 	}
 }
@@ -191,47 +191,47 @@ func TestV2DeprecationSnapshotRecover(t *testing.T) {
 	assert.NoError(t, epc.Close())
 }
 
-func runEtcdAndCreateSnapshot(t testing.TB, serverVersion e2e.ClusterVersion, dataDir string, snapshotCount uint64) *e2e.EtcdProcessCluster {
+func runEtcdAndCreateSnapshot(tb testing.TB, serverVersion e2e.ClusterVersion, dataDir string, snapshotCount uint64) *e2e.EtcdProcessCluster {
 	cfg := e2e.ConfigStandalone(*e2e.NewConfig(
 		e2e.WithVersion(serverVersion),
 		e2e.WithDataDirPath(dataDir),
 		e2e.WithSnapshotCount(snapshotCount),
 		e2e.WithKeepDataDir(true),
 	))
-	epc, err := e2e.NewEtcdProcessCluster(t.Context(), t, e2e.WithConfig(cfg))
-	assert.NoError(t, err)
+	epc, err := e2e.NewEtcdProcessCluster(tb.Context(), tb, e2e.WithConfig(cfg))
+	assert.NoError(tb, err)
 	return epc
 }
 
-func addAndRemoveKeysAndMembers(ctx context.Context, t testing.TB, cc *e2e.EtcdctlV3, snapshotCount uint64) (members []uint64) {
+func addAndRemoveKeysAndMembers(ctx context.Context, tb testing.TB, cc *e2e.EtcdctlV3, snapshotCount uint64) (members []uint64) {
 	// Execute some non-trivial key&member operation
 	var i uint64
 	for i = 0; i < snapshotCount*3; i++ {
 		err := cc.Put(ctx, fmt.Sprintf("%d", i), "1", config.PutOptions{})
-		require.NoError(t, err)
+		require.NoError(tb, err)
 	}
 	member1, err := cc.MemberAddAsLearner(ctx, "member1", []string{"http://127.0.0.1:2000"})
-	require.NoError(t, err)
+	require.NoError(tb, err)
 	members = append(members, member1.Member.ID)
 
 	for i = 0; i < snapshotCount*2; i++ {
 		_, err = cc.Delete(ctx, fmt.Sprintf("%d", i), config.DeleteOptions{})
-		require.NoError(t, err)
+		require.NoError(tb, err)
 	}
 	_, err = cc.MemberRemove(ctx, member1.Member.ID)
-	require.NoError(t, err)
+	require.NoError(tb, err)
 
 	for i = 0; i < snapshotCount; i++ {
 		err = cc.Put(ctx, fmt.Sprintf("%d", i), "2", config.PutOptions{})
-		require.NoError(t, err)
+		require.NoError(tb, err)
 	}
 	member2, err := cc.MemberAddAsLearner(ctx, "member2", []string{"http://127.0.0.1:2001"})
-	require.NoError(t, err)
+	require.NoError(tb, err)
 	members = append(members, member2.Member.ID)
 
 	for i = 0; i < snapshotCount/2; i++ {
 		err = cc.Put(ctx, fmt.Sprintf("%d", i), "3", config.PutOptions{})
-		assert.NoError(t, err)
+		assert.NoError(tb, err)
 	}
 	return members
 }
@@ -240,39 +240,39 @@ func filterSnapshotFiles(path string) bool {
 	return strings.HasSuffix(path, ".snap")
 }
 
-func assertSnapshotsMatch(t testing.TB, firstDataDir, secondDataDir string, patch func([]byte) []byte) {
-	lg := zaptest.NewLogger(t)
+func assertSnapshotsMatch(tb testing.TB, firstDataDir, secondDataDir string, patch func([]byte) []byte) {
+	lg := zaptest.NewLogger(tb)
 	firstFiles, err := fileutil.ListFiles(firstDataDir, filterSnapshotFiles)
-	require.NoError(t, err)
+	require.NoError(tb, err)
 	secondFiles, err := fileutil.ListFiles(secondDataDir, filterSnapshotFiles)
-	require.NoError(t, err)
-	assert.NotEmpty(t, firstFiles)
-	assert.NotEmpty(t, secondFiles)
-	assert.Equal(t, len(firstFiles), len(secondFiles))
+	require.NoError(tb, err)
+	assert.NotEmpty(tb, firstFiles)
+	assert.NotEmpty(tb, secondFiles)
+	assert.Len(tb, secondFiles, len(firstFiles))
 	sort.Strings(firstFiles)
 	sort.Strings(secondFiles)
 	for i := 0; i < len(firstFiles); i++ {
 		firstSnapshot, err := snap.Read(lg, firstFiles[i])
-		require.NoError(t, err)
+		require.NoError(tb, err)
 		secondSnapshot, err := snap.Read(lg, secondFiles[i])
-		require.NoError(t, err)
-		assertMembershipEqual(t, openSnap(patch(firstSnapshot.Data)), openSnap(patch(secondSnapshot.Data)))
+		require.NoError(tb, err)
+		assertMembershipEqual(tb, openSnap(patch(firstSnapshot.Data)), openSnap(patch(secondSnapshot.Data)))
 	}
 }
 
-func assertMembershipEqual(t testing.TB, firstStore v2store.Store, secondStore v2store.Store) {
-	rc1 := membership.NewCluster(zaptest.NewLogger(t))
+func assertMembershipEqual(tb testing.TB, firstStore v2store.Store, secondStore v2store.Store) {
+	rc1 := membership.NewCluster(zaptest.NewLogger(tb))
 	rc1.SetStore(firstStore)
 	rc1.Recover(func(lg *zap.Logger, v *semver.Version) {})
 
-	rc2 := membership.NewCluster(zaptest.NewLogger(t))
+	rc2 := membership.NewCluster(zaptest.NewLogger(tb))
 	rc2.SetStore(secondStore)
 	rc2.Recover(func(lg *zap.Logger, v *semver.Version) {})
 
 	// membership should match
 	if !reflect.DeepEqual(rc1.Members(), rc2.Members()) {
-		t.Logf("memberids_from_last_version = %+v, member_ids_from_current_version = %+v", rc1.MemberIDs(), rc2.MemberIDs())
-		t.Errorf("members_from_last_version_snapshot = %+v, members_from_current_version_snapshot %+v", rc1.Members(), rc2.Members())
+		tb.Logf("memberids_from_last_version = %+v, member_ids_from_current_version = %+v", rc1.MemberIDs(), rc2.MemberIDs())
+		tb.Errorf("members_from_last_version_snapshot = %+v, members_from_current_version_snapshot %+v", rc1.Members(), rc2.Members())
 	}
 }
 
diff --git a/tests/framework/e2e/cluster.go b/tests/framework/e2e/cluster.go
index 83c64b790..6e667f641 100644
--- a/tests/framework/e2e/cluster.go
+++ b/tests/framework/e2e/cluster.go
@@ -425,23 +425,23 @@ func WithExtensiveMetrics() EPClusterOption {
 
 // NewEtcdProcessCluster launches a new cluster from etcd processes, returning
 // a new EtcdProcessCluster once all nodes are ready to accept client requests.
-func NewEtcdProcessCluster(ctx context.Context, t testing.TB, opts ...EPClusterOption) (*EtcdProcessCluster, error) {
+func NewEtcdProcessCluster(ctx context.Context, tb testing.TB, opts ...EPClusterOption) (*EtcdProcessCluster, error) {
 	cfg := NewConfig(opts...)
-	epc, err := InitEtcdProcessCluster(t, cfg)
+	epc, err := InitEtcdProcessCluster(tb, cfg)
 	if err != nil {
 		return nil, err
 	}
 
-	return StartEtcdProcessCluster(ctx, t, epc, cfg)
+	return StartEtcdProcessCluster(ctx, tb, epc, cfg)
 }
 
 // InitEtcdProcessCluster initializes a new cluster based on the given config.
 // It doesn't start the cluster.
-func InitEtcdProcessCluster(t testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) {
-	SkipInShortMode(t)
+func InitEtcdProcessCluster(tb testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) {
+	SkipInShortMode(tb)
 
 	if cfg.Logger == nil {
-		cfg.Logger = zaptest.NewLogger(t)
+		cfg.Logger = zaptest.NewLogger(tb)
 	}
 	if cfg.BasePort == 0 {
 		cfg.BasePort = EtcdProcessBasePort
@@ -460,17 +460,17 @@ func InitEtcdProcessCluster(t testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdP
 		}
 	}
 
-	etcdCfgs := cfg.EtcdAllServerProcessConfigs(t)
+	etcdCfgs := cfg.EtcdAllServerProcessConfigs(tb)
 	epc := &EtcdProcessCluster{
 		Cfg:     cfg,
-		lg:      zaptest.NewLogger(t),
+		lg:      zaptest.NewLogger(tb),
 		Procs:   make([]EtcdProcess, cfg.ClusterSize),
 		nextSeq: cfg.ClusterSize,
 	}
 
 	// launch etcd processes
 	for i := range etcdCfgs {
-		proc, err := NewEtcdProcess(t, etcdCfgs[i])
+		proc, err := NewEtcdProcess(tb, etcdCfgs[i])
 		if err != nil {
 			epc.Close()
 			return nil, fmt.Errorf("cannot configure: %w", err)
@@ -482,7 +482,7 @@ func InitEtcdProcessCluster(t testing.TB, cfg *EtcdProcessClusterConfig) (*EtcdP
 }
 
 // StartEtcdProcessCluster launches a new cluster from etcd processes.
-func StartEtcdProcessCluster(ctx context.Context, t testing.TB, epc *EtcdProcessCluster, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) {
+func StartEtcdProcessCluster(ctx context.Context, tb testing.TB, epc *EtcdProcessCluster, cfg *EtcdProcessClusterConfig) (*EtcdProcessCluster, error) {
 	if cfg.RollingStart {
 		if err := epc.RollingStart(ctx); err != nil {
 			return nil, fmt.Errorf("cannot rolling-start: %w", err)
@@ -496,11 +496,11 @@ func StartEtcdProcessCluster(ctx context.Context, t testing.TB, epc *EtcdProcess
 	for _, proc := range epc.Procs {
 		if cfg.GoFailEnabled && !proc.Failpoints().Enabled() {
 			epc.Close()
-			t.Skip("please run 'make gofail-enable && make build' before running the test")
+			tb.Skip("please run 'make gofail-enable && make build' before running the test")
 		}
 	}
 	if cfg.InitialLeaderIndex >= 0 {
-		if err := epc.MoveLeader(ctx, t, cfg.InitialLeaderIndex); err != nil {
+		if err := epc.MoveLeader(ctx, tb, cfg.InitialLeaderIndex); err != nil {
 			return nil, fmt.Errorf("failed to move leader: %w", err)
 		}
 	}
@@ -1078,29 +1078,29 @@ func findMemberIDByEndpoint(members []*etcdserverpb.Member, endpoint string) (ui
 
 // WaitLeader returns index of the member in c.Members() that is leader
 // or fails the test (if not established in 30s).
-func (epc *EtcdProcessCluster) WaitLeader(t testing.TB) int {
-	ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
+func (epc *EtcdProcessCluster) WaitLeader(tb testing.TB) int {
+	ctx, cancel := context.WithTimeout(tb.Context(), 30*time.Second)
 	defer cancel()
-	return epc.WaitMembersForLeader(ctx, t, epc.Procs)
+	return epc.WaitMembersForLeader(ctx, tb, epc.Procs)
 }
 
 // WaitMembersForLeader waits until given members agree on the same leader,
 // and returns its 'index' in the 'membs' list
-func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testing.TB, membs []EtcdProcess) int {
+func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, tb testing.TB, membs []EtcdProcess) int {
 	cc := epc.Etcdctl()
 
 	// ensure leader is up via linearizable get
 	for {
 		select {
 		case <-ctx.Done():
-			t.Fatal("WaitMembersForLeader timeout")
+			tb.Fatal("WaitMembersForLeader timeout")
 		default:
 		}
 		_, err := cc.Get(ctx, "0", config.GetOptions{Timeout: 10*config.TickDuration + time.Second})
 		if err == nil || strings.Contains(err.Error(), "Key not found") {
 			break
 		}
-		t.Logf("WaitMembersForLeader Get err: %v", err)
+		tb.Logf("WaitMembersForLeader Get err: %v", err)
 	}
 
 	leaders := make(map[uint64]struct{})
@@ -1108,7 +1108,7 @@ func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testi
 	for {
 		select {
 		case <-ctx.Done():
-			t.Fatal("WaitMembersForLeader timeout")
+			tb.Fatal("WaitMembersForLeader timeout")
 		default:
 		}
 		for i := range membs {
@@ -1118,7 +1118,7 @@ func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testi
 					// if member[i] has stopped
 					continue
 				}
-				t.Fatal(err)
+				tb.Fatal(err)
 			}
 			members[resp[0].Header.MemberId] = i
 			leaders[resp[0].Leader] = struct{}{}
@@ -1133,24 +1133,24 @@ func (epc *EtcdProcessCluster) WaitMembersForLeader(ctx context.Context, t testi
 	}
 	for l := range leaders {
 		if index, ok := members[l]; ok {
-			t.Logf("members agree on a leader, members:%v , leader:%v", members, l)
+			tb.Logf("members agree on a leader, members:%v , leader:%v", members, l)
 			return index
 		}
-		t.Fatalf("members agree on a leader which is not one of members, members:%v , leader:%v", members, l)
+		tb.Fatalf("members agree on a leader which is not one of members, members:%v , leader:%v", members, l)
 	}
-	t.Fatal("impossible path of execution")
+	tb.Fatal("impossible path of execution")
 	return -1
 }
 
 // MoveLeader moves the leader to the ith process.
-func (epc *EtcdProcessCluster) MoveLeader(ctx context.Context, t testing.TB, i int) error {
+func (epc *EtcdProcessCluster) MoveLeader(ctx context.Context, tb testing.TB, i int) error {
 	if i < 0 || i >= len(epc.Procs) {
 		return fmt.Errorf("invalid index: %d, must between 0 and %d", i, len(epc.Procs)-1)
 	}
-	t.Logf("moving leader to Procs[%d]", i)
-	oldLeader := epc.WaitMembersForLeader(ctx, t, epc.Procs)
+	tb.Logf("moving leader to Procs[%d]", i)
+	oldLeader := epc.WaitMembersForLeader(ctx, tb, epc.Procs)
 	if oldLeader == i {
-		t.Logf("Procs[%d] is already the leader", i)
+		tb.Logf("Procs[%d] is already the leader", i)
 		return nil
 	}
 	resp, err := epc.Procs[i].Etcdctl().Status(ctx)
@@ -1162,10 +1162,10 @@ func (epc *EtcdProcessCluster) MoveLeader(ctx context.Context, t testing.TB, i i
 	if err != nil {
 		return err
 	}
-	newLeader := epc.WaitMembersForLeader(ctx, t, epc.Procs)
+	newLeader := epc.WaitMembersForLeader(ctx, tb, epc.Procs)
 	if newLeader != i {
-		t.Fatalf("expect new leader to be Procs[%d] but got Procs[%d]", i, newLeader)
+		tb.Fatalf("expect new leader to be Procs[%d] but got Procs[%d]", i, newLeader)
 	}
-	t.Logf("moved leader from Procs[%d] to Procs[%d]", oldLeader, i)
+	tb.Logf("moved leader from Procs[%d] to Procs[%d]", oldLeader, i)
 	return nil
 }
diff --git a/tests/framework/e2e/cluster_direct.go b/tests/framework/e2e/cluster_direct.go
index 70c60dbf4..ea46ea31d 100644
--- a/tests/framework/e2e/cluster_direct.go
+++ b/tests/framework/e2e/cluster_direct.go
@@ -18,6 +18,6 @@ package e2e
 
 import "testing"
 
-func NewEtcdProcess(t testing.TB, cfg *EtcdServerProcessConfig) (EtcdProcess, error) {
-	return NewEtcdServerProcess(t, cfg)
+func NewEtcdProcess(tb testing.TB, cfg *EtcdServerProcessConfig) (EtcdProcess, error) {
+	return NewEtcdServerProcess(tb, cfg)
 }
diff --git a/tests/framework/e2e/e2e.go b/tests/framework/e2e/e2e.go
index f78df5792..6f3954d5d 100644
--- a/tests/framework/e2e/e2e.go
+++ b/tests/framework/e2e/e2e.go
@@ -39,11 +39,11 @@ func (e e2eRunner) TestMain(m *testing.M) {
 	os.Exit(v)
 }
 
-func (e e2eRunner) BeforeTest(t testing.TB) {
-	BeforeTest(t)
+func (e e2eRunner) BeforeTest(tb testing.TB) {
+	BeforeTest(tb)
 }
 
-func (e e2eRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.ClusterOption) intf.Cluster {
+func (e e2eRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config.ClusterOption) intf.Cluster {
 	cfg := config.NewClusterConfig(opts...)
 	e2eConfig := NewConfig(
 		WithClusterSize(cfg.ClusterSize),
@@ -68,7 +68,7 @@ func (e e2eRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.
 		e2eConfig.Client.AutoTLS = false
 		e2eConfig.Client.ConnectionType = ClientTLS
 	default:
-		t.Fatalf("ClientTLS config %q not supported", cfg.ClientTLS)
+		tb.Fatalf("ClientTLS config %q not supported", cfg.ClientTLS)
 	}
 	switch cfg.PeerTLS {
 	case config.NoTLS:
@@ -81,13 +81,13 @@ func (e e2eRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.
 		e2eConfig.IsPeerTLS = true
 		e2eConfig.IsPeerAutoTLS = false
 	default:
-		t.Fatalf("PeerTLS config %q not supported", cfg.PeerTLS)
+		tb.Fatalf("PeerTLS config %q not supported", cfg.PeerTLS)
 	}
-	epc, err := NewEtcdProcessCluster(ctx, t, WithConfig(e2eConfig))
+	epc, err := NewEtcdProcessCluster(ctx, tb, WithConfig(e2eConfig))
 	if err != nil {
-		t.Fatalf("could not start etcd integrationCluster: %s", err)
+		tb.Fatalf("could not start etcd integrationCluster: %s", err)
 	}
-	return &e2eCluster{t, *epc}
+	return &e2eCluster{tb, *epc}
 }
 
 type e2eCluster struct {
diff --git a/tests/framework/e2e/etcd_process.go b/tests/framework/e2e/etcd_process.go
index 805909a01..6d8dd3036 100644
--- a/tests/framework/e2e/etcd_process.go
+++ b/tests/framework/e2e/etcd_process.go
@@ -103,7 +103,7 @@ type EtcdServerProcessConfig struct {
 	Proxy         *proxy.ServerConfig
 }
 
-func NewEtcdServerProcess(t testing.TB, cfg *EtcdServerProcessConfig) (*EtcdServerProcess, error) {
+func NewEtcdServerProcess(tb testing.TB, cfg *EtcdServerProcessConfig) (*EtcdServerProcess, error) {
 	if !fileutil.Exist(cfg.ExecPath) {
 		return nil, fmt.Errorf("could not find etcd binary: %s", cfg.ExecPath)
 	}
@@ -123,7 +123,7 @@ func NewEtcdServerProcess(t testing.TB, cfg *EtcdServerProcessConfig) (*EtcdServ
 		}
 	}
 	if cfg.LazyFSEnabled {
-		ep.lazyfs = newLazyFS(cfg.lg, cfg.DataDirPath, t)
+		ep.lazyfs = newLazyFS(cfg.lg, cfg.DataDirPath, tb)
 	}
 	return ep, nil
 }
diff --git a/tests/framework/e2e/metrics.go b/tests/framework/e2e/metrics.go
new file mode 100644
index 000000000..e28661206
--- /dev/null
+++ b/tests/framework/e2e/metrics.go
@@ -0,0 +1,41 @@
+// Copyright 2025 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package e2e
+
+import (
+	"bytes"
+	"io"
+	"net/http"
+
+	dto "github.com/prometheus/client_model/go"
+	"github.com/prometheus/common/expfmt"
+)
+
+func GetMetrics(metricsURL string) (map[string]*dto.MetricFamily, error) {
+	httpClient := http.Client{Transport: &http.Transport{}}
+	resp, err := httpClient.Get(metricsURL)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	data, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	var parser expfmt.TextParser
+	return parser.TextToMetricFamilies(bytes.NewReader(data))
+}
diff --git a/tests/framework/e2e/testing.go b/tests/framework/e2e/testing.go
index 7d7de27fd..99aa8dac6 100644
--- a/tests/framework/e2e/testing.go
+++ b/tests/framework/e2e/testing.go
@@ -20,7 +20,7 @@ import (
 	"go.etcd.io/etcd/client/pkg/v3/testutil"
 )
 
-func BeforeTest(t testing.TB) {
-	SkipInShortMode(t)
-	testutil.BeforeTest(t)
+func BeforeTest(tb testing.TB) {
+	SkipInShortMode(tb)
+	testutil.BeforeTest(tb)
 }
diff --git a/tests/framework/e2e/util.go b/tests/framework/e2e/util.go
index d72f2d493..94e1cd6a8 100644
--- a/tests/framework/e2e/util.go
+++ b/tests/framework/e2e/util.go
@@ -150,8 +150,8 @@ func ToTLS(s string) string {
 	return s
 }
 
-func SkipInShortMode(t testing.TB) {
-	testutil.SkipTestIfShortMode(t, "e2e tests are not running in --short mode")
+func SkipInShortMode(tb testing.TB) {
+	testutil.SkipTestIfShortMode(tb, "e2e tests are not running in --short mode")
 }
 
 func mergeEnvVariables(envVars map[string]string) []string {
diff --git a/tests/framework/integration/cluster.go b/tests/framework/integration/cluster.go
index 1570fdc19..5cd6321b0 100644
--- a/tests/framework/integration/cluster.go
+++ b/tests/framework/integration/cluster.go
@@ -412,23 +412,23 @@ func (c *Cluster) WaitMembersMatch(t testutil.TB, membs []*pb.Member) {
 
 // WaitLeader returns index of the member in c.Members that is leader
 // or fails the test (if not established in 30s).
-func (c *Cluster) WaitLeader(t testing.TB) int {
-	return c.WaitMembersForLeader(t, c.Members)
+func (c *Cluster) WaitLeader(tb testing.TB) int {
+	return c.WaitMembersForLeader(tb, c.Members)
 }
 
 // WaitMembersForLeader waits until given members agree on the same leader,
 // and returns its 'index' in the 'membs' list
-func (c *Cluster) WaitMembersForLeader(t testing.TB, membs []*Member) int {
-	t.Logf("WaitMembersForLeader")
-	ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
+func (c *Cluster) WaitMembersForLeader(tb testing.TB, membs []*Member) int {
+	tb.Logf("WaitMembersForLeader")
+	ctx, cancel := context.WithTimeout(tb.Context(), 30*time.Second)
 	defer cancel()
 	l := 0
-	for l = c.waitMembersForLeader(ctx, t, membs); l < 0; {
+	for l = c.waitMembersForLeader(ctx, tb, membs); l < 0; {
 		if ctx.Err() != nil {
-			t.Fatalf("WaitLeader FAILED: %v", ctx.Err())
+			tb.Fatalf("WaitLeader FAILED: %v", ctx.Err())
 		}
 	}
-	t.Logf("WaitMembersForLeader succeeded. Cluster leader index: %v", l)
+	tb.Logf("WaitMembersForLeader succeeded. Cluster leader index: %v", l)
 
 	// TODO: Consider second pass check as sometimes leadership is lost
 	// soon after election:
@@ -444,15 +444,15 @@ func (c *Cluster) WaitMembersForLeader(t testing.TB, membs []*Member) int {
 
 // WaitMembersForLeader waits until given members agree on the same leader,
 // and returns its 'index' in the 'membs' list
-func (c *Cluster) waitMembersForLeader(ctx context.Context, t testing.TB, membs []*Member) int {
+func (c *Cluster) waitMembersForLeader(ctx context.Context, tb testing.TB, membs []*Member) int {
 	possibleLead := make(map[uint64]bool)
 	var lead uint64
 	for _, m := range membs {
 		possibleLead[uint64(m.Server.MemberID())] = true
 	}
-	cc, err := c.ClusterClient(t)
+	cc, err := c.ClusterClient(tb)
 	if err != nil {
-		t.Fatal(err)
+		tb.Fatal(err)
 	}
 	// ensure leader is up via linearizable get
 	for {
@@ -483,12 +483,12 @@ func (c *Cluster) waitMembersForLeader(ctx context.Context, t testing.TB, membs
 
 	for i, m := range membs {
 		if uint64(m.Server.MemberID()) == lead {
-			t.Logf("waitMembersForLeader found leader. Member: %v lead: %x", i, lead)
+			tb.Logf("waitMembersForLeader found leader. Member: %v lead: %x", i, lead)
 			return i
 		}
 	}
 
-	t.Logf("waitMembersForLeader failed (-1)")
+	tb.Logf("waitMembersForLeader failed (-1)")
 	return -1
 }
 
@@ -1457,7 +1457,7 @@ func (c *Cluster) Endpoints() []string {
 	return endpoints
 }
 
-func (c *Cluster) ClusterClient(t testing.TB, opts ...framecfg.ClientOption) (client *clientv3.Client, err error) {
+func (c *Cluster) ClusterClient(tb testing.TB, opts ...framecfg.ClientOption) (client *clientv3.Client, err error) {
 	cfg, err := c.newClientCfg()
 	if err != nil {
 		return nil, err
@@ -1469,7 +1469,7 @@ func (c *Cluster) ClusterClient(t testing.TB, opts ...framecfg.ClientOption) (cl
 	if err != nil {
 		return nil, err
 	}
-	t.Cleanup(func() {
+	tb.Cleanup(func() {
 		client.Close()
 	})
 	return client, nil
diff --git a/tests/framework/integration/integration.go b/tests/framework/integration/integration.go
index 6e5de0cd5..6152ca705 100644
--- a/tests/framework/integration/integration.go
+++ b/tests/framework/integration/integration.go
@@ -41,11 +41,11 @@ func (e integrationRunner) TestMain(m *testing.M) {
 	testutil.MustTestMainWithLeakDetection(m)
 }
 
-func (e integrationRunner) BeforeTest(t testing.TB) {
-	BeforeTest(t)
+func (e integrationRunner) BeforeTest(tb testing.TB) {
+	BeforeTest(tb)
 }
 
-func (e integrationRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.ClusterOption) intf.Cluster {
+func (e integrationRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config.ClusterOption) intf.Cluster {
 	var err error
 	cfg := config.NewClusterConfig(opts...)
 	integrationCfg := ClusterConfig{
@@ -55,27 +55,27 @@ func (e integrationRunner) NewCluster(ctx context.Context, t testing.TB, opts ..
 		AuthToken:                  cfg.AuthToken,
 		SnapshotCount:              cfg.SnapshotCount,
 	}
-	integrationCfg.ClientTLS, err = tlsInfo(t, cfg.ClientTLS)
+	integrationCfg.ClientTLS, err = tlsInfo(tb, cfg.ClientTLS)
 	if err != nil {
-		t.Fatalf("ClientTLS: %s", err)
+		tb.Fatalf("ClientTLS: %s", err)
 	}
-	integrationCfg.PeerTLS, err = tlsInfo(t, cfg.PeerTLS)
+	integrationCfg.PeerTLS, err = tlsInfo(tb, cfg.PeerTLS)
 	if err != nil {
-		t.Fatalf("PeerTLS: %s", err)
+		tb.Fatalf("PeerTLS: %s", err)
 	}
 	return &integrationCluster{
-		Cluster: NewCluster(t, &integrationCfg),
-		t:       t,
+		Cluster: NewCluster(tb, &integrationCfg),
+		t:       tb,
 		ctx:     ctx,
 	}
 }
 
-func tlsInfo(t testing.TB, cfg config.TLSConfig) (*transport.TLSInfo, error) {
+func tlsInfo(tb testing.TB, cfg config.TLSConfig) (*transport.TLSInfo, error) {
 	switch cfg {
 	case config.NoTLS:
 		return nil, nil
 	case config.AutoTLS:
-		tls, err := transport.SelfCert(zap.NewNop(), t.TempDir(), []string{"localhost"}, 1)
+		tls, err := transport.SelfCert(zap.NewNop(), tb.TempDir(), []string{"localhost"}, 1)
 		if err != nil {
 			return nil, fmt.Errorf("failed to generate cert: %w", err)
 		}
diff --git a/tests/framework/integration/testing.go b/tests/framework/integration/testing.go
index a4d03c532..7e0dd38d4 100644
--- a/tests/framework/integration/testing.go
+++ b/tests/framework/integration/testing.go
@@ -138,18 +138,18 @@ func assertInTestContext(t testutil.TB) {
 	}
 }
 
-func NewEmbedConfig(t testing.TB, name string) *embed.Config {
+func NewEmbedConfig(tb testing.TB, name string) *embed.Config {
 	cfg := embed.NewConfig()
 	cfg.Name = name
-	lg := zaptest.NewLogger(t, zaptest.Level(zapcore.InfoLevel)).Named(cfg.Name)
+	lg := zaptest.NewLogger(tb, zaptest.Level(zapcore.InfoLevel)).Named(cfg.Name)
 	cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(lg)
-	cfg.Dir = t.TempDir()
+	cfg.Dir = tb.TempDir()
 	return cfg
 }
 
-func NewClient(t testing.TB, cfg clientv3.Config) (*clientv3.Client, error) {
+func NewClient(tb testing.TB, cfg clientv3.Config) (*clientv3.Client, error) {
 	if cfg.Logger == nil {
-		cfg.Logger = zaptest.NewLogger(t).Named("client")
+		cfg.Logger = zaptest.NewLogger(tb).Named("client")
 	}
 	return clientv3.New(cfg)
 }
diff --git a/tests/framework/unit/unit.go b/tests/framework/unit/unit.go
index f822b7dd1..d09ef0bdd 100644
--- a/tests/framework/unit/unit.go
+++ b/tests/framework/unit/unit.go
@@ -42,10 +42,10 @@ func (e unitRunner) TestMain(m *testing.M) {
 	}
 }
 
-func (e unitRunner) BeforeTest(t testing.TB) {
+func (e unitRunner) BeforeTest(tb testing.TB) {
 }
 
-func (e unitRunner) NewCluster(ctx context.Context, t testing.TB, opts ...config.ClusterOption) intf.Cluster {
-	testutil.SkipTestIfShortMode(t, "Cannot create clusters in --short tests")
+func (e unitRunner) NewCluster(ctx context.Context, tb testing.TB, opts ...config.ClusterOption) intf.Cluster {
+	testutil.SkipTestIfShortMode(tb, "Cannot create clusters in --short tests")
 	return nil
 }
diff --git a/tests/go.mod b/tests/go.mod
index 51e39fa7e..f6d2af450 100644
--- a/tests/go.mod
+++ b/tests/go.mod
@@ -23,8 +23,8 @@ require (
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
 	github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
-	github.com/prometheus/client_golang v1.21.1
-	github.com/prometheus/client_model v0.6.1
+	github.com/prometheus/client_golang v1.22.0
+	github.com/prometheus/client_model v0.6.2
 	github.com/prometheus/common v0.63.0
 	github.com/soheilhy/cmux v0.1.5
 	github.com/stretchr/testify v1.10.0
@@ -47,7 +47,7 @@ require (
 	golang.org/x/crypto v0.37.0
 	golang.org/x/sync v0.13.0
 	golang.org/x/time v0.11.0
-	google.golang.org/grpc v1.71.1
+	google.golang.org/grpc v1.72.0
 	google.golang.org/protobuf v1.36.6
 )
 
@@ -74,7 +74,6 @@ require (
 	github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jonboulle/clockwork v0.5.0 // indirect
-	github.com/klauspost/compress v1.18.0 // indirect
 	github.com/mattn/go-colorable v0.1.14 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-runewidth v0.0.16 // indirect
@@ -94,7 +93,7 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
 	go.opentelemetry.io/otel/metric v1.35.0 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/net v0.38.0 // indirect
+	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/sys v0.32.0 // indirect
 	golang.org/x/text v0.24.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
diff --git a/tests/go.sum b/tests/go.sum
index 258c8c970..13ae31603 100644
--- a/tests/go.sum
+++ b/tests/go.sum
@@ -108,11 +108,11 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
-github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
 github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
 github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
 github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
@@ -200,8 +200,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -251,8 +251,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
-google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
+google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
+google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
 google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
 google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/tests/integration/v3_leadership_test.go b/tests/integration/v3_leadership_test.go
index ea2b730c6..148c2edf8 100644
--- a/tests/integration/v3_leadership_test.go
+++ b/tests/integration/v3_leadership_test.go
@@ -236,14 +236,14 @@ func TestFirstCommitNotification(t *testing.T) {
 
 func checkFirstCommitNotification(
 	ctx context.Context,
-	t testing.TB,
+	tb testing.TB,
 	member *integration.Member,
 	leaderAppliedIndex uint64,
 	notifier <-chan struct{},
 ) error {
 	// wait until server applies all the changes of leader
 	for member.Server.AppliedIndex() < leaderAppliedIndex {
-		t.Logf("member.Server.AppliedIndex():%v <= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex)
+		tb.Logf("member.Server.AppliedIndex():%v <= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex)
 		select {
 		case <-ctx.Done():
 			return ctx.Err()
@@ -261,7 +261,7 @@ func checkFirstCommitNotification(
 			)
 		}
 	default:
-		t.Logf("member.Server.AppliedIndex():%v >= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex)
+		tb.Logf("member.Server.AppliedIndex():%v >= leaderAppliedIndex:%v", member.Server.AppliedIndex(), leaderAppliedIndex)
 		return fmt.Errorf(
 			"notification was not triggered, member ID: %d",
 			member.ID(),
diff --git a/tests/robustness/report/wal.go b/tests/robustness/report/wal.go
index 077ddc889..bb9b56e9b 100644
--- a/tests/robustness/report/wal.go
+++ b/tests/robustness/report/wal.go
@@ -114,10 +114,13 @@ func ReadWAL(lg *zap.Logger, dataDir string) (state raftpb.HardState, ents []raf
 		_, state, ents, err = w.ReadAll()
 		w.Close()
 		if err != nil {
-			if errors.Is(err, wal.ErrSnapshotNotFound) || errors.Is(err, wal.ErrSliceOutOfRange) {
+			if errors.Is(err, wal.ErrSnapshotNotFound) {
 				lg.Info("Error occurred when reading WAL entries", zap.Error(err))
 				return state, ents, nil
 			}
+			if errors.Is(err, wal.ErrSliceOutOfRange) {
+				return state, nil, fmt.Errorf("failed to read WAL, err: %w", err)
+			}
 			// we can only repair ErrUnexpectedEOF and we never repair twice.
 			if repaired || !errors.Is(err, io.ErrUnexpectedEOF) {
 				return state, nil, fmt.Errorf("failed to read WAL, cannot be repaired, err: %w", err)
diff --git a/tools/etcd-dump-logs/etcd-dump-log_test.go b/tools/etcd-dump-logs/etcd-dump-log_test.go
index bb08ec114..c2581cebb 100644
--- a/tools/etcd-dump-logs/etcd-dump-log_test.go
+++ b/tools/etcd-dump-logs/etcd-dump-log_test.go
@@ -82,10 +82,10 @@ func TestEtcdDumpLogEntryType(t *testing.T) {
 			expected, err := os.ReadFile(path.Join(binDir, argtest.fileExpected))
 			require.NoError(t, err)
 
-			assert.EqualValues(t, string(expected), string(actual))
+			assert.Equal(t, string(expected), string(actual))
 			// The output files contains a lot of trailing whitespaces... difficult to diagnose without printing them explicitly.
 			// TODO(ptabor): Get rid of the whitespaces both in code and the test-files.
-			assert.EqualValues(t, strings.ReplaceAll(string(expected), " ", "_"), strings.ReplaceAll(string(actual), " ", "_"))
+			assert.Equal(t, strings.ReplaceAll(string(expected), " ", "_"), strings.ReplaceAll(string(actual), " ", "_"))
 		})
 	}
 }