Skip to content

Commit cb40021

Browse files
craig[bot]stevendanna
andcommitted
Merge #148438
148438: kvcoord: elide locking requests in txnWriteBuffer r=yuzefovich a=stevendanna If we know that a sufficient lock is already held at the given sequence number, we can often elide duplicate locking requests. Currently, the only requests that modify the buffer are write requests. The test adds skipped cases that would require us to buffer the returned locks and values. Epic: none Release note: None Co-authored-by: Steven Danna <[email protected]>
2 parents b2e185d + e1949f8 commit cb40021

File tree

3 files changed

+386
-63
lines changed

3 files changed

+386
-63
lines changed

pkg/kv/kvclient/kvcoord/txn_interceptor_write_buffer.go

Lines changed: 91 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -740,34 +740,35 @@ func (twb *txnWriteBuffer) applyTransformations(
740740
}
741741
switch t := req.(type) {
742742
case *kvpb.ConditionalPutRequest:
743-
record.transformed = true
744-
// NB: Regardless of whether there is already a buffered write on
745-
// this key or not, we need to send a locking Get to the KV layer to
746-
// acquire a lock. However, if we had knowledge of what locks the
747-
// transaction already holds, we could avoid the locking Get in some
748-
// cases.
749-
getReq := &kvpb.GetRequest{
750-
RequestHeader: kvpb.RequestHeader{
751-
Key: t.Key,
752-
Sequence: t.Sequence,
753-
},
754-
LockNonExisting: len(t.ExpBytes) == 0 || t.AllowIfDoesNotExist,
755-
KeyLockingStrength: lock.Exclusive,
743+
_, lockStr, isServed := twb.maybeServeRead(t.Key, t.Sequence)
744+
// To elide the locking request, we must have both a value (to evaluate
745+
// the condition) and a lock.
746+
if isServed && lockExcludesWrites(lockStr) {
747+
record.stripped = true
748+
} else {
749+
record.transformed = true
750+
getReq := &kvpb.GetRequest{
751+
RequestHeader: kvpb.RequestHeader{
752+
Key: t.Key,
753+
Sequence: t.Sequence,
754+
},
755+
LockNonExisting: len(t.ExpBytes) == 0 || t.AllowIfDoesNotExist,
756+
KeyLockingStrength: lock.Exclusive,
757+
}
758+
var getReqU kvpb.RequestUnion
759+
getReqU.MustSetInner(getReq)
760+
// Send a locking Get request to the KV layer; we'll evaluate the
761+
// condition locally based on the response.
762+
baRemote.Requests = append(baRemote.Requests, getReqU)
756763
}
757-
var getReqU kvpb.RequestUnion
758-
getReqU.MustSetInner(getReq)
759-
// Send a locking Get request to the KV layer; we'll evaluate the
760-
// condition locally based on the response.
761-
baRemote.Requests = append(baRemote.Requests, getReqU)
762764

763765
case *kvpb.PutRequest:
764-
// If the MustAcquireExclusiveLock flag is set on the Put, then we
765-
// need to add a locking Get to the BatchRequest, including if the
766-
// key doesn't exist.
767-
if t.MustAcquireExclusiveLock {
768-
// TODO(yuzefovich,ssd): ensure that we elide the lock
769-
// acquisition whenever possible (e.g. blind UPSERT in an
770-
// implicit txn).
766+
_, lockStr, _ := twb.maybeServeRead(t.Key, t.Sequence)
767+
// If the MustAcquireExclusiveLock flag is set then we need to add a
768+
// locking Get to the BatchRequest, including if the key doesn't exist. We
769+
// can elide this locking request when we already have an existing lock.
770+
lockRequired := t.MustAcquireExclusiveLock && !lockExcludesWrites(lockStr)
771+
if lockRequired {
771772
var getReqU kvpb.RequestUnion
772773
getReqU.MustSetInner(&kvpb.GetRequest{
773774
RequestHeader: kvpb.RequestHeader{
@@ -779,16 +780,17 @@ func (twb *txnWriteBuffer) applyTransformations(
779780
})
780781
baRemote.Requests = append(baRemote.Requests, getReqU)
781782
}
782-
record.stripped = !t.MustAcquireExclusiveLock
783-
record.transformed = t.MustAcquireExclusiveLock
783+
record.stripped = !lockRequired
784+
record.transformed = lockRequired
784785

785786
case *kvpb.DeleteRequest:
786-
// If MustAcquireExclusiveLock flag is set on the DeleteRequest,
787-
// then we need to add a locking Get to the BatchRequest, including
788-
// if the key doesn't exist.
789-
if t.MustAcquireExclusiveLock {
790-
// TODO(ssd): ensure that we elide the lock acquisition
791-
// whenever possible.
787+
_, lockStr, served := twb.maybeServeRead(t.Key, t.Sequence)
788+
// If MustAcquireExclusiveLock flag is set on the DeleteRequest, then we
789+
// 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))
793+
if lockRequired {
792794
var getReqU kvpb.RequestUnion
793795
getReqU.MustSetInner(&kvpb.GetRequest{
794796
RequestHeader: kvpb.RequestHeader{
@@ -800,32 +802,32 @@ func (twb *txnWriteBuffer) applyTransformations(
800802
})
801803
baRemote.Requests = append(baRemote.Requests, getReqU)
802804
}
803-
record.stripped = !t.MustAcquireExclusiveLock
804-
record.transformed = t.MustAcquireExclusiveLock
805+
record.stripped = !lockRequired
806+
record.transformed = lockRequired
805807

806808
case *kvpb.GetRequest:
807809
// If the key is in the buffer, we must serve the read from the buffer.
808810
// The actual serving of the read will happen on the response path though.
809811
stripped := false
810-
_, served := twb.maybeServeRead(t.Key, t.Sequence)
812+
_, lockStr, served := twb.maybeServeRead(t.Key, t.Sequence)
811813
if served {
812-
if t.KeyLockingStrength != lock.None {
814+
if t.KeyLockingStrength > lockStr {
813815
// Even though the Get request must be served from the buffer, as the
814816
// transaction performed a previous write to the key, we still need to
815-
// acquire a lock at the leaseholder. As a result, we can't strip the
816-
// request from the remote batch.
817-
//
818-
// TODO(arul): we could eschew sending this request if we knew there
819-
// was a sufficiently strong lock already present on the key.
820-
log.VEventf(ctx, 2, "locking %s on key %s must be sent to the server", t.Method(), t.Key)
817+
// acquire a lock at the leaseholder because we don't have a known
818+
// lock of a sufficient strength on this key. As a result, we can't
819+
// strip the request from the remote batch.
820+
log.VEventf(ctx, 2, "%s(Locking=%s) on key %s must be sent to the server",
821+
t.Method(), t.KeyLockingStrength, t.Key)
821822
baRemote.Requests = append(baRemote.Requests, ru)
822823
} else {
823824
// We'll synthesize the response from the buffer on the response path;
824-
// eschew sending the request to the KV layer as we don't need to
825-
// acquire a lock.
825+
// eschew sending the request to the KV layer since we already have a
826+
// lock of a sufficient strength on this key.
826827
stripped = true
827828
log.VEventf(
828-
ctx, 2, "non-locking %s on key %s can be fully served by the client; not sending to KV", t.Method(), t.Key,
829+
ctx, 2, "%s(Locking=%s) on key %s can be fully served by the client; not sending to KV",
830+
t.Method(), t.KeyLockingStrength, t.Key,
829831
)
830832
}
831833
} else {
@@ -866,14 +868,18 @@ func (twb *txnWriteBuffer) seekItemForSpan(key, endKey roachpb.Key) *bufferedWri
866868
// deletion tombstone on the key is present in the buffer. Additionally, a
867869
// boolean indicating whether the read request was served or not is also
868870
// returned.
871+
//
872+
// The returned locked strength is the highest lock strength known to be held at
873+
// the given sequence number.
869874
func (twb *txnWriteBuffer) maybeServeRead(
870875
key roachpb.Key, seq enginepb.TxnSeq,
871-
) (*roachpb.Value, bool) {
876+
) (*roachpb.Value, lock.Strength, bool) {
872877
it := twb.buffer.MakeIter()
873878
seek := twb.seekItemForSpan(key, nil)
874879
it.FirstOverlap(seek)
875880
if it.Valid() {
876881
bufferedVals := it.Cur().vals
882+
lockStr := it.Cur().heldStr(seq)
877883
// In the common case, we're reading the most recently buffered write. That
878884
// is, the sequence number we're reading at is greater than or equal to the
879885
// sequence number of the last write that was buffered. The list of buffered
@@ -885,15 +891,15 @@ func (twb *txnWriteBuffer) maybeServeRead(
885891
// using a binary search here instead.
886892
for i := len(bufferedVals) - 1; i >= 0; i-- {
887893
if seq >= bufferedVals[i].seq {
888-
return bufferedVals[i].valPtr(), true
894+
return bufferedVals[i].valPtr(), lockStr, true
889895
}
890896
}
891897
// We've iterated through the buffer, but it seems like our sequence number
892898
// is smaller than any buffered write performed by our transaction. We can't
893899
// serve the read locally.
894-
return nil, false
900+
return nil, lockStr, false
895901
}
896-
return nil, false
902+
return nil, lock.None, false
897903
}
898904

899905
// mergeWithScanResp takes a ScanRequest, that was sent to the KV layer, and the
@@ -994,7 +1000,7 @@ func (twb *txnWriteBuffer) mergeBufferAndResp(
9941000
case -1:
9951001
// The key in the buffer is less than the next key in the server's
9961002
// response, so we prefer it.
997-
val, served := twb.maybeServeRead(it.Cur().key, respIter.seq())
1003+
val, _, served := twb.maybeServeRead(it.Cur().key, respIter.seq())
9981004
if served && val.IsPresent() {
9991005
// NB: Only include a buffered value in the response if it hasn't been
10001006
// deleted by the transaction previously. This matches the behaviour
@@ -1007,7 +1013,7 @@ func (twb *txnWriteBuffer) mergeBufferAndResp(
10071013
case 0:
10081014
// The key exists in the buffer. We must serve the read from the buffer,
10091015
// assuming it is visible to the sequence number of the request.
1010-
val, served := twb.maybeServeRead(it.Cur().key, respIter.seq())
1016+
val, _, served := twb.maybeServeRead(it.Cur().key, respIter.seq())
10111017
if served {
10121018
if val.IsPresent() {
10131019
// NB: Only include a buffered value in the response if it hasn't been
@@ -1039,7 +1045,7 @@ func (twb *txnWriteBuffer) mergeBufferAndResp(
10391045
respIter.next()
10401046
}
10411047
for it.Valid() {
1042-
val, served := twb.maybeServeRead(it.Cur().key, respIter.seq())
1048+
val, _, served := twb.maybeServeRead(it.Cur().key, respIter.seq())
10431049
if served && val.IsPresent() {
10441050
// Like above, we'll only include the value in the response if the Scan's
10451051
// sequence number requires us to see it and it isn't a deletion
@@ -1130,7 +1136,7 @@ func (rr requestRecord) toResp(
11301136

11311137
var val *roachpb.Value
11321138
var served bool
1133-
val, served = twb.maybeServeRead(req.Key, req.Sequence)
1139+
val, _, served = twb.maybeServeRead(req.Key, req.Sequence)
11341140
if !served {
11351141
// We only use the response from KV if there wasn't already a
11361142
// buffered value for this key that our transaction wrote
@@ -1151,7 +1157,7 @@ func (rr requestRecord) toResp(
11511157
}
11521158

11531159
var lei *lockAcquisition
1154-
if exclusionTimestampRequired {
1160+
if rr.transformed && exclusionTimestampRequired {
11551161
lei = &lockAcquisition{
11561162
str: lock.Exclusive,
11571163
seq: req.Sequence,
@@ -1166,7 +1172,7 @@ func (rr requestRecord) toResp(
11661172

11671173
case *kvpb.PutRequest:
11681174
var lei *lockAcquisition
1169-
if req.MustAcquireExclusiveLock && exclusionTimestampRequired {
1175+
if rr.transformed && exclusionTimestampRequired {
11701176
lei = &lockAcquisition{
11711177
str: lock.Exclusive,
11721178
seq: req.Sequence,
@@ -1180,14 +1186,13 @@ func (rr requestRecord) toResp(
11801186
// To correctly populate FoundKey in the response, we must prefer any
11811187
// buffered values (if they exist).
11821188
var foundKey bool
1183-
val, served := twb.maybeServeRead(req.Key, req.Sequence)
1189+
val, _, served := twb.maybeServeRead(req.Key, req.Sequence)
11841190
if served {
11851191
log.VEventf(ctx, 2, "serving read portion of %s on key %s from the buffer", req.Method(), req.Key)
11861192
foundKey = val.IsPresent()
1187-
} else if req.MustAcquireExclusiveLock {
1193+
} else if rr.transformed {
11881194
// We sent a GetRequest to the KV layer to acquire an exclusive lock
1189-
// on the key, regardless of whether the key already exists or not.
1190-
// Populate FoundKey using the response.
1195+
// on the key, populate FoundKey using the response.
11911196
getResp := br.GetInner().(*kvpb.GetResponse)
11921197
if log.ExpensiveLogEnabled(ctx, 2) {
11931198
log.Eventf(ctx, "synthesizing DeleteResponse from GetResponse: %#v", getResp)
@@ -1208,7 +1213,7 @@ func (rr requestRecord) toResp(
12081213
}
12091214

12101215
var lei *lockAcquisition
1211-
if req.MustAcquireExclusiveLock && exclusionTimestampRequired {
1216+
if rr.transformed && exclusionTimestampRequired {
12121217
lei = &lockAcquisition{
12131218
str: lock.Exclusive,
12141219
seq: req.Sequence,
@@ -1222,7 +1227,7 @@ func (rr requestRecord) toResp(
12221227
twb.addToBuffer(req.Key, roachpb.Value{}, req.Sequence, req.KVNemesisSeq, lei)
12231228

12241229
case *kvpb.GetRequest:
1225-
val, served := twb.maybeServeRead(req.Key, req.Sequence)
1230+
val, _, served := twb.maybeServeRead(req.Key, req.Sequence)
12261231
if served {
12271232
getResp := &kvpb.GetResponse{}
12281233
if val.IsPresent() {
@@ -1527,6 +1532,13 @@ func (bw *bufferedWrite) exclusionExpectedSinceTimestamp() hlc.Timestamp {
15271532
return hlc.Timestamp{}
15281533
}
15291534

1535+
func (bw *bufferedWrite) heldStr(seq enginepb.TxnSeq) lock.Strength {
1536+
if bw.lki == nil {
1537+
return lock.None
1538+
}
1539+
return bw.lki.heldStr(seq)
1540+
}
1541+
15301542
func (bw *bufferedWrite) rollbackLockInfo(seq enginepb.TxnSeq) {
15311543
if bw.lki == nil {
15321544
return
@@ -1754,6 +1766,19 @@ func (li *lockedKeyInfo) heldGE(str lock.Strength) bool {
17541766
return false
17551767
}
17561768

1769+
// heldStr returns the strongest lock held at the given sequence number.
1770+
func (li *lockedKeyInfo) heldStr(seq enginepb.TxnSeq) lock.Strength {
1771+
for _, str := range unreplicatedHolderStrengths {
1772+
minSeq := li.heldStrengths[heldStrengthToIndexMap[str]]
1773+
if minSeq != notHeldSentinel {
1774+
if minSeq <= seq {
1775+
return str
1776+
}
1777+
}
1778+
}
1779+
return lock.None
1780+
}
1781+
17571782
// rollbackSequence rolls back the given sequence number. It returns false if
17581783
// the lock is no longer held at any sequence number.
17591784
func (li *lockedKeyInfo) rollbackSequence(seq enginepb.TxnSeq) bool {
@@ -1774,6 +1799,10 @@ func (li *lockedKeyInfo) rollbackSequence(seq enginepb.TxnSeq) bool {
17741799
return stillHeld
17751800
}
17761801

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

0 commit comments

Comments
 (0)