Skip to content

Commit aa6267d

Browse files
craig[bot]yuzefovich
andcommitted
143947: explain: fix rare flake in recently added test r=yuzefovich a=yuzefovich Recently added `TestMaximumMemoryUsage` can encounter a rare flake when some metamorphic variables are set - the ComponentStats proto might be dropped from the trace, so we won't have "maximum memory usage" stat that the test expects. Fix this by adding a retry loop. Fixes: #143833. Release note: None 143951: kvcoord: fix CPut handling for intra-batch conflicts r=yuzefovich a=yuzefovich Previously, for condition evaluation part of CPuts, we would ignore writes that were performed within the same KV batch. That was the case since we performed the read from the buffer when applying the transformation but would actually buffer writes after the KV server response comes back, so previous writes within the same KV batch would be invisible. This is now fixed by buffering the write when applying the transformation. Also avoid flushing any buffered writes when rolling back the txn. Epic: None Release note: None 143955: sql/stats: remove extended sentry reporting for an error r=yuzefovich a=yuzefovich This commit reverts 85500f0. I think we fixed the root cause of the "first bucket should have NumRange=0" in 10a2ffe, so we no longer need the extended sentry reporting (which also didn't quite work in all cases). Informs: #93892. Epic: None Release note: None Co-authored-by: Yahor Yuzefovich <[email protected]>
4 parents 6705d50 + adeddb5 + da7a4eb + 114a87b commit aa6267d

File tree

8 files changed

+88
-95
lines changed

8 files changed

+88
-95
lines changed

pkg/kv/kvclient/kvcoord/txn_interceptor_write_buffer.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,12 @@ func (twb *txnWriteBuffer) SendLocked(
131131
return twb.wrapped.SendLocked(ctx, ba)
132132
}
133133

134-
if _, ok := ba.GetArg(kvpb.EndTxn); ok {
135-
// TODO(arul): should we only flush if the transaction is being committed?
136-
// If the transaction is being rolled back, we shouldn't needlessly flush
137-
// writes.
134+
if etArg, ok := ba.GetArg(kvpb.EndTxn); ok {
135+
if !etArg.(*kvpb.EndTxnRequest).Commit {
136+
// We're performing a rollback, so there is no point in flushing
137+
// anything.
138+
return twb.wrapped.SendLocked(ctx, ba)
139+
}
138140
return twb.flushWithEndTxn(ctx, ba)
139141
}
140142

@@ -375,6 +377,9 @@ func (twb *txnWriteBuffer) applyTransformations(
375377
// Send a locking Get request to the KV layer; we'll evaluate the
376378
// condition locally based on the response.
377379
baRemote.Requests = append(baRemote.Requests, getReqU)
380+
// Buffer a Put under the optimistic assumption that the condition
381+
// will be satisfied.
382+
twb.addToBuffer(t.Key, t.Value, t.Sequence)
378383

379384
case *kvpb.PutRequest:
380385
// If the MustAcquireExclusiveLock flag is set on the Put, then we need to
@@ -838,13 +843,14 @@ func (t transformation) toResp(
838843
req.AllowIfDoesNotExist,
839844
)
840845
if condFailedErr != nil {
846+
// TODO(yuzefovich): consider "poisoning" the txnWriteBuffer when we
847+
// hit a condition failed error to avoid mistaken usages (e.g. an
848+
// attempt to flush with the EndTxn request with Commit=true).
841849
pErr := kvpb.NewErrorWithTxn(condFailedErr, txn)
842850
pErr.SetErrorIndex(int32(t.index))
843851
return kvpb.ResponseUnion{}, pErr
844852
}
845-
// The condition was satisfied; buffer a Put, and return a synthesized
846-
// response.
847-
twb.addToBuffer(req.Key, req.Value, req.Sequence)
853+
// The condition was satisfied - return a synthesized response.
848854
ru.MustSetInner(&kvpb.ConditionalPutResponse{})
849855

850856
case *kvpb.PutRequest:
@@ -897,12 +903,6 @@ func (t transformation) toResp(
897903
panic("unimplemented")
898904
}
899905

900-
// TODO(arul): in the future, when we'll evaluate CPuts locally, we'll have
901-
// this function take in the result of the KVGet, save the CPut function
902-
// locally on the transformation, and use these two things to evaluate the
903-
// condition here, on the client. We'll then construct and return the
904-
// appropriate response.
905-
906906
return ru, nil
907907
}
908908

pkg/kv/kvclient/kvcoord/txn_interceptor_write_buffer_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,11 +1109,11 @@ func TestTxnWriteBufferDecomposesConditionalPuts(t *testing.T) {
11091109
require.IsType(t, &kvpb.ConditionFailedError{}, pErr.GoError())
11101110
}
11111111

1112-
// Lastly, commit the transaction. A put should only be flushed if the condition
1113-
// evaluated successfully.
1112+
// Lastly, commit or rollback the transaction. A put should only be
1113+
// flushed if the condition evaluated successfully.
11141114
ba = &kvpb.BatchRequest{}
11151115
ba.Header = kvpb.Header{Txn: &txn}
1116-
ba.Add(&kvpb.EndTxnRequest{Commit: true})
1116+
ba.Add(&kvpb.EndTxnRequest{Commit: condEvalSuccessful})
11171117

11181118
mockSender.MockSend(func(ba *kvpb.BatchRequest) (*kvpb.BatchResponse, *kvpb.Error) {
11191119
if condEvalSuccessful {

pkg/kv/kvclient/kvcoord/txn_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,7 @@ func TestTxnBufferedWritesConditionalPuts(t *testing.T) {
20112011
if expErr {
20122012
require.Error(t, err)
20132013
require.IsType(t, &kvpb.ConditionFailedError{}, err)
2014+
return err
20142015
} else {
20152016
require.NoError(t, err)
20162017
}
@@ -2022,11 +2023,10 @@ func TestTxnBufferedWritesConditionalPuts(t *testing.T) {
20222023
}
20232024
})
20242025

2025-
if commit {
2026+
if commit && !expErr {
20262027
require.NoError(t, err)
20272028
} else {
20282029
require.Error(t, err)
2029-
testutils.IsError(err, "abort")
20302030
}
20312031

20322032
// Verify the values are visible only if the transaction commited

pkg/sql/logictest/testdata/logic_test/buffered_writes

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,47 @@ query II
8282
SELECT * FROM t1
8383
----
8484
1 1
85+
86+
statement ok
87+
CREATE TABLE t2 (k INT PRIMARY KEY);
88+
89+
statement ok
90+
BEGIN;
91+
92+
statement error pgcode 23505 duplicate key value violates unique constraint "t2_pkey"
93+
INSERT INTO t2 VALUES (1), (1);
94+
95+
statement ok
96+
ROLLBACK;
97+
98+
statement ok
99+
BEGIN;
100+
101+
statement ok
102+
INSERT INTO t2 VALUES (1);
103+
104+
statement error pgcode 23505 duplicate key value violates unique constraint "t2_pkey"
105+
INSERT INTO t2 VALUES (1);
106+
107+
statement ok
108+
ROLLBACK;
109+
110+
statement ok
111+
BEGIN;
112+
113+
statement ok
114+
INSERT INTO t2 VALUES (1);
115+
116+
statement ok
117+
DELETE FROM t2 WHERE k = 1;
118+
119+
statement ok
120+
INSERT INTO t2 VALUES (1);
121+
122+
statement ok
123+
COMMIT;
124+
125+
query I
126+
SELECT * FROM t2
127+
----
128+
1

pkg/sql/opt/exec/explain/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ go_test(
8989
"//pkg/util/log",
9090
"//pkg/util/treeprinter",
9191
"@com_github_cockroachdb_datadriven//:datadriven",
92+
"@com_github_cockroachdb_errors//:errors",
9293
"@com_github_stretchr_testify//assert",
9394
"@com_github_stretchr_testify//require",
9495
"@in_gopkg_yaml_v2//:yaml_v2",

pkg/sql/opt/exec/explain/output_test.go

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
3131
"github.com/cockroachdb/cockroach/pkg/util/log"
3232
"github.com/cockroachdb/datadriven"
33+
"github.com/cockroachdb/errors"
3334
"github.com/stretchr/testify/assert"
3435
"github.com/stretchr/testify/require"
3536
yaml "gopkg.in/yaml.v2"
@@ -417,19 +418,29 @@ func TestMaximumMemoryUsage(t *testing.T) {
417418
return err
418419
})
419420

420-
rows := db.QueryStr(t, "EXPLAIN ANALYZE SELECT max(v) FROM t GROUP BY bucket;")
421-
var output strings.Builder
422-
maxMemoryRE := regexp.MustCompile(`maximum memory usage: ([\d\.]+) MiB`)
423-
var maxMemoryUsage float64
424-
for _, row := range rows {
425-
output.WriteString(row[0])
426-
output.WriteString("\n")
427-
s := strings.TrimSpace(row[0])
428-
if matches := maxMemoryRE.FindStringSubmatch(s); len(matches) > 0 {
429-
var err error
430-
maxMemoryUsage, err = strconv.ParseFloat(matches[1], 64)
431-
require.NoError(t, err)
421+
// In rare cases (due to metamorphic randomization) we might drop the
422+
// ComponentStats proto that powers "maximum memory usage" stat from the
423+
// trace, so we add a retry loop around this.
424+
testutils.SucceedsSoon(t, func() error {
425+
rows := db.QueryStr(t, "EXPLAIN ANALYZE SELECT max(v) FROM t GROUP BY bucket;")
426+
var output strings.Builder
427+
maxMemoryRE := regexp.MustCompile(`maximum memory usage: ([\d\.]+) MiB`)
428+
var maxMemoryUsage float64
429+
for _, row := range rows {
430+
output.WriteString(row[0])
431+
output.WriteString("\n")
432+
s := strings.TrimSpace(row[0])
433+
if matches := maxMemoryRE.FindStringSubmatch(s); len(matches) > 0 {
434+
var err error
435+
maxMemoryUsage, err = strconv.ParseFloat(matches[1], 64)
436+
if err != nil {
437+
return err
438+
}
439+
}
432440
}
433-
}
434-
require.Greaterf(t, maxMemoryUsage, 5.0, "expected maximum memory usage to be at least 5 MiB, full output:\n\n%s", output.String())
441+
if maxMemoryUsage < 5.0 {
442+
return errors.Newf("expected maximum memory usage to be at least 5 MiB, full output:\n\n%s", output.String())
443+
}
444+
return nil
445+
})
435446
}

pkg/sql/stats/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ go_library(
5757
"//pkg/util/log",
5858
"//pkg/util/mon",
5959
"//pkg/util/protoutil",
60-
"//pkg/util/sentryutil",
6160
"//pkg/util/stop",
6261
"//pkg/util/syncutil",
6362
"//pkg/util/timeutil",

pkg/sql/stats/forecast.go

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,17 @@ package stats
77

88
import (
99
"context"
10-
"crypto/md5"
11-
"encoding/json"
12-
"fmt"
1310
"math"
1411
"slices"
15-
"strconv"
1612
"time"
1713

1814
"github.com/cockroachdb/cockroach/pkg/jobs/jobspb"
1915
"github.com/cockroachdb/cockroach/pkg/settings"
2016
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
2117
"github.com/cockroachdb/cockroach/pkg/sql/opt/cat"
2218
"github.com/cockroachdb/cockroach/pkg/sql/sem/eval"
23-
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
2419
"github.com/cockroachdb/cockroach/pkg/sql/types"
25-
"github.com/cockroachdb/cockroach/pkg/util/errorutil"
2620
"github.com/cockroachdb/cockroach/pkg/util/log"
27-
"github.com/cockroachdb/cockroach/pkg/util/sentryutil"
2821
"github.com/cockroachdb/errors"
2922
"github.com/cockroachdb/redact"
3023
)
@@ -336,61 +329,6 @@ func forecastColumnStatistics(
336329
}
337330
forecast.HistogramData = &histData
338331
forecast.setHistogramBuckets(hist)
339-
340-
// Verify that the first two buckets (the initial NULL bucket and the first
341-
// non-NULL bucket) both have NumRange=0 and DistinctRange=0. (We must check
342-
// this after calling setHistogramBuckets to account for rounding.) See
343-
// #93892.
344-
for _, bucket := range forecast.Histogram {
345-
if bucket.NumRange != 0 || bucket.DistinctRange != 0 {
346-
// Build a JSON representation of the first several buckets in each
347-
// observed histogram so that we can figure out what happened.
348-
const debugBucketCount = 5
349-
jsonStats := make([]*JSONStatistic, 0, len(observed))
350-
351-
addStat := func(stat *TableStatistic) {
352-
jsonStat := &JSONStatistic{
353-
Name: stat.Name,
354-
CreatedAt: stat.CreatedAt.String(),
355-
Columns: []string{strconv.FormatInt(int64(stat.ColumnIDs[0]), 10)},
356-
RowCount: stat.RowCount,
357-
DistinctCount: stat.DistinctCount,
358-
NullCount: stat.NullCount,
359-
AvgSize: stat.AvgSize,
360-
}
361-
if err := jsonStat.SetHistogram(stat.HistogramData); err == nil &&
362-
len(jsonStat.HistogramBuckets) > debugBucketCount {
363-
// Limit the histogram to the first several buckets.
364-
jsonStat.HistogramBuckets = jsonStat.HistogramBuckets[0:debugBucketCount]
365-
}
366-
// Replace UpperBounds with a hash.
367-
for i := range jsonStat.HistogramBuckets {
368-
hash := md5.Sum([]byte(jsonStat.HistogramBuckets[i].UpperBound))
369-
jsonStat.HistogramBuckets[i].UpperBound = fmt.Sprintf("_%x", hash)
370-
}
371-
jsonStats = append(jsonStats, jsonStat)
372-
}
373-
addStat(forecast)
374-
for i := range observed {
375-
addStat(observed[i])
376-
}
377-
var debugging redact.SafeString
378-
if j, err := json.Marshal(jsonStats); err == nil {
379-
debugging = redact.SafeString(j)
380-
}
381-
err := errorutil.UnexpectedWithIssueErrorf(
382-
93892,
383-
"forecasted histogram had first bucket with non-zero NumRange or DistinctRange: %s",
384-
debugging,
385-
)
386-
sentryutil.SendReport(ctx, &st.SV, err)
387-
return nil, err
388-
}
389-
if bucket.UpperBound != tree.DNull {
390-
// Stop checking after the first non-NULL bucket.
391-
break
392-
}
393-
}
394332
}
395333

396334
return forecast, nil

0 commit comments

Comments
 (0)