blob: 1697ff3d3b04ef65bc7438edc7fef24656b0b1be [file]
package restore
/*
* This file contains functions related to executing multiple SQL statements in parallel.
*/
import (
"fmt"
"strings"
"sync"
"sync/atomic"
"github.com/apache/cloudberry-backup/options"
"github.com/apache/cloudberry-backup/toc"
"github.com/apache/cloudberry-backup/utils"
"github.com/apache/cloudberry-go-libs/gplog"
)
var (
mutex = &sync.Mutex{}
txMutex = &sync.Mutex{}
)
func executeStatementsForConn(statements chan toc.StatementWithType, fatalErr *error, numErrors *int32, progressBar utils.ProgressBar, whichConn int, executeInParallel bool) {
for statement := range statements {
if wasTerminated || *fatalErr != nil {
if executeInParallel {
gplog.Error("Error detected on connection %d. Terminating transactions.", whichConn)
txMutex.Lock()
if connectionPool.Tx[whichConn] != nil {
connectionPool.Rollback(whichConn)
}
txMutex.Unlock()
}
return
}
gplog.Debug("Executing statement: %s on connection: %d", strings.TrimSpace(statement.Statement), whichConn)
_, err := connectionPool.Exec(statement.Statement, whichConn)
if err != nil {
gplog.Verbose("Error encountered when executing statement: %s Error was: %s", strings.TrimSpace(statement.Statement), err.Error())
if MustGetFlagBool(options.ON_ERROR_CONTINUE) {
if executeInParallel {
atomic.AddInt32(numErrors, 1)
if statement.ObjectType == toc.OBJ_TABLE {
mutex.Lock()
errorTablesMetadata[statement.Schema+"."+statement.Name] = Empty{}
mutex.Unlock()
}
} else {
*numErrors = *numErrors + 1
if statement.ObjectType == toc.OBJ_TABLE {
errorTablesMetadata[statement.Schema+"."+statement.Name] = Empty{}
}
}
} else {
*fatalErr = err
}
}
if progressBar != nil {
progressBar.Increment()
}
}
if executeInParallel {
txMutex.Lock()
if connectionPool.Tx[whichConn] != nil {
connectionPool.Commit(whichConn)
}
txMutex.Unlock()
}
}
func findLeastBusyConn(connStatementCounts map[int]int) int {
minStatements := connStatementCounts[1]
leastBusyConn := 1
for conn, count := range connStatementCounts {
if count < minStatements {
leastBusyConn = conn
minStatements = count
}
}
return leastBusyConn
}
func scheduleStatementsOnWorkers(statements []toc.StatementWithType, numConns int) map[int][]toc.StatementWithType {
splitStatements := make(map[int][]toc.StatementWithType, numConns)
cohortConnAssignments := make(map[uint32]int)
connStatementCounts := make(map[int]int, numConns)
for i := 0; i < numConns; i++ {
connStatementCounts[i] = 0
}
// During backup, we track objects into "cohorts" that must be backed up in the same
// transaction to avoid lock contention and/or deadlocks. Split up statements to
// connections by cohort.
for i := 0; i < len(statements); i++ {
var cohort uint32
if len(statements[i].Tier) == 0 {
// older backups will not have a Tier value
cohort = 0
} else {
cohort = statements[i].Tier[1]
}
keyConn, alreadyPinned := cohortConnAssignments[cohort]
if alreadyPinned {
splitStatements[keyConn] = append(splitStatements[keyConn], statements[i])
connStatementCounts[keyConn] = connStatementCounts[keyConn] + 1
} else {
leastBusyConn := findLeastBusyConn(connStatementCounts)
cohortConnAssignments[cohort] = leastBusyConn
splitStatements[leastBusyConn] = append(splitStatements[leastBusyConn], statements[i])
connStatementCounts[leastBusyConn] = connStatementCounts[leastBusyConn] + 1
}
}
return splitStatements
}
/*
* This function creates a worker pool of N goroutines to be able to execute up
* to N statements in parallel.
*/
func ExecutePredataStatements(statements []toc.StatementWithType, progressBar utils.ProgressBar, executeInParallel bool, whichConn ...int) int32 {
var workerPool sync.WaitGroup
var fatalErr error
var numErrors int32
if len(statements) == 0 {
return 0
}
if !executeInParallel {
tasks := make(chan toc.StatementWithType, len(statements))
for _, statement := range statements {
tasks <- statement
}
close(tasks)
connNum := connectionPool.ValidateConnNum(whichConn...)
executeStatementsForConn(tasks, &fatalErr, &numErrors, progressBar, connNum, executeInParallel)
} else {
panicChan := make(chan error)
splitStatements := scheduleStatementsOnWorkers(statements, connectionPool.NumConns)
chanMap := make(map[int]chan toc.StatementWithType, connectionPool.NumConns)
// preinitialize channels to prevent concurrent read and write
for i := 0; i < connectionPool.NumConns; i++ {
chanMap[i] = make(chan toc.StatementWithType, len(splitStatements[i]))
for _, statement := range splitStatements[i] {
chanMap[i] <- statement
}
close(chanMap[i])
}
for i := 0; i < connectionPool.NumConns; i++ {
workerPool.Add(1)
go func(connNum int) {
defer func() {
if panicErr := recover(); panicErr != nil {
panicChan <- fmt.Errorf("%v", panicErr)
}
}()
defer workerPool.Done()
connNum = connectionPool.ValidateConnNum(connNum)
executeStatementsForConn(chanMap[connNum], &fatalErr, &numErrors, progressBar, connNum, executeInParallel)
}(i)
}
workerPool.Wait()
// Allow panics to crash from the main process, invoking DoCleanup
select {
case err := <-panicChan:
gplog.Fatal(err, "")
default:
// no panic, nothing to do
}
}
if fatalErr != nil {
fmt.Println("")
gplog.Fatal(fatalErr, "")
} else if numErrors > 0 {
fmt.Println("")
gplog.Error("Encountered %d errors during metadata restore; see log file %s for a list of failed statements.", numErrors, gplog.GetLogFilePath())
}
return numErrors
}
func ExecuteStatements(statements []toc.StatementWithType, progressBar utils.ProgressBar, executeInParallel bool, whichConn ...int) int32 {
var workerPool sync.WaitGroup
var fatalErr error
var numErrors int32
tasks := make(chan toc.StatementWithType, len(statements))
for _, statement := range statements {
tasks <- statement
}
close(tasks)
if !executeInParallel {
connNum := connectionPool.ValidateConnNum(whichConn...)
executeStatementsForConn(tasks, &fatalErr, &numErrors, progressBar, connNum, executeInParallel)
} else {
panicChan := make(chan error)
for i := 0; i < connectionPool.NumConns; i++ {
workerPool.Add(1)
go func(connNum int) {
defer func() {
if panicErr := recover(); panicErr != nil {
panicChan <- fmt.Errorf("%v", panicErr)
}
}()
defer workerPool.Done()
connNum = connectionPool.ValidateConnNum(connNum)
executeStatementsForConn(tasks, &fatalErr, &numErrors, progressBar, connNum, executeInParallel)
}(i)
}
workerPool.Wait()
// Allow panics to crash from the main process, invoking DoCleanup
select {
case err := <-panicChan:
gplog.Fatal(err, "")
default:
// no panic, nothing to do
}
}
if fatalErr != nil {
fmt.Println("")
gplog.Fatal(fatalErr, "")
} else if numErrors > 0 {
fmt.Println("")
gplog.Error("Encountered %d errors during metadata restore; see log file %s for a list of failed statements.", numErrors, gplog.GetLogFilePath())
}
return numErrors
}
/*
* There is an existing bug in Greenplum where creating indexes in parallel
* on an AO table that didn't have any indexes previously can cause
* deadlock.
*
* We work around this issue by restoring post data objects in
* two batches. The first batch takes one index from each table and
* restores them in parallel (which has no possibility of deadlock) and
* then the second restores all other postdata objects in parallel. After
* each table has at least one index, there is no more risk of deadlock.
*
* A third batch is created specifically for postdata metadata
* (e.g. ALTER INDEX, ALTER EVENT TRIGGER, COMMENT ON). These
* statements cannot be concurrently run with batch two since that
* is where the dependent postdata objects are being created.
*/
func BatchPostdataStatements(statements []toc.StatementWithType) ([]toc.StatementWithType, []toc.StatementWithType, []toc.StatementWithType) {
indexMap := make(map[string]bool)
firstBatch := make([]toc.StatementWithType, 0)
secondBatch := make([]toc.StatementWithType, 0)
thirdBatch := make([]toc.StatementWithType, 0)
for _, statement := range statements {
_, tableIndexPresent := indexMap[statement.ReferenceObject]
if statement.ObjectType == toc.OBJ_INDEX && !tableIndexPresent {
indexMap[statement.ReferenceObject] = true
firstBatch = append(firstBatch, statement)
} else if strings.Contains(statement.ObjectType, " METADATA") {
thirdBatch = append(thirdBatch, statement)
} else {
secondBatch = append(secondBatch, statement)
}
}
return firstBatch, secondBatch, thirdBatch
}
func BatchPredataStatements(statements []toc.StatementWithType) ([]toc.StatementWithType, map[uint32][]toc.StatementWithType, []toc.StatementWithType) {
foundNumberedTier := false
firstTierZero := make([]toc.StatementWithType, 0)
numberedTiers := make(map[uint32][]toc.StatementWithType)
secondTierZero := make([]toc.StatementWithType, 0)
for _, statement := range statements {
// For backwards compatibility, assume restores without Tier are always 0,0. This is also
// used for some object types like Schemas where we haven't yet gone through the work of
// tiering them out for parallel execution.
if len(statement.Tier) == 0 {
statement.Tier = []uint32{0, 0}
}
if statement.Tier[0] > 0 {
foundNumberedTier = true
numberedTiers[statement.Tier[0]] = append(numberedTiers[statement.Tier[0]], statement)
} else if statement.Tier[0] == 0 {
if !foundNumberedTier {
// insert into firstTierZero
firstTierZero = append(firstTierZero, statement)
} else {
// insert into secondTierZero
secondTierZero = append(secondTierZero, statement)
}
}
}
return firstTierZero, numberedTiers, secondTierZero
}