Skip to content

Commit e1adfe5

Browse files
Slachaider-chat-bot
andcommitted
refactor: check all table column types with single query before freeze
Co-authored-by: aider (anthropic/claude-opus-4-5-20251101) <[email protected]>
1 parent 088e891 commit e1adfe5

File tree

2 files changed

+87
-12
lines changed

2 files changed

+87
-12
lines changed

pkg/backup/create.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,22 @@ func (b *Backuper) createBackupLocal(ctx context.Context, backupName, diffFromRe
301301
}
302302
}
303303

304+
// Check all tables for consistent column types BEFORE any freeze operations
305+
// This avoids having unfrozen data parts if only one table contains inconsistent column data types
306+
// https://github.com/Altinity/clickhouse-backup/issues/529
307+
if b.cfg.ClickHouse.CheckPartsColumns && doBackupData {
308+
tablesToCheck := make([]clickhouse.Table, 0, len(tables))
309+
for _, table := range tables {
310+
if !table.Skip && table.BackupType == clickhouse.ShardBackupFull {
311+
tablesToCheck = append(tablesToCheck, table)
312+
}
313+
}
314+
if err := b.ch.CheckSystemPartsColumnsForTables(ctx, tablesToCheck); err != nil {
315+
return errors.Wrap(err, "CheckSystemPartsColumnsForTables failed")
316+
}
317+
log.Debug().Msgf("CheckSystemPartsColumnsForTables passed for %d tables", len(tablesToCheck))
318+
}
319+
304320
var backupDataSize, backupObjectDiskSize, backupMetadataSize uint64
305321
var metaMutex sync.Mutex
306322
createBackupWorkingGroup, createCtx := errgroup.WithContext(ctx)
@@ -892,11 +908,8 @@ func (b *Backuper) AddTableToLocalBackup(ctx context.Context, backupName string,
892908
}
893909
return nil, nil, nil, nil, nil
894910
}
895-
if b.cfg.ClickHouse.CheckPartsColumns {
896-
if err := b.ch.CheckSystemPartsColumns(ctx, table); err != nil {
897-
return nil, nil, nil, nil, err
898-
}
899-
}
911+
// Note: CheckPartsColumns is now done for all tables at once in createBackupLocal
912+
// before any AddTableToLocalBackup calls to avoid unfrozen data parts on failure
900913
// backup data
901914
if err := b.ch.FreezeTable(ctx, table, shadowBackupUUID); err != nil {
902915
return nil, nil, nil, nil, err

pkg/clickhouse/clickhouse.go

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,18 +1391,80 @@ func (ch *ClickHouse) CheckReplicationInProgress(table metadata.TableMetadata) (
13911391
return true, nil
13921392
}
13931393

1394+
// ColumnDataTypesWithTable extends ColumnDataTypes with database and table information for batch queries
1395+
type ColumnDataTypesWithTable struct {
1396+
Database string `ch:"database"`
1397+
Table string `ch:"table"`
1398+
Column string `ch:"column"`
1399+
Types []string `ch:"uniq_types"`
1400+
}
1401+
13941402
// CheckSystemPartsColumns check data parts types consistency https://github.com/Altinity/clickhouse-backup/issues/529#issuecomment-1554460504
13951403
func (ch *ClickHouse) CheckSystemPartsColumns(ctx context.Context, table *Table) error {
1396-
var err error
1397-
partColumnsDataTypes := make([]ColumnDataTypes, 0)
1398-
partsColumnsSQL := "SELECT column, groupUniqArray(type) AS uniq_types " +
1404+
tables := []Table{*table}
1405+
return ch.CheckSystemPartsColumnsForTables(ctx, tables)
1406+
}
1407+
1408+
// CheckSystemPartsColumnsForTables checks data parts types consistency for multiple tables using a single SQL query
1409+
// This avoids having unfrozen data parts if only one table contains inconsistent column data types
1410+
func (ch *ClickHouse) CheckSystemPartsColumnsForTables(ctx context.Context, tables []Table) error {
1411+
if len(tables) == 0 {
1412+
return nil
1413+
}
1414+
1415+
// Build the WHERE clause for all tables
1416+
var conditions []string
1417+
for _, table := range tables {
1418+
if table.Skip {
1419+
continue
1420+
}
1421+
// Only check MergeTree family tables that have data
1422+
if !strings.HasSuffix(table.Engine, "MergeTree") && table.Engine != "MaterializedMySQL" && table.Engine != "MaterializedPostgreSQL" {
1423+
continue
1424+
}
1425+
conditions = append(conditions, fmt.Sprintf("(database='%s' AND table='%s')", table.Database, table.Name))
1426+
}
1427+
1428+
if len(conditions) == 0 {
1429+
return nil
1430+
}
1431+
1432+
partColumnsDataTypes := make([]ColumnDataTypesWithTable, 0)
1433+
partsColumnsSQL := "SELECT database, table, column, groupUniqArray(type) AS uniq_types " +
13991434
"FROM system.parts_columns " +
1400-
"WHERE active AND database=? AND table=? AND type NOT LIKE 'Enum%(%' AND type NOT LIKE 'Tuple(%' AND type NOT LIKE 'Nullable(Enum%(%' AND type NOT LIKE 'Nullable(Tuple(%' AND type NOT LIKE 'Array(Tuple(%' AND type NOT LIKE 'Nullable(Array(Tuple(%' " +
1401-
"GROUP BY column HAVING length(uniq_types) > 1"
1402-
if err = ch.SelectContext(ctx, &partColumnsDataTypes, partsColumnsSQL, table.Database, table.Name); err != nil {
1435+
"WHERE active AND (" + strings.Join(conditions, " OR ") + ") " +
1436+
"AND type NOT LIKE 'Enum%(%' AND type NOT LIKE 'Tuple(%' AND type NOT LIKE 'Nullable(Enum%(%' " +
1437+
"AND type NOT LIKE 'Nullable(Tuple(%' AND type NOT LIKE 'Array(Tuple(%' AND type NOT LIKE 'Nullable(Array(Tuple(%' " +
1438+
"GROUP BY database, table, column HAVING length(uniq_types) > 1"
1439+
1440+
if err := ch.SelectContext(ctx, &partColumnsDataTypes, partsColumnsSQL); err != nil {
14031441
return err
14041442
}
1405-
return ch.CheckTypesConsistency(table, partColumnsDataTypes)
1443+
1444+
// Group results by table and check consistency
1445+
tableDataTypes := make(map[string][]ColumnDataTypes)
1446+
for _, colData := range partColumnsDataTypes {
1447+
key := fmt.Sprintf("%s.%s", colData.Database, colData.Table)
1448+
tableDataTypes[key] = append(tableDataTypes[key], ColumnDataTypes{
1449+
Column: colData.Column,
1450+
Types: colData.Types,
1451+
})
1452+
}
1453+
1454+
// Check each table that has inconsistent types
1455+
for _, table := range tables {
1456+
if table.Skip {
1457+
continue
1458+
}
1459+
key := fmt.Sprintf("%s.%s", table.Database, table.Name)
1460+
if colTypes, exists := tableDataTypes[key]; exists {
1461+
if err := ch.CheckTypesConsistency(&table, colTypes); err != nil {
1462+
return err
1463+
}
1464+
}
1465+
}
1466+
1467+
return nil
14061468
}
14071469

14081470
var dateWithParams = regexp.MustCompile(`^(Date[^(]+)\([^)]+\)`)

0 commit comments

Comments
 (0)