Skip to content

Commit eefea29

Browse files
craig[bot]arulajmani
andcommitted
Merge #148571
148571: kvclient: change lock elision logic r=stevendanna a=arulajmani Previously, when write requests had MustAcquireExclusiveLock set on them, we would elide a transformed locking Get request regardless of the strength with which a lock was known to exist on the key. This patch cleans things up to check the strength with which the known to exist lock is held, only eliding the locking Get if the lock is held with Exclusive lock strength. I've modified the TestTxnWriteBufferElidesUnnecessaryLockingRequests to account for buffered shared locks in the future. This isn't testing anything meaningful yet, though, because no lock knowledge is added to the buffer for locking reads currently. Epic: none Release note: None Co-authored-by: Arul Ajmani <[email protected]>
2 parents 5aac329 + ddddf61 commit eefea29

File tree

2 files changed

+107
-64
lines changed

2 files changed

+107
-64
lines changed

pkg/kv/kvclient/kvcoord/txn_interceptor_write_buffer.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ func (twb *txnWriteBuffer) applyTransformations(
743743
_, lockStr, isServed := twb.maybeServeRead(t.Key, t.Sequence)
744744
// To elide the locking request, we must have both a value (to evaluate
745745
// the condition) and a lock.
746-
if isServed && lockExcludesWrites(lockStr) {
746+
if isServed && lockStr == lock.Exclusive {
747747
record.stripped = true
748748
} else {
749749
record.transformed = true
@@ -767,7 +767,7 @@ func (twb *txnWriteBuffer) applyTransformations(
767767
// If the MustAcquireExclusiveLock flag is set then we need to add a
768768
// locking Get to the BatchRequest, including if the key doesn't exist. We
769769
// can elide this locking request when we already have an existing lock.
770-
lockRequired := t.MustAcquireExclusiveLock && !lockExcludesWrites(lockStr)
770+
lockRequired := t.MustAcquireExclusiveLock && lockStr != lock.Exclusive
771771
if lockRequired {
772772
var getReqU kvpb.RequestUnion
773773
getReqU.MustSetInner(&kvpb.GetRequest{
@@ -787,9 +787,9 @@ func (twb *txnWriteBuffer) applyTransformations(
787787
_, lockStr, served := twb.maybeServeRead(t.Key, t.Sequence)
788788
// If MustAcquireExclusiveLock flag is set on the DeleteRequest, then we
789789
// need to add a locking Get to the BatchRequest, including if the key
790-
// doesn't exist. We can elide this locking request when we have both a
791-
// value (to populate the FoundKey field in the response) and a lock.
792-
lockRequired := t.MustAcquireExclusiveLock && !(served && lockExcludesWrites(lockStr))
790+
// doesn't exist. We can only elide this locking request when we have both
791+
// a value (to populate the FoundKey field in the response) and a lock.
792+
lockRequired := t.MustAcquireExclusiveLock && !(served && lockStr == lock.Exclusive)
793793
if lockRequired {
794794
var getReqU kvpb.RequestUnion
795795
getReqU.MustSetInner(&kvpb.GetRequest{
@@ -1799,10 +1799,6 @@ func (li *lockedKeyInfo) rollbackSequence(seq enginepb.TxnSeq) bool {
17991799
return stillHeld
18001800
}
18011801

1802-
func lockExcludesWrites(str lock.Strength) bool {
1803-
return str >= lock.Shared
1804-
}
1805-
18061802
// getKey reads the key for the next KV from a slice of BatchResponses field of
18071803
// {,Reverse}ScanResponse. The KV is encoded in the following format:
18081804
//

pkg/kv/kvclient/kvcoord/txn_interceptor_write_buffer_test.go

Lines changed: 102 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2865,12 +2865,18 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
28652865
// it locks. Every place where this is false represents a missed opportunity
28662866
// to avoid a locking request.
28672867
buffersLock bool
2868+
// bufferedLockStr indicates the strength of the lock that is buffered. Used
2869+
// in conjunction with buffersLock above.
2870+
bufferedLockStr lock.Strength
28682871
// isFullyCovered, when true, indicates that this request can be served
2869-
// completely from the buffer if a previous request locked the value. This
2870-
// is different from requiresValue as it indicates that requests that aren't
2871-
// fully covered must always be sent to KV since there is no way to know if
2872-
// the buffer has all required required values.
2873-
isFullyCovered bool
2872+
// completely from the buffer if a previous request locked the value at an
2873+
// equal or higher lock strength. If the request is never fully covered, use
2874+
// lock.None.
2875+
//
2876+
// This is different from requiresValue as it
2877+
// indicates that requests that aren't fully covered must always be sent to
2878+
// KV since there is no way to know if the buffer has all required values.
2879+
isFullyCoveredByLockStrength lock.Strength
28742880

28752881
generateRequest func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction)
28762882
optionalGenSecondRequest func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction)
@@ -2884,24 +2890,26 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
28842890
keyC := roachpb.Key("c")
28852891

28862892
// A number of the requests expect a single locking get to be produced. This
2887-
// validate function is shared across them.
2888-
validateLockingGet := func(t *testing.T, ba *kvpb.BatchRequest) *kvpb.BatchResponse {
2889-
getReq := ba.Requests[0].GetGet()
2890-
require.NotNil(t, getReq)
2891-
require.Equal(t, lock.Exclusive, getReq.KeyLockingStrength)
2892-
require.Equal(t, lock.Unreplicated, getReq.KeyLockingDurability)
2893-
resp := ba.CreateReply()
2894-
resp.Txn = ba.Txn
2895-
return resp
2893+
// validator function is shared across them.
2894+
validateLockingGetOfStr := func(str lock.Strength) func(t *testing.T, ba *kvpb.BatchRequest) *kvpb.BatchResponse {
2895+
return func(t *testing.T, ba *kvpb.BatchRequest) *kvpb.BatchResponse {
2896+
getReq := ba.Requests[0].GetGet()
2897+
require.NotNil(t, getReq)
2898+
require.Equal(t, str, getReq.KeyLockingStrength)
2899+
require.Equal(t, lock.Unreplicated, getReq.KeyLockingDurability) // NB: always Unreplicated
2900+
resp := ba.CreateReply()
2901+
resp.Txn = ba.Txn
2902+
return resp
2903+
}
28962904
}
28972905

28982906
reqs := []lockingRequests{
28992907
{
2900-
name: "ReplicatedLockingScanScanFormat=KEY_VALUES",
2901-
buffersLock: false,
2902-
buffersValue: false,
2903-
requiresValue: true,
2904-
isFullyCovered: false,
2908+
name: "ReplicatedLockingScanScanFormat=KEY_VALUES",
2909+
buffersLock: false,
2910+
buffersValue: false,
2911+
requiresValue: true,
2912+
isFullyCoveredByLockStrength: lock.None, // Scans are never covered
29052913
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
29062914
ba.Add(&kvpb.ScanRequest{
29072915
KeyLockingStrength: lock.Exclusive,
@@ -2923,11 +2931,11 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
29232931
},
29242932
},
29252933
{
2926-
name: "ReplicatedLockingScanScanFormat=BATCH_RESPONSE",
2927-
buffersLock: false,
2928-
buffersValue: false,
2929-
requiresValue: true,
2930-
isFullyCovered: false,
2934+
name: "ReplicatedLockingScanScanFormat=BATCH_RESPONSE",
2935+
buffersLock: false,
2936+
buffersValue: false,
2937+
requiresValue: true,
2938+
isFullyCoveredByLockStrength: lock.None, // Scans are never covered
29312939
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
29322940
ba.Add(&kvpb.ScanRequest{
29332941
KeyLockingStrength: lock.Exclusive,
@@ -2953,11 +2961,11 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
29532961
},
29542962
},
29552963
{
2956-
name: "ReplicatedLockingReverseScan",
2957-
buffersLock: false,
2958-
buffersValue: false,
2959-
requiresValue: true,
2960-
isFullyCovered: false,
2964+
name: "ReplicatedLockingReverseScan",
2965+
buffersLock: false,
2966+
buffersValue: false,
2967+
requiresValue: true,
2968+
isFullyCoveredByLockStrength: lock.None, // ReverseScans are never covered
29612969
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
29622970
ba.Add(&kvpb.ReverseScanRequest{
29632971
KeyLockingStrength: lock.Exclusive,
@@ -2980,11 +2988,11 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
29802988
},
29812989
},
29822990
{
2983-
name: "ReplicatedLockingGet",
2984-
buffersLock: false,
2985-
buffersValue: false,
2986-
requiresValue: true,
2987-
isFullyCovered: true,
2991+
name: "ReplicatedLockingGet(Strength=Exclusive)",
2992+
buffersLock: false,
2993+
buffersValue: false,
2994+
requiresValue: true,
2995+
isFullyCoveredByLockStrength: lock.Exclusive,
29882996
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
29892997
getReq := &kvpb.GetRequest{
29902998
RequestHeader: kvpb.RequestHeader{Key: keyA, Sequence: txn.Sequence},
@@ -2993,42 +3001,77 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
29933001
getReq.KeyLockingStrength = lock.Exclusive
29943002
ba.Add(getReq)
29953003
},
2996-
validateAndGenerateResponse: validateLockingGet,
3004+
validateAndGenerateResponse: validateLockingGetOfStr(lock.Exclusive),
3005+
},
3006+
{
3007+
name: "UnReplicatedLockingGet(Strength=Exclusive)",
3008+
buffersLock: false,
3009+
buffersValue: false,
3010+
requiresValue: true,
3011+
isFullyCoveredByLockStrength: lock.Exclusive,
3012+
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
3013+
getReq := &kvpb.GetRequest{
3014+
RequestHeader: kvpb.RequestHeader{Key: keyA, Sequence: txn.Sequence},
3015+
}
3016+
getReq.KeyLockingDurability = lock.Unreplicated
3017+
getReq.KeyLockingStrength = lock.Exclusive
3018+
ba.Add(getReq)
3019+
},
3020+
validateAndGenerateResponse: validateLockingGetOfStr(lock.Exclusive),
3021+
},
3022+
{
3023+
name: "ReplicatedLockingGet(Strength=Shared)",
3024+
buffersLock: false,
3025+
buffersValue: false,
3026+
requiresValue: true,
3027+
isFullyCoveredByLockStrength: lock.Shared,
3028+
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
3029+
getReq := &kvpb.GetRequest{
3030+
RequestHeader: kvpb.RequestHeader{Key: keyA, Sequence: txn.Sequence},
3031+
}
3032+
getReq.KeyLockingDurability = lock.Replicated
3033+
getReq.KeyLockingStrength = lock.Shared
3034+
ba.Add(getReq)
3035+
},
3036+
validateAndGenerateResponse: validateLockingGetOfStr(lock.Shared),
29973037
},
29983038
{
2999-
name: "PutMustAcquireExclusive",
3000-
buffersLock: true,
3001-
buffersValue: true,
3002-
requiresValue: false,
3003-
isFullyCovered: true,
3039+
name: "PutMustAcquireExclusive",
3040+
buffersLock: true,
3041+
bufferedLockStr: lock.Exclusive,
3042+
buffersValue: true,
3043+
requiresValue: false,
3044+
isFullyCoveredByLockStrength: lock.Exclusive,
30043045
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
30053046
putReq := putArgs(keyA, valueAStr, txn.Sequence)
30063047
putReq.MustAcquireExclusiveLock = true
30073048
ba.Add(putReq)
30083049
},
3009-
validateAndGenerateResponse: validateLockingGet,
3050+
validateAndGenerateResponse: validateLockingGetOfStr(lock.Exclusive),
30103051
},
30113052
{
3012-
name: "DeleteMustAcquireExclusive",
3013-
buffersLock: true,
3014-
buffersValue: true,
3053+
name: "DeleteMustAcquireExclusive",
3054+
buffersLock: true,
3055+
bufferedLockStr: lock.Exclusive,
3056+
buffersValue: true,
30153057
// Delete requires a value to correctly return the number of deleted row.
3016-
requiresValue: true,
3017-
isFullyCovered: true,
3058+
requiresValue: true,
3059+
isFullyCoveredByLockStrength: lock.Exclusive,
30183060
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
30193061
delReq := delArgs(keyA, txn.Sequence)
30203062
delReq.MustAcquireExclusiveLock = true
30213063
ba.Add(delReq)
30223064
},
3023-
validateAndGenerateResponse: validateLockingGet,
3065+
validateAndGenerateResponse: validateLockingGetOfStr(lock.Exclusive),
30243066
},
30253067
{
3026-
name: "ConditionalPut",
3027-
buffersLock: true,
3028-
buffersValue: true,
3068+
name: "ConditionalPut",
3069+
buffersLock: true,
3070+
bufferedLockStr: lock.Exclusive,
3071+
buffersValue: true,
30293072
// ConditionalPut requires a value to evaluate its expected bytes condition.
3030-
requiresValue: true,
3031-
isFullyCovered: true,
3073+
requiresValue: true,
3074+
isFullyCoveredByLockStrength: lock.Exclusive,
30323075
generateRequest: func(t *testing.T, ba *kvpb.BatchRequest, txn *roachpb.Transaction) {
30333076
ba.Add(cputArgs(keyA, valueAStr, "", txn.Sequence))
30343077
},
@@ -3039,7 +3082,7 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
30393082
cput.AllowIfDoesNotExist = true
30403083
ba.Add(cput)
30413084
},
3042-
validateAndGenerateResponse: validateLockingGet,
3085+
validateAndGenerateResponse: validateLockingGetOfStr(lock.Exclusive),
30433086
},
30443087
}
30453088

@@ -3080,8 +3123,12 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
30803123
secondReq.generateRequest(t, ba, &txn)
30813124
}
30823125

3126+
// The second request is fully covered if the first request has already
3127+
// acquired a lock at a sufficient lock strength.
3128+
isFullyCovered := secondReq.isFullyCoveredByLockStrength != lock.None &&
3129+
firstReq.bufferedLockStr >= secondReq.isFullyCoveredByLockStrength
30833130
mockSender.MockSend(func(ba *kvpb.BatchRequest) (*kvpb.BatchResponse, *kvpb.Error) {
3084-
if secondReq.isFullyCovered {
3131+
if isFullyCovered {
30853132
resp := ba.CreateReply()
30863133
resp.Txn = ba.Txn
30873134
return resp, nil
@@ -3092,7 +3139,7 @@ func TestTxnWriteBufferElidesUnnecessaryLockingRequests(t *testing.T) {
30923139
})
30933140

30943141
expectedCalls := 0
3095-
if !secondReq.isFullyCovered {
3142+
if !isFullyCovered {
30963143
expectedCalls = 1
30973144
}
30983145
numCalled := mockSender.NumCalled()

0 commit comments

Comments
 (0)