github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rowexec/hashjoiner_test.go (about) 1 // Copyright 2016 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 rowexec 12 13 import ( 14 "context" 15 "fmt" 16 "math" 17 "sort" 18 "strings" 19 "testing" 20 21 "github.com/cockroachdb/cockroach/pkg/base" 22 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 23 "github.com/cockroachdb/cockroach/pkg/sql/execinfra" 24 "github.com/cockroachdb/cockroach/pkg/sql/execinfrapb" 25 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 26 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 27 "github.com/cockroachdb/cockroach/pkg/sql/types" 28 "github.com/cockroachdb/cockroach/pkg/storage" 29 "github.com/cockroachdb/cockroach/pkg/testutils" 30 "github.com/cockroachdb/cockroach/pkg/testutils/distsqlutils" 31 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 32 "github.com/cockroachdb/cockroach/pkg/util/mon" 33 "github.com/cockroachdb/errors" 34 ) 35 36 func TestHashJoiner(t *testing.T) { 37 defer leaktest.AfterTest(t)() 38 39 testCases := joinerTestCases() 40 41 // Add INTERSECT ALL cases with HashJoinerSpecs. 42 for _, tc := range intersectAllTestCases() { 43 testCases = append(testCases, setOpTestCaseToJoinerTestCase(tc)) 44 } 45 46 // Add EXCEPT ALL cases with HashJoinerSpecs. 47 for _, tc := range exceptAllTestCases() { 48 testCases = append(testCases, setOpTestCaseToJoinerTestCase(tc)) 49 } 50 51 ctx := context.Background() 52 st := cluster.MakeTestingClusterSettings() 53 tempEngine, _, err := storage.NewTempEngine(ctx, storage.DefaultStorageEngine, base.DefaultTestTempStorageConfig(st), base.DefaultTestStoreSpec) 54 if err != nil { 55 t.Fatal(err) 56 } 57 defer tempEngine.Close() 58 59 evalCtx := tree.MakeTestingEvalContext(st) 60 defer evalCtx.Stop(ctx) 61 diskMonitor := mon.MakeMonitor( 62 "test-disk", 63 mon.DiskResource, 64 nil, /* curCount */ 65 nil, /* maxHist */ 66 -1, /* increment: use default block size */ 67 math.MaxInt64, 68 st, 69 ) 70 diskMonitor.Start(ctx, nil /* pool */, mon.MakeStandaloneBudget(math.MaxInt64)) 71 defer diskMonitor.Stop(ctx) 72 73 for _, c := range testCases { 74 // testFunc is a helper function that runs a hashJoin with the current 75 // test case. 76 // flowCtxSetup can optionally be provided to set up additional testing 77 // knobs in the flowCtx before instantiating a hashJoiner and hjSetup can 78 // optionally be provided to modify the hashJoiner after instantiation but 79 // before Run(). 80 testFunc := func(t *testing.T, flowCtxSetup func(f *execinfra.FlowCtx), hjSetup func(h *hashJoiner)) error { 81 side := rightSide 82 for i := 0; i < 2; i++ { 83 leftInput := distsqlutils.NewRowBuffer(c.leftTypes, c.leftInput, distsqlutils.RowBufferArgs{}) 84 rightInput := distsqlutils.NewRowBuffer(c.rightTypes, c.rightInput, distsqlutils.RowBufferArgs{}) 85 out := &distsqlutils.RowBuffer{} 86 flowCtx := execinfra.FlowCtx{ 87 EvalCtx: &evalCtx, 88 Cfg: &execinfra.ServerConfig{ 89 Settings: st, 90 TempStorage: tempEngine, 91 DiskMonitor: &diskMonitor, 92 }, 93 } 94 if flowCtxSetup != nil { 95 flowCtxSetup(&flowCtx) 96 } 97 post := execinfrapb.PostProcessSpec{Projection: true, OutputColumns: c.outCols} 98 spec := &execinfrapb.HashJoinerSpec{ 99 LeftEqColumns: c.leftEqCols, 100 RightEqColumns: c.rightEqCols, 101 Type: c.joinType, 102 OnExpr: c.onExpr, 103 } 104 h, err := newHashJoiner( 105 &flowCtx, 0 /* processorID */, spec, leftInput, 106 rightInput, &post, out, false, /* disableTempStorage */ 107 ) 108 if err != nil { 109 return err 110 } 111 outTypes := h.OutputTypes() 112 if hjSetup != nil { 113 hjSetup(h) 114 } 115 // Only force the other side after running the buffering logic once. 116 if i == 1 { 117 h.forcedStoredSide = &side 118 } 119 h.Run(context.Background()) 120 side = otherSide(h.storedSide) 121 122 if !out.ProducerClosed() { 123 return errors.New("output RowReceiver not closed") 124 } 125 126 if err := checkExpectedRows(outTypes, c.expected, out); err != nil { 127 return err 128 } 129 } 130 return nil 131 } 132 133 // Run test with a variety of initial buffer sizes. 134 for _, initialBuffer := range []int64{0, 32, 64, 128, 1024 * 1024} { 135 t.Run(fmt.Sprintf("InitialBuffer=%d", initialBuffer), func(t *testing.T) { 136 if err := testFunc(t, nil, func(h *hashJoiner) { 137 h.initialBufferSize = initialBuffer 138 }); err != nil { 139 t.Fatal(err) 140 } 141 }) 142 } 143 144 // Run test with a variety of memory limits. 145 for _, memLimit := range []int64{1, 256, 512, 1024, 2048} { 146 t.Run(fmt.Sprintf("MemLimit=%d", memLimit), func(t *testing.T) { 147 if err := testFunc(t, func(f *execinfra.FlowCtx) { 148 f.Cfg.TestingKnobs.MemoryLimitBytes = memLimit 149 }, nil); err != nil { 150 t.Fatal(err) 151 } 152 }) 153 } 154 } 155 } 156 157 func TestHashJoinerError(t *testing.T) { 158 defer leaktest.AfterTest(t)() 159 160 v := [10]sqlbase.EncDatum{} 161 for i := range v { 162 v[i] = sqlbase.DatumToEncDatum(types.Int, tree.NewDInt(tree.DInt(i))) 163 } 164 165 testCases := joinerErrorTestCases() 166 167 ctx := context.Background() 168 st := cluster.MakeTestingClusterSettings() 169 tempEngine, _, err := storage.NewTempEngine(ctx, storage.DefaultStorageEngine, base.DefaultTestTempStorageConfig(st), base.DefaultTestStoreSpec) 170 if err != nil { 171 t.Fatal(err) 172 } 173 defer tempEngine.Close() 174 175 evalCtx := tree.MakeTestingEvalContext(st) 176 defer evalCtx.Stop(ctx) 177 diskMonitor := mon.MakeMonitor( 178 "test-disk", 179 mon.DiskResource, 180 nil, /* curCount */ 181 nil, /* maxHist */ 182 -1, /* increment: use default block size */ 183 math.MaxInt64, 184 st, 185 ) 186 diskMonitor.Start(ctx, nil /* pool */, mon.MakeStandaloneBudget(math.MaxInt64)) 187 defer diskMonitor.Stop(ctx) 188 189 for _, c := range testCases { 190 // testFunc is a helper function that runs a hashJoin with the current 191 // test case after running the provided setup function. 192 testFunc := func(t *testing.T, setup func(h *hashJoiner)) error { 193 leftInput := distsqlutils.NewRowBuffer(c.leftTypes, c.leftInput, distsqlutils.RowBufferArgs{}) 194 rightInput := distsqlutils.NewRowBuffer(c.rightTypes, c.rightInput, distsqlutils.RowBufferArgs{}) 195 out := &distsqlutils.RowBuffer{} 196 flowCtx := execinfra.FlowCtx{ 197 EvalCtx: &evalCtx, 198 Cfg: &execinfra.ServerConfig{ 199 Settings: st, 200 TempStorage: tempEngine, 201 DiskMonitor: &diskMonitor, 202 }, 203 } 204 205 post := execinfrapb.PostProcessSpec{Projection: true, OutputColumns: c.outCols} 206 spec := &execinfrapb.HashJoinerSpec{ 207 LeftEqColumns: c.leftEqCols, 208 RightEqColumns: c.rightEqCols, 209 Type: c.joinType, 210 OnExpr: c.onExpr, 211 } 212 h, err := newHashJoiner( 213 &flowCtx, 0 /* processorID */, spec, leftInput, rightInput, 214 &post, out, false, /* disableTempStorage */ 215 ) 216 if err != nil { 217 return err 218 } 219 outTypes := h.OutputTypes() 220 setup(h) 221 h.Run(context.Background()) 222 223 if !out.ProducerClosed() { 224 return errors.New("output RowReceiver not closed") 225 } 226 227 return checkExpectedRows(outTypes, nil, out) 228 } 229 230 t.Run(c.description, func(t *testing.T) { 231 if err := testFunc(t, func(h *hashJoiner) { 232 h.initialBufferSize = 1024 * 32 233 }); err == nil { 234 t.Errorf("Expected an error:%s, but found nil", c.expectedErr) 235 } else if err.Error() != c.expectedErr.Error() { 236 t.Errorf("HashJoinerErrorTest: expected\n%s, but found\n%v", c.expectedErr, err) 237 } 238 }) 239 } 240 } 241 242 func checkExpectedRows( 243 types []*types.T, expectedRows sqlbase.EncDatumRows, results *distsqlutils.RowBuffer, 244 ) error { 245 var expected []string 246 for _, row := range expectedRows { 247 expected = append(expected, row.String(types)) 248 } 249 sort.Strings(expected) 250 expStr := strings.Join(expected, "") 251 252 var rets []string 253 for { 254 row, meta := results.Next() 255 if meta != nil { 256 return errors.Errorf("unexpected metadata: %v", meta) 257 } 258 if row == nil { 259 break 260 } 261 rets = append(rets, row.String(types)) 262 } 263 sort.Strings(rets) 264 retStr := strings.Join(rets, "") 265 266 if expStr != retStr { 267 return errors.Errorf("invalid results; expected:\n %s\ngot:\n %s", 268 expStr, retStr) 269 } 270 return nil 271 } 272 273 // TestHashJoinerDrain tests that, if the consumer starts draining, the 274 // hashJoiner informs the producers and drains them. 275 // 276 // Concretely, the HashJoiner is set up to read the right input fully before 277 // starting to produce rows, so only the left input will be asked to drain if 278 // the consumer is draining. 279 func TestHashJoinerDrain(t *testing.T) { 280 defer leaktest.AfterTest(t)() 281 v := [10]sqlbase.EncDatum{} 282 for i := range v { 283 v[i] = sqlbase.DatumToEncDatum(types.Int, tree.NewDInt(tree.DInt(i))) 284 } 285 spec := execinfrapb.HashJoinerSpec{ 286 LeftEqColumns: []uint32{0}, 287 RightEqColumns: []uint32{0}, 288 Type: sqlbase.InnerJoin, 289 // Implicit @1 = @2 constraint. 290 } 291 outCols := []uint32{0} 292 inputs := []sqlbase.EncDatumRows{ 293 { 294 {v[0]}, 295 {v[1]}, 296 }, 297 { 298 {v[0]}, 299 {v[1]}, 300 }, 301 } 302 expected := sqlbase.EncDatumRows{ 303 {v[0]}, 304 } 305 leftInputDrainNotification := make(chan error, 1) 306 leftInputConsumerDone := func(rb *distsqlutils.RowBuffer) { 307 // Check that draining occurs before the left input has been consumed, 308 // not at the end. 309 // The left input started with 2 rows and 1 was consumed to find out 310 // that we need to drain. So we expect 1 to be left. 311 rb.Mu.Lock() 312 defer rb.Mu.Unlock() 313 if len(rb.Mu.Records) != 1 { 314 leftInputDrainNotification <- errors.Errorf( 315 "expected 1 row left, got: %d", len(rb.Mu.Records)) 316 return 317 } 318 leftInputDrainNotification <- nil 319 } 320 leftInput := distsqlutils.NewRowBuffer( 321 sqlbase.OneIntCol, 322 inputs[0], 323 distsqlutils.RowBufferArgs{OnConsumerDone: leftInputConsumerDone}, 324 ) 325 rightInput := distsqlutils.NewRowBuffer(sqlbase.OneIntCol, inputs[1], distsqlutils.RowBufferArgs{}) 326 out := distsqlutils.NewRowBuffer( 327 sqlbase.OneIntCol, 328 nil, /* rows */ 329 distsqlutils.RowBufferArgs{AccumulateRowsWhileDraining: true}, 330 ) 331 332 settings := cluster.MakeTestingClusterSettings() 333 evalCtx := tree.MakeTestingEvalContext(settings) 334 ctx := context.Background() 335 defer evalCtx.Stop(ctx) 336 flowCtx := execinfra.FlowCtx{ 337 Cfg: &execinfra.ServerConfig{Settings: settings}, 338 EvalCtx: &evalCtx, 339 } 340 341 post := execinfrapb.PostProcessSpec{Projection: true, OutputColumns: outCols} 342 // Since the use of external storage overrides h.initialBufferSize, disable 343 // it for this test. 344 h, err := newHashJoiner( 345 &flowCtx, 0 /* processorID */, &spec, leftInput, rightInput, 346 &post, out, true, /* disableTempStorage */ 347 ) 348 if err != nil { 349 t.Fatal(err) 350 } 351 // Disable initial buffering. We always store the right stream in this case. 352 // If not disabled, both streams will be fully consumed before outputting 353 // any rows. 354 h.initialBufferSize = 0 355 356 out.ConsumerDone() 357 h.Run(context.Background()) 358 359 if !out.ProducerClosed() { 360 t.Fatalf("output RowReceiver not closed") 361 } 362 363 callbackErr := <-leftInputDrainNotification 364 if callbackErr != nil { 365 t.Fatal(callbackErr) 366 } 367 368 leftInput.Mu.Lock() 369 defer leftInput.Mu.Unlock() 370 if len(leftInput.Mu.Records) != 0 { 371 t.Fatalf("left input not drained; still %d rows in it", len(leftInput.Mu.Records)) 372 } 373 374 if err := checkExpectedRows(sqlbase.OneIntCol, expected, out); err != nil { 375 t.Fatal(err) 376 } 377 } 378 379 // TestHashJoinerDrainAfterBuildPhaseError tests that, if the HashJoiner 380 // encounters an error in the "build phase" (reading of the right input), the 381 // joiner will drain both inputs. 382 func TestHashJoinerDrainAfterBuildPhaseError(t *testing.T) { 383 defer leaktest.AfterTest(t)() 384 385 v := [10]sqlbase.EncDatum{} 386 for i := range v { 387 v[i] = sqlbase.DatumToEncDatum(types.Int, tree.NewDInt(tree.DInt(i))) 388 } 389 spec := execinfrapb.HashJoinerSpec{ 390 LeftEqColumns: []uint32{0}, 391 RightEqColumns: []uint32{0}, 392 Type: sqlbase.InnerJoin, 393 // Implicit @1 = @2 constraint. 394 } 395 outCols := []uint32{0} 396 inputs := []sqlbase.EncDatumRows{ 397 { 398 {v[0]}, 399 {v[1]}, 400 }, 401 { 402 {v[0]}, 403 {v[1]}, 404 }, 405 } 406 leftInputDrainNotification := make(chan error, 1) 407 leftInputConsumerDone := func(rb *distsqlutils.RowBuffer) { 408 // Check that draining occurs before the left input has been consumed, not 409 // at the end. 410 rb.Mu.Lock() 411 defer rb.Mu.Unlock() 412 if len(rb.Mu.Records) != 2 { 413 leftInputDrainNotification <- errors.Errorf( 414 "expected 2 rows left in the left input, got: %d", len(rb.Mu.Records)) 415 return 416 } 417 leftInputDrainNotification <- nil 418 } 419 rightInputDrainNotification := make(chan error, 1) 420 rightInputConsumerDone := func(rb *distsqlutils.RowBuffer) { 421 // Check that draining occurs before the right input has been consumed, not 422 // at the end. 423 rb.Mu.Lock() 424 defer rb.Mu.Unlock() 425 if len(rb.Mu.Records) != 2 { 426 rightInputDrainNotification <- errors.Errorf( 427 "expected 2 rows left in the right input, got: %d", len(rb.Mu.Records)) 428 return 429 } 430 rightInputDrainNotification <- nil 431 } 432 rightErrorReturned := false 433 rightInputNext := func(rb *distsqlutils.RowBuffer) (sqlbase.EncDatumRow, *execinfrapb.ProducerMetadata) { 434 if !rightErrorReturned { 435 rightErrorReturned = true 436 // The right input is going to return an error as the first thing. 437 return nil, &execinfrapb.ProducerMetadata{Err: errors.Errorf("Test error. Please drain.")} 438 } 439 // Let RowBuffer.Next() do its usual thing. 440 return nil, nil 441 } 442 leftInput := distsqlutils.NewRowBuffer( 443 sqlbase.OneIntCol, 444 inputs[0], 445 distsqlutils.RowBufferArgs{OnConsumerDone: leftInputConsumerDone}, 446 ) 447 rightInput := distsqlutils.NewRowBuffer( 448 sqlbase.OneIntCol, 449 inputs[1], 450 distsqlutils.RowBufferArgs{ 451 OnConsumerDone: rightInputConsumerDone, 452 OnNext: rightInputNext, 453 }, 454 ) 455 out := distsqlutils.NewRowBuffer( 456 sqlbase.OneIntCol, 457 nil, /* rows */ 458 distsqlutils.RowBufferArgs{}, 459 ) 460 st := cluster.MakeTestingClusterSettings() 461 evalCtx := tree.MakeTestingEvalContext(st) 462 defer evalCtx.Stop(context.Background()) 463 flowCtx := execinfra.FlowCtx{ 464 Cfg: &execinfra.ServerConfig{Settings: st}, 465 EvalCtx: &evalCtx, 466 } 467 468 // Disable external storage for this test to avoid initializing temp storage 469 // infrastructure. 470 post := execinfrapb.PostProcessSpec{Projection: true, OutputColumns: outCols} 471 h, err := newHashJoiner( 472 &flowCtx, 0 /* processorID */, &spec, leftInput, rightInput, 473 &post, out, true, /* disableTempStorage */ 474 ) 475 if err != nil { 476 t.Fatal(err) 477 } 478 // Disable initial buffering. We always store the right stream in this case. 479 h.initialBufferSize = 0 480 481 h.Run(context.Background()) 482 483 if !out.ProducerClosed() { 484 t.Fatalf("output RowReceiver not closed") 485 } 486 487 callbackErr := <-leftInputDrainNotification 488 if callbackErr != nil { 489 t.Fatal(callbackErr) 490 } 491 492 leftInput.Mu.Lock() 493 defer leftInput.Mu.Unlock() 494 if len(leftInput.Mu.Records) != 0 { 495 t.Fatalf("left input not drained; still %d rows in it", len(leftInput.Mu.Records)) 496 } 497 498 out.Mu.Lock() 499 defer out.Mu.Unlock() 500 if len(out.Mu.Records) != 1 { 501 t.Fatalf("expected 1 record, got: %d", len(out.Mu.Records)) 502 } 503 if !testutils.IsError(out.Mu.Records[0].Meta.Err, "Test error. Please drain.") { 504 t.Fatalf("expected %q, got: %v", "Test error", out.Mu.Records[0].Meta.Err) 505 } 506 } 507 508 // BenchmarkHashJoiner times how long it takes to join two tables of the same 509 // variable size. There is a 1:1 relationship between the rows of each table. 510 // TODO(asubiotto): More complex benchmarks. 511 func BenchmarkHashJoiner(b *testing.B) { 512 ctx := context.Background() 513 st := cluster.MakeTestingClusterSettings() 514 evalCtx := tree.MakeTestingEvalContext(st) 515 defer evalCtx.Stop(ctx) 516 diskMonitor := execinfra.NewTestDiskMonitor(ctx, st) 517 defer diskMonitor.Stop(ctx) 518 flowCtx := &execinfra.FlowCtx{ 519 EvalCtx: &evalCtx, 520 Cfg: &execinfra.ServerConfig{ 521 Settings: st, 522 DiskMonitor: diskMonitor, 523 }, 524 } 525 tempEngine, _, err := storage.NewTempEngine(ctx, storage.DefaultStorageEngine, base.DefaultTestTempStorageConfig(st), base.DefaultTestStoreSpec) 526 if err != nil { 527 b.Fatal(err) 528 } 529 defer tempEngine.Close() 530 flowCtx.Cfg.TempStorage = tempEngine 531 532 spec := &execinfrapb.HashJoinerSpec{ 533 LeftEqColumns: []uint32{0}, 534 RightEqColumns: []uint32{0}, 535 Type: sqlbase.InnerJoin, 536 // Implicit @1 = @2 constraint. 537 } 538 post := &execinfrapb.PostProcessSpec{} 539 540 const numCols = 1 541 for _, spill := range []bool{true, false} { 542 flowCtx.Cfg.TestingKnobs.ForceDiskSpill = spill 543 b.Run(fmt.Sprintf("spill=%t", spill), func(b *testing.B) { 544 for _, numRows := range []int{0, 1 << 2, 1 << 4, 1 << 8, 1 << 12, 1 << 16} { 545 if spill && numRows < 1<<8 { 546 // The benchmark takes a long time with a small number of rows and 547 // spilling, since the times change wildly. Disable for now. 548 continue 549 } 550 b.Run(fmt.Sprintf("rows=%d", numRows), func(b *testing.B) { 551 rows := sqlbase.MakeIntRows(numRows, numCols) 552 leftInput := execinfra.NewRepeatableRowSource(sqlbase.OneIntCol, rows) 553 rightInput := execinfra.NewRepeatableRowSource(sqlbase.OneIntCol, rows) 554 b.SetBytes(int64(8 * numRows * numCols * 2)) 555 b.ResetTimer() 556 for i := 0; i < b.N; i++ { 557 // TODO(asubiotto): Get rid of uncleared state between 558 // hashJoiner Run()s to omit instantiation time from benchmarks. 559 h, err := newHashJoiner( 560 flowCtx, 0 /* processorID */, spec, leftInput, rightInput, 561 post, &rowDisposer{}, false, /* disableTempStorage */ 562 ) 563 if err != nil { 564 b.Fatal(err) 565 } 566 h.Run(context.Background()) 567 leftInput.Reset() 568 rightInput.Reset() 569 } 570 }) 571 } 572 }) 573 } 574 }