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  }