From 662c542d6cba1e92c3a0990b34d11116b729715a Mon Sep 17 00:00:00 2001 From: Benjamin Wang Date: Tue, 21 Jul 2026 16:04:12 +0100 Subject: [PATCH 1/2] Add an unit test to reproduce the issue of costTxnReq ignores nested RequestTxn Signed-off-by: Benjamin Wang --- server/storage/quota_test.go | 80 ++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 server/storage/quota_test.go diff --git a/server/storage/quota_test.go b/server/storage/quota_test.go new file mode 100644 index 00000000000..e5acb1da215 --- /dev/null +++ b/server/storage/quota_test.go @@ -0,0 +1,80 @@ +// Copyright 2026 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 storage + +import ( + "testing" + + "github.com/stretchr/testify/require" + + pb "go.etcd.io/etcd/api/v3/etcdserverpb" +) + +func TestCostTxn(t *testing.T) { + putOp := func(key, value string) *pb.RequestOp { + return &pb.RequestOp{ + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{Key: []byte(key), Value: []byte(value)}, + }, + } + } + txnOp := func(txn *pb.TxnRequest) *pb.RequestOp { + return &pb.RequestOp{ + Request: &pb.RequestOp_RequestTxn{RequestTxn: txn}, + } + } + + tests := []struct { + name string + req *pb.TxnRequest + want int + }{ + { + name: "flat put", + req: &pb.TxnRequest{ + Success: []*pb.RequestOp{putOp("foo", "bar")}, + }, + want: costPut(&pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}), + }, + { + name: "nested txn put in success branch must be counted", + req: &pb.TxnRequest{ + Success: []*pb.RequestOp{ + txnOp(&pb.TxnRequest{ + Success: []*pb.RequestOp{putOp("nested-key", "nested-value")}, + }), + }, + }, + want: costPut(&pb.PutRequest{Key: []byte("nested-key"), Value: []byte("nested-value")}), + }, + { + name: "nested txn put in failure branch must be counted", + req: &pb.TxnRequest{ + Failure: []*pb.RequestOp{ + txnOp(&pb.TxnRequest{ + Failure: []*pb.RequestOp{putOp("nested-key", "nested-value")}, + }), + }, + }, + want: costPut(&pb.PutRequest{Key: []byte("nested-key"), Value: []byte("nested-value")}), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, costTxn(tt.req)) + }) + } +} From 5cdad102e4651b6be692c76a5358ef3476dcfcea Mon Sep 17 00:00:00 2001 From: Benjamin Wang Date: Tue, 21 Jul 2026 16:05:13 +0100 Subject: [PATCH 2/2] Fix the costTxnReq ignores nested RequestTxn issue Signed-off-by: Benjamin Wang --- server/storage/quota.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/server/storage/quota.go b/server/storage/quota.go index f9ff72d7e82..2dbe83da10d 100644 --- a/server/storage/quota.go +++ b/server/storage/quota.go @@ -149,11 +149,13 @@ func (b *BackendQuota) Cost(v any) int { func costPut(r *pb.PutRequest) int { return kvOverhead + len(r.Key) + len(r.Value) } func costTxnReq(u *pb.RequestOp) int { - r := u.GetRequestPut() - if r == nil { - return 0 + if r := u.GetRequestPut(); r != nil { + return costPut(r) + } + if t := u.GetRequestTxn(); t != nil { + return costTxn(t) } - return costPut(r) + return 0 } func costTxn(r *pb.TxnRequest) int {