@@ -4794,6 +4794,156 @@ func TestQueryTimeout(t *testing.T) {
47944794 testutil .Equals (t , context .DeadlineExceeded , res .Err )
47954795}
47964796
4797+ func TestMaxSamples (t * testing.T ) {
4798+ t .Parallel ()
4799+
4800+ t .Run ("max_samples with rate function" , func (t * testing.T ) {
4801+ t .Parallel ()
4802+ storage := teststorage .New (t )
4803+ defer storage .Close ()
4804+
4805+ app := storage .Appender (context .Background ())
4806+ // Create 1000 series with samples every 15s for 5 minutes
4807+ for i := range 1000 {
4808+ for ts := int64 (0 ); ts <= 300 ; ts += 15 {
4809+ _ , err := app .Append (0 , labels .FromStrings (labels .MetricName , "test_metric" , "series" , strconv .Itoa (i )), ts * 1000 , float64 (ts ))
4810+ require .NoError (t , err )
4811+ }
4812+ }
4813+ require .NoError (t , app .Commit ())
4814+
4815+ // With 1000 series and a 2m window, rate() will keep ~8 samples per series in memory
4816+ // = ~8000 samples total
4817+ query := `rate(test_metric[2m])`
4818+ start := time .Unix (120 , 0 )
4819+ end := time .Unix (300 , 0 )
4820+ step := 30 * time .Second
4821+
4822+ t .Run ("exceeds limit" , func (t * testing.T ) {
4823+ ng := engine .New (engine.Opts {
4824+ EngineOpts : promql.EngineOpts {
4825+ Timeout : 1 * time .Hour ,
4826+ MaxSamples : 5000 , // Lower than ~8000 expected
4827+ },
4828+ })
4829+ q , err := ng .NewRangeQuery (context .Background (), storage , nil , query , start , end , step )
4830+ require .NoError (t , err )
4831+ res := q .Exec (context .Background ())
4832+ require .Error (t , res .Err , "expected max_samples error" )
4833+ require .Contains (t , res .Err .Error (), "query processing would load too many samples into memory" )
4834+ })
4835+
4836+ t .Run ("within limit" , func (t * testing.T ) {
4837+ ng := engine .New (engine.Opts {
4838+ EngineOpts : promql.EngineOpts {
4839+ Timeout : 1 * time .Hour ,
4840+ MaxSamples : 50000 , // Higher than ~8000 expected
4841+ },
4842+ })
4843+ q , err := ng .NewRangeQuery (context .Background (), storage , nil , query , start , end , step )
4844+ require .NoError (t , err )
4845+ res := q .Exec (context .Background ())
4846+ require .NoError (t , res .Err )
4847+ })
4848+ })
4849+
4850+ t .Run ("max_samples with vector selector" , func (t * testing.T ) {
4851+ t .Parallel ()
4852+ storage := teststorage .New (t )
4853+ defer storage .Close ()
4854+
4855+ app := storage .Appender (context .Background ())
4856+ // 10000 series, each step will have 10000 samples in memory
4857+ for i := range 10000 {
4858+ for ts := int64 (0 ); ts <= 300 ; ts += 30 {
4859+ _ , err := app .Append (0 , labels .FromStrings (labels .MetricName , "test_metric" , "series" , strconv .Itoa (i )), ts * 1000 , float64 (ts ))
4860+ require .NoError (t , err )
4861+ }
4862+ }
4863+ require .NoError (t , app .Commit ())
4864+
4865+ query := `test_metric`
4866+ start := time .Unix (0 , 0 )
4867+ end := time .Unix (60 , 0 )
4868+ step := 30 * time .Second
4869+
4870+ ng := engine .New (engine.Opts {
4871+ EngineOpts : promql.EngineOpts {
4872+ Timeout : 1 * time .Hour ,
4873+ MaxSamples : 5000 , // Lower than 10000 series per step
4874+ },
4875+ })
4876+ q , err := ng .NewRangeQuery (context .Background (), storage , nil , query , start , end , step )
4877+ require .NoError (t , err )
4878+ res := q .Exec (context .Background ())
4879+ require .Error (t , res .Err )
4880+ require .Contains (t , res .Err .Error (), "query processing would load too many samples into memory" )
4881+ })
4882+
4883+ t .Run ("max_samples with subquery" , func (t * testing.T ) {
4884+ t .Parallel ()
4885+ storage := teststorage .New (t )
4886+ defer storage .Close ()
4887+
4888+ app := storage .Appender (context .Background ())
4889+ // 1000 series with subquery that accumulates samples
4890+ for i := range 1000 {
4891+ for ts := int64 (0 ); ts <= 600 ; ts += 15 {
4892+ _ , err := app .Append (0 , labels .FromStrings (labels .MetricName , "test_metric" , "series" , strconv .Itoa (i )), ts * 1000 , float64 (ts ))
4893+ require .NoError (t , err )
4894+ }
4895+ }
4896+ require .NoError (t , app .Commit ())
4897+
4898+ // Subquery with 2m range and 30s step = 5 steps per evaluation
4899+ // With 1000 series, that's ~5000 samples in ring buffer
4900+ query := `sum_over_time(test_metric[2m:30s])`
4901+ start := time .Unix (120 , 0 )
4902+ end := time .Unix (300 , 0 )
4903+ step := 60 * time .Second
4904+
4905+ ng := engine .New (engine.Opts {
4906+ EngineOpts : promql.EngineOpts {
4907+ Timeout : 1 * time .Hour ,
4908+ MaxSamples : 1000 , // Lower than expected
4909+ },
4910+ })
4911+ q , err := ng .NewRangeQuery (context .Background (), storage , nil , query , start , end , step )
4912+ require .NoError (t , err )
4913+ res := q .Exec (context .Background ())
4914+ require .Error (t , res .Err )
4915+ require .Contains (t , res .Err .Error (), "query processing would load too many samples into memory" )
4916+ })
4917+
4918+ t .Run ("max_samples disabled by default" , func (t * testing.T ) {
4919+ t .Parallel ()
4920+ storage := teststorage .New (t )
4921+ defer storage .Close ()
4922+
4923+ app := storage .Appender (context .Background ())
4924+ for i := range 100 {
4925+ for ts := int64 (0 ); ts < 300 ; ts += 30 {
4926+ _ , err := app .Append (0 , labels .FromStrings (labels .MetricName , "test_metric" , "series" , strconv .Itoa (i )), ts * 1000 , float64 (ts ))
4927+ require .NoError (t , err )
4928+ }
4929+ }
4930+ require .NoError (t , app .Commit ())
4931+
4932+ query := `rate(test_metric[1m])`
4933+ start := time .Unix (0 , 0 )
4934+ end := time .Unix (300 , 0 )
4935+ step := 30 * time .Second
4936+
4937+ ng := engine .New (engine.Opts {
4938+ EngineOpts : promql.EngineOpts {Timeout : 1 * time .Hour },
4939+ })
4940+ q , err := ng .NewRangeQuery (context .Background (), storage , nil , query , start , end , step )
4941+ require .NoError (t , err )
4942+ res := q .Exec (context .Background ())
4943+ require .NoError (t , res .Err )
4944+ })
4945+ }
4946+
47974947type hintRecordingQuerier struct {
47984948 storage.Querier
47994949 mux sync.Mutex
0 commit comments