github.com/cloudberrydb/gpbackup@v1.0.3-0.20240118031043-5410fd45eed6/integration/parallel_queries_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/cloudberrydb/gp-common-go-libs/dbconn"
     9  	"github.com/cloudberrydb/gp-common-go-libs/testhelper"
    10  	"github.com/cloudberrydb/gpbackup/options"
    11  	"github.com/cloudberrydb/gpbackup/restore"
    12  	"github.com/cloudberrydb/gpbackup/toc"
    13  	"github.com/cloudberrydb/gpbackup/utils"
    14  
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  	. "github.com/onsi/gomega/gbytes"
    18  )
    19  
    20  var _ = Describe("backup, utils, and restore integration tests related to parallelism", func() {
    21  	Describe("Connection pooling tests", func() {
    22  		var tempConn *dbconn.DBConn
    23  		BeforeEach(func() {
    24  			tempConn = dbconn.NewDBConnFromEnvironment("testdb")
    25  			tempConn.MustConnect(2)
    26  		})
    27  		AfterEach(func() {
    28  			tempConn.Close()
    29  			tempConn = nil
    30  		})
    31  		It("exhibits session-like behavior when successive queries are executed on the same connection", func() {
    32  			_, _ = tempConn.Exec("SET client_min_messages TO error;", 1)
    33  			/*
    34  			 * The default value of client_min_messages is "notice", so now connection 1
    35  			 * should have it set to "error" and 0 should still have it set to "notice".
    36  			 */
    37  			notInSession := dbconn.MustSelectString(tempConn, "SELECT setting AS string FROM pg_settings WHERE name = 'client_min_messages';", 0)
    38  			inSession := dbconn.MustSelectString(tempConn, "SELECT setting AS string FROM pg_settings WHERE name = 'client_min_messages';", 1)
    39  			Expect(notInSession).To(Equal("notice"))
    40  			Expect(inSession).To(Equal("error"))
    41  		})
    42  	})
    43  	Describe("Parallel statement execution tests", func() {
    44  		/*
    45  		 * We can't inspect goroutines directly to check for parallelism without
    46  		 * adding runtime hooks to the code, so we test parallelism by executing
    47  		 * statements with varying pg_sleep durations.  In the serial case these
    48  		 * statements will complete in order of execution, while in the parallel
    49  		 * case they will complete in order of increasing sleep duration.
    50  		 *
    51  		 * Because a call to now() will record the timestamp at the start of the
    52  		 * session instead of the timestamp after the pg_sleep call, we must add
    53  		 * the sleep duration to the now() timestamp to get an accurate result.
    54  		 *
    55  		 * Using sleep durations on the order of 0.5 seconds will slow down test
    56  		 * runs slightly, but this is necessary to overcome query execution time
    57  		 * fluctuations.
    58  		 */
    59  		/*
    60  		 * We use a separate connection even for serial runs to avoid losing the
    61  		 * configuration of the main connection variable.
    62  		 */
    63  		var tempConn *dbconn.DBConn
    64  		orderQuery := "SELECT exec_index AS string FROM public.timestamps ORDER BY exec_time;"
    65  		BeforeEach(func() {
    66  			tempConn = dbconn.NewDBConnFromEnvironment("testdb")
    67  			restore.SetConnection(tempConn)
    68  			tempConn.MustConnect(4)
    69  			testhelper.AssertQueryRuns(tempConn, "SET ROLE testrole")
    70  
    71  		})
    72  		AfterEach(func() {
    73  			tempConn.Close()
    74  			tempConn = nil
    75  			restore.SetConnection(connectionPool)
    76  		})
    77  		Context("no errors", func() {
    78  			first := "SELECT pg_sleep(0.5); INSERT INTO public.timestamps VALUES (1, now() + '0.5 seconds'::interval);"
    79  			second := "SELECT pg_sleep(1.5); INSERT INTO public.timestamps VALUES (2, now() + '1.5 seconds'::interval);"
    80  			third := "INSERT INTO public.timestamps VALUES (3, now());"
    81  			fourth := "SELECT pg_sleep(1); INSERT INTO public.timestamps VALUES (4, now() + '1 second'::interval);"
    82  			statements := []toc.StatementWithType{
    83  				{ObjectType: "TABLE", Statement: first},
    84  				{ObjectType: "DATABASE", Statement: second},
    85  				{ObjectType: "SEQUENCE", Statement: third},
    86  				{ObjectType: "DATABASE", Statement: fourth},
    87  			}
    88  			BeforeEach(func() {
    89  				testhelper.AssertQueryRuns(tempConn, "CREATE TABLE public.timestamps(exec_index int, exec_time timestamp);")
    90  			})
    91  			AfterEach(func() {
    92  				testhelper.AssertQueryRuns(tempConn, "DROP TABLE public.timestamps;")
    93  			})
    94  			It("can execute all statements in the list serially", func() {
    95  				expectedOrderArray := []string{"1", "2", "3", "4"}
    96  				restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, false)
    97  				resultOrderArray := dbconn.MustSelectStringSlice(tempConn, orderQuery)
    98  				Expect(resultOrderArray).To(Equal(expectedOrderArray))
    99  			})
   100  
   101  			It("can execute all statements in the list in parallel", func() {
   102  				expectedOrderArray := []string{"3", "1", "4", "2"}
   103  				restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, true)
   104  				resultOrderArray := dbconn.MustSelectStringSlice(tempConn, orderQuery)
   105  				Expect(resultOrderArray).To(Equal(expectedOrderArray))
   106  			})
   107  
   108  		})
   109  		Context("error conditions", func() {
   110  			goodStmt := "SELECT * FROM pg_class LIMIT 1;"
   111  			syntaxError := "BAD SYNTAX;"
   112  			statements := []toc.StatementWithType{
   113  				{ObjectType: "TABLE", Statement: goodStmt},
   114  				{ObjectType: "INDEX", Statement: syntaxError},
   115  			}
   116  			Context("on-error-continue is not set", func() {
   117  				It("panics after exiting goroutines when running serially", func() {
   118  					errorMessage := ""
   119  					defer func() {
   120  						if r := recover(); r != nil {
   121  							errorMessage = strings.TrimSpace(fmt.Sprintf("%v", r))
   122  							Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`)))
   123  							Expect(errorMessage).To(ContainSubstring(`[CRITICAL]:-ERROR: syntax error at or near "BAD"`))
   124  							Expect(errorMessage).To(Not(ContainSubstring("goroutine")))
   125  						}
   126  					}()
   127  					restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, false)
   128  				})
   129  				It("panics after exiting goroutines when running in parallel", func() {
   130  					errorMessage := ""
   131  					defer func() {
   132  						if r := recover(); r != nil {
   133  							errorMessage = strings.TrimSpace(fmt.Sprintf("%v", r))
   134  							Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`)))
   135  							Expect(errorMessage).To(ContainSubstring(`[CRITICAL]:-ERROR: syntax error at or near "BAD"`))
   136  							Expect(errorMessage).To(Not(ContainSubstring("goroutine")))
   137  						}
   138  					}()
   139  					restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, true)
   140  				})
   141  			})
   142  			Context("on-error-continue is set", func() {
   143  				BeforeEach(func() {
   144  					_ = restoreCmdFlags.Set(options.ON_ERROR_CONTINUE, "true")
   145  				})
   146  				It("does not panic, but logs errors when running serially", func() {
   147  					restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, false)
   148  					Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`)))
   149  					Expect(stderr).To(Say(regexp.QuoteMeta("[ERROR]:-Encountered 1 errors during metadata restore; see log file gbytes.Buffer for a list of failed statements.")))
   150  					Expect(stderr).To(Not(Say(regexp.QuoteMeta("goroutine"))))
   151  				})
   152  				It("does not panic, but logs errors when running in parallel", func() {
   153  					restore.ExecuteStatementsAndCreateProgressBar(statements, "", utils.PB_NONE, true)
   154  					Expect(logFile).To(Say(regexp.QuoteMeta(`[DEBUG]:-Error encountered when executing statement: BAD SYNTAX; Error was: ERROR: syntax error at or near "BAD"`)))
   155  					Expect(stderr).To(Say(regexp.QuoteMeta("[ERROR]:-Encountered 1 errors during metadata restore; see log file gbytes.Buffer for a list of failed statements.")))
   156  					Expect(stderr).To(Not(Say(regexp.QuoteMeta("goroutine"))))
   157  				})
   158  			})
   159  
   160  		})
   161  	})
   162  })