// Go runs the diff. If there is no error, it will drain both sides. // If an error occurs, it will just return it and stop. func (rd *RowSubsetDiffer) Go(log logutil.Logger) (dr DiffReport, err error) { dr.startingTime = time.Now() defer dr.ComputeQPS() var superset []sqltypes.Value var subset []sqltypes.Value advanceSuperset := true advanceSubset := true for { if advanceSuperset { superset, err = rd.superset.Next() if err != nil { return } advanceSuperset = false } if advanceSubset { subset, err = rd.subset.Next() if err != nil { return } advanceSubset = false } dr.processedRows++ if superset == nil { // no more rows from the superset if subset == nil { // no more rows from subset either, we're done return } // drain subset, update count if count, err := rd.subset.Drain(); err != nil { return dr, err } else { dr.extraRowsRight += 1 + count } return } if subset == nil { // no more rows from the subset // we know we have rows from superset, drain if _, err := rd.superset.Drain(); err != nil { return dr, err } return } // we have both superset and subset, compare f := RowsEqual(superset, subset) if f == -1 { // rows are the same, next dr.matchingRows++ advanceSuperset = true advanceSubset = true continue } if f >= rd.pkFieldCount { // rows have the same primary key, only content is different if dr.mismatchedRows < 10 { log.Errorf("Different content %v in same PK: %v != %v", dr.mismatchedRows, superset, subset) } dr.mismatchedRows++ advanceSuperset = true advanceSubset = true continue } // have to find the 'smallest' raw and advance it c, err := CompareRows(rd.superset.Fields(), rd.pkFieldCount, superset, subset) if err != nil { return dr, err } if c < 0 { advanceSuperset = true continue } else if c > 0 { if dr.extraRowsRight < 10 { log.Errorf("Extra row %v on subset: %v", dr.extraRowsRight, subset) } dr.extraRowsRight++ advanceSubset = true continue } // After looking at primary keys more carefully, // they're the same. Logging a regular difference // then, and advancing both. if dr.mismatchedRows < 10 { log.Errorf("Different content %v in same PK: %v != %v", dr.mismatchedRows, superset, subset) } dr.mismatchedRows++ advanceSuperset = true advanceSubset = true } }
// This function runs on the machine acting as the source for the clone. // // Check master/slave status and determine restore needs. // If this instance is a slave, stop replication, otherwise place in read-only mode. // Record replication position. // Shutdown mysql // Check paths for storing data // // Depending on the serverMode flag, we do the following: // serverMode = false: // Compress /vt/vt_[0-9a-f]+/data/vt_.+ // Compute hash (of compressed files, as we serve .gz files here) // Place in /vt/clone_src where they will be served by http server (not rpc) // Restart mysql // serverMode = true: // Make symlinks for /vt/vt_[0-9a-f]+/data/vt_.+ to innodb files // Compute hash (of uncompressed files, as we serve uncompressed files) // Place symlinks in /vt/clone_src where they will be served by http server // Leave mysql stopped, return slaveStartRequired, readOnly func (mysqld *Mysqld) CreateSnapshot(logger logutil.Logger, dbName, sourceAddr string, allowHierarchicalReplication bool, concurrency int, serverMode bool, hookExtraEnv map[string]string) (snapshotManifestUrlPath string, slaveStartRequired, readOnly bool, err error) { if dbName == "" { return "", false, false, errors.New("CreateSnapshot failed: no database name provided") } if err = mysqld.validateCloneSource(serverMode, hookExtraEnv); err != nil { return } // save initial state so we can restore on Start() slaveStartRequired = false sourceIsMaster := false readOnly = true slaveStatus, err := mysqld.SlaveStatus() if err == nil { slaveStartRequired = slaveStatus.SlaveRunning() } else if err == ErrNotSlave { sourceIsMaster = true } else { // If we can't get any data, just fail. return } readOnly, err = mysqld.IsReadOnly() if err != nil { return } // Stop sources of writes so we can get a consistent replication position. // If the source is a slave use the master replication position // unless we are allowing hierarchical replicas. masterAddr := "" var replicationPosition proto.ReplicationPosition if sourceIsMaster { if err = mysqld.SetReadOnly(true); err != nil { return } replicationPosition, err = mysqld.MasterPosition() if err != nil { return } masterAddr = mysqld.IpAddr() } else { if err = mysqld.StopSlave(hookExtraEnv); err != nil { return } var slaveStatus *proto.ReplicationStatus slaveStatus, err = mysqld.SlaveStatus() if err != nil { return } replicationPosition = slaveStatus.Position // We are a slave, check our replication strategy before // choosing the master address. if allowHierarchicalReplication { masterAddr = mysqld.IpAddr() } else { masterAddr, err = mysqld.GetMasterAddr() if err != nil { return } } } if err = mysqld.Shutdown(true, MysqlWaitTime); err != nil { return } var smFile string dataFiles, snapshotErr := mysqld.createSnapshot(logger, concurrency, serverMode) if snapshotErr != nil { logger.Errorf("CreateSnapshot failed: %v", snapshotErr) } else { var sm *SnapshotManifest sm, snapshotErr = newSnapshotManifest(sourceAddr, mysqld.IpAddr(), masterAddr, dbName, dataFiles, replicationPosition, proto.ReplicationPosition{}) if snapshotErr != nil { logger.Errorf("CreateSnapshot failed: %v", snapshotErr) } else { smFile = path.Join(mysqld.SnapshotDir, SnapshotManifestFile) if snapshotErr = writeJson(smFile, sm); snapshotErr != nil { logger.Errorf("CreateSnapshot failed: %v", snapshotErr) } } } // restore our state if required if serverMode && snapshotErr == nil { logger.Infof("server mode snapshot worked, not restarting mysql") } else { if err = mysqld.SnapshotSourceEnd(slaveStartRequired, readOnly, false /*deleteSnapshot*/, hookExtraEnv); err != nil { return } } if snapshotErr != nil { return "", slaveStartRequired, readOnly, snapshotErr } relative, err := filepath.Rel(mysqld.SnapshotDir, smFile) if err != nil { return "", slaveStartRequired, readOnly, nil } return path.Join(SnapshotURLPath, relative), slaveStartRequired, readOnly, nil }