github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/client_replica_backpressure_test.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package kvserver_test 12 13 import ( 14 "context" 15 "fmt" 16 "net/url" 17 "sync" 18 "sync/atomic" 19 "testing" 20 "time" 21 22 "github.com/cockroachdb/cockroach/pkg/base" 23 "github.com/cockroachdb/cockroach/pkg/keys" 24 "github.com/cockroachdb/cockroach/pkg/kv/kvserver" 25 "github.com/cockroachdb/cockroach/pkg/roachpb" 26 "github.com/cockroachdb/cockroach/pkg/testutils" 27 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 28 "github.com/cockroachdb/cockroach/pkg/testutils/testcluster" 29 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 30 "github.com/cockroachdb/cockroach/pkg/util/randutil" 31 "github.com/cockroachdb/errors" 32 "github.com/jackc/pgx" 33 "github.com/stretchr/testify/require" 34 ) 35 36 // Test that mitigations to backpressure when reducing the range size work. 37 func TestBackpressureNotAppliedWhenReducingRangeSize(t *testing.T) { 38 defer leaktest.AfterTest(t)() 39 40 rRand, _ := randutil.NewPseudoRand() 41 ctx := context.Background() 42 43 // Some arbitrary data sizes we'll load into a table and then use to derive 44 // range size parameters. We want something not too tiny but also not too big 45 // that it takes a while to load. 46 const ( 47 rowSize = 16 << 10 // 16 KiB 48 dataSize = 512 << 10 // 512 KiB 49 numRows = dataSize / rowSize 50 ) 51 52 // setup will set up a testcluster with a table filled with data. All splits 53 // will be blocked until the returned closure is called. 54 setup := func(t *testing.T, numServers int) ( 55 tc *testcluster.TestCluster, 56 args base.TestClusterArgs, 57 tdb *sqlutils.SQLRunner, 58 tablePrefix roachpb.Key, 59 unblockSplit func(), 60 waitForBlockedRange func(id roachpb.RangeID), 61 ) { 62 // Add a testing knob to block split transactions which we'll enable before 63 // we return from setup. 64 var allowSplits atomic.Value 65 allowSplits.Store(true) 66 unblockCh := make(chan struct{}, 1) 67 var rangesBlocked sync.Map 68 args = base.TestClusterArgs{ 69 ServerArgs: base.TestServerArgs{ 70 Knobs: base.TestingKnobs{ 71 Store: &kvserver.StoreTestingKnobs{ 72 TestingRequestFilter: func(ctx context.Context, ba roachpb.BatchRequest) *roachpb.Error { 73 if ba.Header.Txn != nil && ba.Header.Txn.Name == "split" && !allowSplits.Load().(bool) { 74 rangesBlocked.Store(ba.Header.RangeID, true) 75 defer rangesBlocked.Delete(ba.Header.RangeID) 76 select { 77 case <-unblockCh: 78 return roachpb.NewError(errors.Errorf("splits disabled")) 79 case <-ctx.Done(): 80 <-ctx.Done() 81 } 82 } 83 return nil 84 }, 85 }, 86 }, 87 }, 88 } 89 tc = testcluster.StartTestCluster(t, numServers, args) 90 require.NoError(t, tc.WaitForFullReplication()) 91 92 // Create the table, split it off, and load it up with data. 93 tdb = sqlutils.MakeSQLRunner(tc.ServerConn(0)) 94 tdb.Exec(t, "CREATE TABLE foo (k INT PRIMARY KEY, v BYTES NOT NULL)") 95 96 var tableID int 97 tdb.QueryRow(t, "SELECT table_id FROM crdb_internal.tables WHERE name = 'foo'").Scan(&tableID) 98 require.NotEqual(t, 0, tableID) 99 tablePrefix = keys.SystemSQLCodec.TablePrefix(uint32(tableID)) 100 tc.SplitRangeOrFatal(t, tablePrefix) 101 require.NoError(t, tc.WaitForSplitAndInitialization(tablePrefix)) 102 103 for i := 0; i < dataSize/rowSize; i++ { 104 tdb.Exec(t, "UPSERT INTO foo VALUES ($1, $2)", 105 rRand.Intn(numRows), randutil.RandBytes(rRand, rowSize)) 106 } 107 108 // Block splits and return. 109 allowSplits.Store(false) 110 var closeOnce sync.Once 111 unblockSplit = func() { 112 closeOnce.Do(func() { 113 allowSplits.Store(true) 114 close(unblockCh) 115 }) 116 } 117 waitForBlockedRange = func(id roachpb.RangeID) { 118 testutils.SucceedsSoon(t, func() error { 119 if _, ok := rangesBlocked.Load(id); !ok { 120 return errors.Errorf("waiting for %v to be blocked", id) 121 } 122 return nil 123 }) 124 } 125 return tc, args, tdb, tablePrefix, unblockSplit, waitForBlockedRange 126 } 127 128 waitForZoneConfig := func(t *testing.T, tc *testcluster.TestCluster, tablePrefix roachpb.Key, exp int64) { 129 testutils.SucceedsSoon(t, func() error { 130 for i := 0; i < tc.NumServers(); i++ { 131 s := tc.Server(i) 132 _, r := getFirstStoreReplica(t, s, tablePrefix) 133 _, zone := r.DescAndZone() 134 if *zone.RangeMaxBytes != exp { 135 return fmt.Errorf("expected %d, got %d", exp, *zone.RangeMaxBytes) 136 } 137 } 138 return nil 139 }) 140 } 141 142 moveTableToNewStore := func(t *testing.T, tc *testcluster.TestCluster, args base.TestClusterArgs, tablePrefix roachpb.Key) { 143 tc.AddServer(t, args.ServerArgs) 144 testutils.SucceedsSoon(t, func() error { 145 desc, err := tc.LookupRange(tablePrefix) 146 require.NoError(t, err) 147 voters := desc.Replicas().Voters() 148 if len(voters) == 1 && voters[0].NodeID == tc.Server(1).NodeID() { 149 return nil 150 } 151 if len(voters) == 1 { 152 desc, err = tc.AddReplicas(tablePrefix, tc.Target(1)) 153 if err != nil { 154 return err 155 } 156 } 157 if err = tc.TransferRangeLease(desc, tc.Target(1)); err != nil { 158 return err 159 } 160 _, err = tc.RemoveReplicas(tablePrefix, tc.Target(0)) 161 return err 162 }) 163 } 164 165 t.Run("no backpressure when much larger on existing node", func(t *testing.T) { 166 tc, _, tdb, tablePrefix, unblockSplits, _ := setup(t, 1) 167 defer tc.Stopper().Stop(ctx) 168 defer unblockSplits() 169 170 tdb.Exec(t, "ALTER TABLE foo CONFIGURE ZONE USING "+ 171 "range_max_bytes = $1, range_min_bytes = $2", dataSize/5, dataSize/10) 172 waitForZoneConfig(t, tc, tablePrefix, dataSize/5) 173 174 // Don't observe backpressure. 175 tdb.Exec(t, "UPSERT INTO foo VALUES ($1, $2)", 176 rRand.Intn(10000000), randutil.RandBytes(rRand, rowSize)) 177 }) 178 179 t.Run("no backpressure when much larger on new node", func(t *testing.T) { 180 tc, args, tdb, tablePrefix, unblockSplits, _ := setup(t, 1) 181 defer tc.Stopper().Stop(ctx) 182 defer unblockSplits() 183 184 // We didn't want to have to load too much data into these ranges because 185 // it makes the testing slower so let's lower the threshold at which we'll 186 // consider the range to be way over the backpressure limit from megabytes 187 // down to kilobytes. 188 tdb.Exec(t, "SET CLUSTER SETTING kv.range.backpressure_byte_tolerance = '1 KiB'") 189 190 tdb.Exec(t, "ALTER TABLE foo CONFIGURE ZONE USING "+ 191 "range_max_bytes = $1, range_min_bytes = $2", dataSize/5, dataSize/10) 192 waitForZoneConfig(t, tc, tablePrefix, dataSize/5) 193 194 // Then we'll add a new server and move the table there. 195 moveTableToNewStore(t, tc, args, tablePrefix) 196 197 // Don't observe backpressure. 198 tdb.Exec(t, "UPSERT INTO foo VALUES ($1, $2)", 199 rRand.Intn(10000000), randutil.RandBytes(rRand, rowSize)) 200 }) 201 202 t.Run("no backpressure when near limit on existing node", func(t *testing.T) { 203 tc, _, tdb, tablePrefix, unblockSplits, _ := setup(t, 1) 204 defer tc.Stopper().Stop(ctx) 205 defer unblockSplits() 206 207 // We didn't want to have to load too much data into these ranges because 208 // it makes the testing slower so let's lower the threshold at which we'll 209 // consider the range to be way over the backpressure limit from megabytes 210 // down to kilobytes. 211 tdb.Exec(t, "SET CLUSTER SETTING kv.range.backpressure_byte_tolerance = '128 KiB'") 212 213 // Now we'll change the range_max_bytes to be half the range size less a bit 214 // so that the range size is above the backpressure threshold but within the 215 // backpressureByteTolerance. We won't see backpressure because the range 216 // will remember its previous zone config setting. 217 s, repl := getFirstStoreReplica(t, tc.Server(0), tablePrefix.Next()) 218 newMax := repl.GetMVCCStats().Total()/2 - 32<<10 219 newMin := newMax / 4 220 tdb.Exec(t, "ALTER TABLE foo CONFIGURE ZONE USING "+ 221 "range_max_bytes = $1, range_min_bytes = $2", newMax, newMin) 222 waitForZoneConfig(t, tc, tablePrefix, newMax) 223 224 // Don't observe backpressure because we remember the previous max size on 225 // this node. 226 tdb.Exec(t, "UPSERT INTO foo VALUES ($1, $2)", 227 rRand.Intn(10000000), randutil.RandBytes(rRand, rowSize)) 228 229 // Allow one split to occur and make sure that the remembered value is 230 // cleared. 231 unblockSplits() 232 233 testutils.SucceedsSoon(t, func() error { 234 if size := repl.LargestPreviousMaxRangeSizeBytes(); size != 0 { 235 _ = s.ForceSplitScanAndProcess() 236 return errors.Errorf("expected LargestPreviousMaxRangeSizeBytes to be 0, got %d", size) 237 } 238 return nil 239 }) 240 }) 241 // This case is very similar to the above case but differs in that the range 242 // is moved to a new node after the range size is decreased. This new node 243 // never knew about the old, larger range size, and thus will backpressure 244 // writes. This is the one case that is not mitigated by either 245 // backpressureByteTolerance or largestPreviousMaxRangeSizeBytes. 246 t.Run("backpressure when near limit on new node", func(t *testing.T) { 247 tc, args, tdb, tablePrefix, unblockSplits, waitForBlocked := setup(t, 1) 248 defer tc.Stopper().Stop(ctx) 249 defer unblockSplits() 250 251 // Now we'll change the range_max_bytes to be half the range size less a 252 // bit. This is the range where we expect to observe backpressure. 253 _, repl := getFirstStoreReplica(t, tc.Server(0), tablePrefix.Next()) 254 newMax := repl.GetMVCCStats().Total()/2 - 32<<10 255 newMin := newMax / 4 256 tdb.Exec(t, "ALTER TABLE foo CONFIGURE ZONE USING "+ 257 "range_max_bytes = $1, range_min_bytes = $2", newMax, newMin) 258 waitForZoneConfig(t, tc, tablePrefix, newMax) 259 260 // Then we'll add a new server and move the table there. 261 moveTableToNewStore(t, tc, args, tablePrefix) 262 263 s, repl := getFirstStoreReplica(t, tc.Server(1), tablePrefix) 264 s.SetReplicateQueueActive(false) 265 require.Len(t, repl.Desc().Replicas().All(), 1) 266 // We really need to make sure that the split queue has hit this range, 267 // otherwise we'll fail to backpressure. 268 go func() { _ = s.ForceSplitScanAndProcess() }() 269 waitForBlocked(repl.RangeID) 270 271 // Observe backpressure now that the range is just over the limit. 272 // Use pgx so that cancellation does something reasonable. 273 url, cleanup := sqlutils.PGUrl(t, tc.Server(1).ServingSQLAddr(), "", url.User("root")) 274 defer cleanup() 275 conf, err := pgx.ParseConnectionString(url.String()) 276 require.NoError(t, err) 277 c, err := pgx.Connect(conf) 278 require.NoError(t, err) 279 ctxWithCancel, cancel := context.WithCancel(ctx) 280 defer cancel() 281 upsertErrCh := make(chan error) 282 go func() { 283 _, err := c.ExecEx(ctxWithCancel, "UPSERT INTO foo VALUES ($1, $2)", 284 nil /* options */, rRand.Intn(numRows), randutil.RandBytes(rRand, rowSize)) 285 upsertErrCh <- err 286 }() 287 288 select { 289 case <-time.After(10 * time.Millisecond): 290 cancel() 291 case err := <-upsertErrCh: 292 t.Fatalf("expected no error because the request should hang, got %v", err) 293 } 294 require.Equal(t, context.Canceled, <-upsertErrCh) 295 }) 296 }