github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/builtin_mem_usage_test.go (about) 1 // Copyright 2017 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 sql 12 13 import ( 14 "context" 15 gosql "database/sql" 16 "testing" 17 18 "github.com/cockroachdb/cockroach/pkg/base" 19 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 20 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 21 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 22 "github.com/cockroachdb/errors" 23 "github.com/lib/pq" 24 ) 25 26 // lowMemoryBudget is the memory budget used to test builtins are recording 27 // their memory use. The budget needs to be large enough to establish the 28 // initial database connection, but small enough to overflow easily. It's set 29 // to be comfortably large enough that the server can start up with a bit of 30 // extra space to overflow. 31 const lowMemoryBudget = 500000 32 33 // rowSize is the length of the string present in each row of the table created 34 // by createTableWithLongStrings. 35 const rowSize = 30000 36 37 // numRows is the number of rows to insert in createTableWithLongStrings. 38 // numRows and rowSize were picked arbitrarily but so that rowSize * numRows > 39 // lowMemoryBudget, so that aggregating them all in a CONCAT_AGG or 40 // ARRAY_AGG will exhaust lowMemoryBudget. 41 const numRows = 50 42 43 // createTableWithLongStrings creates a table with a modest number of long strings, 44 // with the intention of using them to exhaust a memory budget. 45 func createTableWithLongStrings(sqlDB *gosql.DB) error { 46 if _, err := sqlDB.Exec(` 47 CREATE DATABASE d; 48 CREATE TABLE d.t (a STRING) 49 `); err != nil { 50 return err 51 } 52 53 for i := 0; i < numRows; i++ { 54 if _, err := sqlDB.Exec(`INSERT INTO d.t VALUES (repeat('a', $1))`, rowSize); err != nil { 55 return err 56 } 57 } 58 return nil 59 } 60 61 // TestConcatAggMonitorsMemory verifies that the aggregates incrementally 62 // record their memory usage as they build up their result. 63 func TestAggregatesMonitorMemory(t *testing.T) { 64 defer leaktest.AfterTest(t)() 65 66 // By avoiding printing the aggregate results we prevent anything 67 // besides the aggregate itself from being able to catch the 68 // large memory usage. 69 statements := []string{ 70 `SELECT length(concat_agg(a)) FROM d.t`, 71 `SELECT array_length(array_agg(a), 1) FROM d.t`, 72 `SELECT json_typeof(json_agg(A)) FROM d.t`, 73 } 74 75 for _, statement := range statements { 76 s, sqlDB, _ := serverutils.StartServer(t, base.TestServerArgs{ 77 SQLMemoryPoolSize: lowMemoryBudget, 78 }) 79 80 defer s.Stopper().Stop(context.Background()) 81 82 if err := createTableWithLongStrings(sqlDB); err != nil { 83 t.Fatal(err) 84 } 85 86 _, err := sqlDB.Exec(statement) 87 if pqErr := (*pq.Error)(nil); !errors.As(err, &pqErr) || pqErr.Code != pgcode.OutOfMemory { 88 t.Fatalf("Expected \"%s\" to consume too much memory", statement) 89 } 90 } 91 } 92 93 func TestEvaluatedMemoryIsChecked(t *testing.T) { 94 defer leaktest.AfterTest(t)() 95 // We select the LENGTH here and elsewhere because if we passed the result of 96 // REPEAT up as a result, the memory error would be caught there even if 97 // REPEAT was not doing its accounting. 98 testData := []string{ 99 `SELECT length(repeat('abc', 70000000))`, 100 `SELECT crdb_internal.no_constant_folding(length(repeat('abc', 70000000)))`, 101 } 102 103 for _, statement := range testData { 104 t.Run("", func(t *testing.T) { 105 s, sqlDB, _ := serverutils.StartServer(t, base.TestServerArgs{ 106 SQLMemoryPoolSize: lowMemoryBudget, 107 }) 108 defer s.Stopper().Stop(context.Background()) 109 110 _, err := sqlDB.Exec( 111 statement, 112 ) 113 if pqErr := (*pq.Error)(nil); !errors.As(err, &pqErr) || pqErr.Code != pgcode.ProgramLimitExceeded { 114 t.Errorf("Expected \"%s\" to OOM, but it didn't", statement) 115 } 116 }) 117 } 118 }