func (wr *Wrangler) applySchemaShardSimple(statusArray []*TabletStatus, preflight *mysqlctl.SchemaChangeResult, masterTabletAlias topo.TabletAlias, change string, force bool) (*mysqlctl.SchemaChangeResult, error) { // check all tablets have the same schema as the master's // BeforeSchema. If not, we shouldn't proceed relog.Info("Checking schema on all tablets") for _, status := range statusArray { diffs := mysqlctl.DiffSchemaToArray("master", preflight.BeforeSchema, status.ti.Alias().String(), status.beforeSchema) if len(diffs) > 0 { if force { relog.Warning("Tablet %v has inconsistent schema, ignoring: %v", status.ti.Alias(), strings.Join(diffs, "\n")) } else { return nil, fmt.Errorf("Tablet %v has inconsistent schema: %v", status.ti.Alias(), strings.Join(diffs, "\n")) } } } // we're good, just send to the master relog.Info("Applying schema change to master in simple mode") sc := &mysqlctl.SchemaChange{Sql: change, Force: force, AllowReplication: true, BeforeSchema: preflight.BeforeSchema, AfterSchema: preflight.AfterSchema} return wr.ApplySchema(masterTabletAlias, sc) }
func (wr *Wrangler) applySchemaKeyspace(keyspace string, change string, simple, force bool) (*mysqlctl.SchemaChangeResult, error) { shards, err := wr.ts.GetShardNames(keyspace) if err != nil { return nil, err } // corner cases if len(shards) == 0 { return nil, fmt.Errorf("No shards in keyspace %v", keyspace) } if len(shards) == 1 { relog.Info("Only one shard in keyspace %v, using ApplySchemaShard", keyspace) return wr.ApplySchemaShard(keyspace, shards[0], change, topo.TabletAlias{}, simple, force) } // Get schema on all shard masters in parallel relog.Info("Getting schema on all shards") beforeSchemas := make([]*mysqlctl.SchemaDefinition, len(shards)) shardInfos := make([]*topo.ShardInfo, len(shards)) wg := sync.WaitGroup{} mu := sync.Mutex{} getErrs := make([]string, 0, 5) for i, shard := range shards { wg.Add(1) go func(i int, shard string) { var err error defer func() { if err != nil { mu.Lock() getErrs = append(getErrs, err.Error()) mu.Unlock() } wg.Done() }() shardInfos[i], err = wr.ts.GetShard(keyspace, shard) if err != nil { return } beforeSchemas[i], err = wr.GetSchema(shardInfos[i].MasterAlias, nil, false) }(i, shard) } wg.Wait() if len(getErrs) > 0 { return nil, fmt.Errorf("Error(s) getting schema: %v", strings.Join(getErrs, ", ")) } // check they all match, or use the force flag relog.Info("Checking starting schemas match on all shards") for i, beforeSchema := range beforeSchemas { if i == 0 { continue } diffs := mysqlctl.DiffSchemaToArray("shard 0", beforeSchemas[0], fmt.Sprintf("shard %v", i), beforeSchema) if len(diffs) > 0 { if force { relog.Warning("Shard %v has inconsistent schema, ignoring: %v", i, strings.Join(diffs, "\n")) } else { return nil, fmt.Errorf("Shard %v has inconsistent schema: %v", i, strings.Join(diffs, "\n")) } } } // preflight on shard 0 master, to get baseline // this assumes shard 0 master doesn't have the schema upgrade applied // if it does, we'll have to fix the slaves and other shards manually. relog.Info("Running Preflight on Shard 0 Master") preflight, err := wr.PreflightSchema(shardInfos[0].MasterAlias, change) if err != nil { return nil, err } // for each shard, apply the change relog.Info("Applying change on all shards") var applyErr error for i, shard := range shards { wg.Add(1) go func(i int, shard string) { defer wg.Done() _, err := wr.lockAndApplySchemaShard(shardInfos[i], preflight, keyspace, shard, shardInfos[i].MasterAlias, change, topo.TabletAlias{}, simple, force) if err != nil { mu.Lock() applyErr = err mu.Unlock() return } }(i, shard) } wg.Wait() if applyErr != nil { return nil, applyErr } return &mysqlctl.SchemaChangeResult{BeforeSchema: preflight.BeforeSchema, AfterSchema: preflight.AfterSchema}, nil }
func (wr *Wrangler) applySchemaShardComplex(statusArray []*TabletStatus, shardInfo *topo.ShardInfo, preflight *mysqlctl.SchemaChangeResult, masterTabletAlias topo.TabletAlias, change string, newParentTabletAlias topo.TabletAlias, force bool) (*mysqlctl.SchemaChangeResult, error) { // apply the schema change to all replica / slave tablets for _, status := range statusArray { // if already applied, we skip this guy diffs := mysqlctl.DiffSchemaToArray("after", preflight.AfterSchema, status.ti.Alias().String(), status.beforeSchema) if len(diffs) == 0 { relog.Info("Tablet %v already has the AfterSchema, skipping", status.ti.Alias()) continue } // make sure the before schema matches diffs = mysqlctl.DiffSchemaToArray("master", preflight.BeforeSchema, status.ti.Alias().String(), status.beforeSchema) if len(diffs) > 0 { if force { relog.Warning("Tablet %v has inconsistent schema, ignoring: %v", status.ti.Alias(), strings.Join(diffs, "\n")) } else { return nil, fmt.Errorf("Tablet %v has inconsistent schema: %v", status.ti.Alias(), strings.Join(diffs, "\n")) } } // take this guy out of the serving graph if necessary ti, err := wr.ts.GetTablet(status.ti.Alias()) if err != nil { return nil, err } typeChangeRequired := ti.Tablet.IsServingType() if typeChangeRequired { // note we want to update the serving graph there err = wr.changeTypeInternal(ti.Alias(), topo.TYPE_SCHEMA_UPGRADE) if err != nil { return nil, err } } // apply the schema change relog.Info("Applying schema change to slave %v in complex mode", status.ti.Alias()) sc := &mysqlctl.SchemaChange{Sql: change, Force: force, AllowReplication: false, BeforeSchema: preflight.BeforeSchema, AfterSchema: preflight.AfterSchema} _, err = wr.ApplySchema(status.ti.Alias(), sc) if err != nil { return nil, err } // put this guy back into the serving graph if typeChangeRequired { err = wr.changeTypeInternal(ti.Alias(), ti.Tablet.Type) if err != nil { return nil, err } } } // if newParentTabletAlias is passed in, use that as the new master if newParentTabletAlias != (topo.TabletAlias{}) { relog.Info("Reparenting with new master set to %v", newParentTabletAlias) tabletMap, err := GetTabletMapForShard(wr.ts, shardInfo.Keyspace(), shardInfo.ShardName()) if err != nil { return nil, err } slaveTabletMap, foundMaster, err := slaveTabletMap(tabletMap) if err != nil { return nil, err } newMasterTablet, err := wr.ts.GetTablet(newParentTabletAlias) if err != nil { return nil, err } err = wr.reparentShardGraceful(slaveTabletMap, foundMaster, newMasterTablet /*leaveMasterReadOnly*/, false) if err != nil { return nil, err } // Here we would apply the schema change to the old // master, but after a reparent it's in Scrap state, // so no need to. When/if reparent leaves the // original master in a different state (like replica // or rdonly), then we should apply the schema there // too. relog.Info("Skipping schema change on old master %v in complex mode, it's been Scrapped", masterTabletAlias) } return &mysqlctl.SchemaChangeResult{BeforeSchema: preflight.BeforeSchema, AfterSchema: preflight.AfterSchema}, nil }