diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php --- a/scripts/sql/manage_storage.php +++ b/scripts/sql/manage_storage.php @@ -31,6 +31,12 @@ 'Do not prompt before performing dangerous operations.'), ), array( + 'name' => 'host', + 'param' => 'hostname', + 'help' => pht( + 'Connect to __host__ instead of the default host.'), + ), + array( 'name' => 'user', 'short' => 'u', 'param' => 'username', @@ -75,10 +81,37 @@ // First, test that the Phabricator configuration is set up correctly. After // we know this works we'll test any administrative credentials specifically. -$ref = PhabricatorDatabaseRef::getMasterDatabaseRef(); -if (!$ref) { - throw new Exception( - pht('No database master is configured.')); +$host = $args->getArg('host'); +if (strlen($host)) { + $ref = null; + + $refs = PhabricatorDatabaseRef::getLiveRefs(); + + // Include the master in case the user is just specifying a redundant + // "--host" flag for no reason and does not actually have a database + // cluster configured. + $refs[] = PhabricatorDatabaseRef::getMasterDatabaseRef(); + + foreach ($refs as $possible_ref) { + if ($possible_ref->getHost() == $host) { + $ref = $possible_ref; + break; + } + } + + if (!$ref) { + throw new PhutilArgumentUsageException( + pht( + 'There is no configured database on host "%s". This command can '. + 'only interact with configured databases.', + $host)); + } +} else { + $ref = PhabricatorDatabaseRef::getMasterDatabaseRef(); + if (!$ref) { + throw new Exception( + pht('No database master is configured.')); + } } $default_user = $ref->getUser(); diff --git a/src/docs/user/cluster/cluster_databases.diviner b/src/docs/user/cluster/cluster_databases.diviner --- a/src/docs/user/cluster/cluster_databases.diviner +++ b/src/docs/user/cluster/cluster_databases.diviner @@ -296,10 +296,15 @@ be slow, so offloading it to a replica can make the performance of the master more consistent. -To dump from a replica, wait for this TODO to be resolved and then do whatever -it says to do: - -TODO: Make `bin/storage dump` replica-aware. See T10758. +To dump from a replica, you can use `bin/storage dump --host ` to +control which host the command connects to. (You may still want to execute +this command //from// that host, to avoid sending the whole dump over the +network). + +With the `--for-replica` flag, the `bin/storage dump` command creates dumps +with `--dump-slave`, which includes a `CHANGE MASTER` statement in the output. +This may be helpful when initially setting up new replicas, as it can make it +easier to change the binlog coordinates to the correct position for the dump. With recent versions of MySQL, it is also possible to configure a //delayed// replica which intentionally lags behind the master (say, by 12 hours). In the diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -7,11 +7,20 @@ $this ->setName('dump') ->setExamples('**dump** [__options__]') - ->setSynopsis(pht('Dump all data in storage to stdout.')); + ->setSynopsis(pht('Dump all data in storage to stdout.')) + ->setArguments( + array( + array( + 'name' => 'for-replica', + 'help' => pht( + 'Add __--dump-slave__ to the __mysqldump__ command, '. + 'generating a CHANGE MASTER statement in the output.'), + ), + )); } public function didExecute(PhutilArgumentParser $args) { - $api = $this->getAPI(); + $api = $this->getAPI(); $patches = $this->getPatches(); $console = PhutilConsole::getConsole(); @@ -33,26 +42,44 @@ list($host, $port) = $this->getBareHostAndPort($api->getHost()); - $flag_password = ''; $password = $api->getPassword(); if ($password) { if (strlen($password->openEnvelope())) { - $flag_password = csprintf('-p%P', $password); + $has_password = true; } } - $flag_port = $port - ? csprintf('--port %d', $port) - : ''; - - return phutil_passthru( - 'mysqldump --hex-blob --single-transaction --default-character-set=utf8 '. - '-u %s %C -h %s %C --databases %Ls', - $api->getUser(), - $flag_password, - $host, - $flag_port, - $databases); + $argv = array(); + $argv[] = '--hex-blob'; + $argv[] = '--single-transaction'; + $argv[] = '--default-character-set=utf8'; + + if ($args->getArg('for-replica')) { + $argv[] = '--dump-slave'; + } + + $argv[] = '-u'; + $argv[] = $api->getUser(); + $argv[] = '-h'; + $argv[] = $host; + + if ($port) { + $argv[] = '--port'; + $argv[] = $port; + } + + $argv[] = '--databases'; + foreach ($databases as $database) { + $argv[] = $database; + } + + if ($has_password) { + $err = phutil_passthru('mysqldump -p%P %Ls', $password, $argv); + } else { + $err = phutil_passthru('mysqldump %Ls', $argv); + } + + return $err; } }