@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Support multiple database masters and convert easy callers

Summary:
Ref T11044. This moves toward partitioned application databases:

- You can define multiple masters.
- Convert all the easily-convertible code to become multi-master aware.

This doesn't convert most of `bin/storage` or "Config > Database (Stuff)" yet, as both are quite involved. They still work for now, but only operate on the first master instead of all masters.

Test Plan: Configured multiple masters, browsed around, ran `bin/storage` commands, ran `bin/storage --host ...`.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11044

Differential Revision: https://secure.phabricator.com/D16115

+122 -81
+3 -1
scripts/sql/manage_storage.php
··· 90 90 // Include the master in case the user is just specifying a redundant 91 91 // "--host" flag for no reason and does not actually have a database 92 92 // cluster configured. 93 - $refs[] = PhabricatorDatabaseRef::getMasterDatabaseRef(); 93 + foreach (PhabricatorDatabaseRef::getMasterDatabaseRefs() as $master_ref) { 94 + $refs[] = $master_ref; 95 + } 94 96 95 97 foreach ($refs as $possible_ref) { 96 98 if ($possible_ref->getHost() == $host) {
+59 -50
src/applications/config/check/PhabricatorDatabaseSetupCheck.php
··· 12 12 } 13 13 14 14 protected function executeChecks() { 15 - $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); 16 - if (!$master) { 15 + $host = PhabricatorEnv::getEnvConfig('mysql.host'); 16 + $matches = null; 17 + if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) { 18 + $host = $matches[1]; 19 + $port = $matches[2]; 20 + 21 + $this->newIssue('storage.mysql.hostport') 22 + ->setName(pht('Deprecated mysql.host Format')) 23 + ->setSummary( 24 + pht( 25 + 'Move port information from `%s` to `%s` in your config.', 26 + 'mysql.host', 27 + 'mysql.port')) 28 + ->setMessage( 29 + pht( 30 + 'Your `%s` configuration contains a port number, but this usage '. 31 + 'is deprecated. Instead, put the port number in `%s`.', 32 + 'mysql.host', 33 + 'mysql.port')) 34 + ->addPhabricatorConfig('mysql.host') 35 + ->addPhabricatorConfig('mysql.port') 36 + ->addCommand( 37 + hsprintf( 38 + '<tt>phabricator/ $</tt> ./bin/config set mysql.host %s', 39 + $host)) 40 + ->addCommand( 41 + hsprintf( 42 + '<tt>phabricator/ $</tt> ./bin/config set mysql.port %s', 43 + $port)); 44 + } 45 + 46 + $masters = PhabricatorDatabaseRef::getMasterDatabaseRefs(); 47 + if (!$masters) { 17 48 // If we're implicitly in read-only mode during disaster recovery, 18 49 // don't bother with these setup checks. 19 50 return; 20 51 } 21 52 53 + foreach ($masters as $master) { 54 + if ($this->checkMasterDatabase($master)) { 55 + break; 56 + } 57 + } 58 + } 59 + 60 + private function checkMasterDatabase(PhabricatorDatabaseRef $master) { 22 61 $conn_raw = $master->newManagementConnection(); 23 62 24 63 try { ··· 34 73 $issue = PhabricatorSetupIssue::newDatabaseConnectionIssue( 35 74 $database_exception); 36 75 $this->addIssue($issue); 37 - return; 76 + return true; 38 77 } 39 78 40 79 $engines = queryfx_all($conn_raw, 'SHOW ENGINES'); ··· 54 93 ->setName(pht('MySQL InnoDB Engine Not Available')) 55 94 ->setMessage($message) 56 95 ->setIsFatal(true); 57 - return; 96 + return true; 58 97 } 59 98 60 99 $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace'); ··· 72 111 ->setMessage($message) 73 112 ->setIsFatal(true) 74 113 ->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); 75 - } else { 76 - $conn_meta = $master->newApplicationConnection( 77 - $namespace.'_meta_data'); 114 + return true; 115 + } 78 116 79 - $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); 80 - $applied = ipull($applied, 'patch', 'patch'); 117 + $conn_meta = $master->newApplicationConnection( 118 + $namespace.'_meta_data'); 81 119 82 - $all = PhabricatorSQLPatchList::buildAllPatches(); 83 - $diff = array_diff_key($all, $applied); 120 + $applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status'); 121 + $applied = ipull($applied, 'patch', 'patch'); 84 122 85 - if ($diff) { 86 - $this->newIssue('storage.patch') 87 - ->setName(pht('Upgrade MySQL Schema')) 88 - ->setMessage( 89 - pht( 90 - "Run the storage upgrade script to upgrade Phabricator's ". 91 - "database schema. Missing patches:<br />%s<br />", 92 - phutil_implode_html(phutil_tag('br'), array_keys($diff)))) 93 - ->addCommand( 94 - hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); 95 - } 96 - } 123 + $all = PhabricatorSQLPatchList::buildAllPatches(); 124 + $diff = array_diff_key($all, $applied); 97 125 98 - $host = PhabricatorEnv::getEnvConfig('mysql.host'); 99 - $matches = null; 100 - if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) { 101 - $host = $matches[1]; 102 - $port = $matches[2]; 103 - 104 - $this->newIssue('storage.mysql.hostport') 105 - ->setName(pht('Deprecated mysql.host Format')) 106 - ->setSummary( 107 - pht( 108 - 'Move port information from `%s` to `%s` in your config.', 109 - 'mysql.host', 110 - 'mysql.port')) 126 + if ($diff) { 127 + $this->newIssue('storage.patch') 128 + ->setName(pht('Upgrade MySQL Schema')) 111 129 ->setMessage( 112 130 pht( 113 - 'Your `%s` configuration contains a port number, but this usage '. 114 - 'is deprecated. Instead, put the port number in `%s`.', 115 - 'mysql.host', 116 - 'mysql.port')) 117 - ->addPhabricatorConfig('mysql.host') 118 - ->addPhabricatorConfig('mysql.port') 131 + "Run the storage upgrade script to upgrade Phabricator's ". 132 + "database schema. Missing patches:<br />%s<br />", 133 + phutil_implode_html(phutil_tag('br'), array_keys($diff)))) 119 134 ->addCommand( 120 - hsprintf( 121 - '<tt>phabricator/ $</tt> ./bin/config set mysql.host %s', 122 - $host)) 123 - ->addCommand( 124 - hsprintf( 125 - '<tt>phabricator/ $</tt> ./bin/config set mysql.port %s', 126 - $port)); 135 + hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade')); 136 + return true; 127 137 } 128 - 129 138 } 130 139 }
-8
src/infrastructure/cluster/PhabricatorClusterDatabasesConfigOptionType.php
··· 85 85 $map[$key] = true; 86 86 } 87 87 88 - if (count($masters) > 1) { 89 - throw new Exception( 90 - pht( 91 - 'Database cluster configuration is invalid: it describes multiple '. 92 - 'masters. No more than one host may be a master. Hosts currently '. 93 - 'configured as masters: %s.', 94 - implode(', ', $masters))); 95 - } 96 88 } 97 89 98 90 }
+39 -14
src/infrastructure/cluster/PhabricatorDatabaseRef.php
··· 446 446 return $this->healthRecord; 447 447 } 448 448 449 - public static function getMasterDatabaseRef() { 449 + public static function getMasterDatabaseRefs() { 450 450 $refs = self::getLiveRefs(); 451 451 452 452 if (!$refs) { 453 - return self::getLiveIndividualRef(); 453 + return array(self::getLiveIndividualRef()); 454 454 } 455 455 456 - $master = null; 456 + $masters = array(); 457 457 foreach ($refs as $ref) { 458 458 if ($ref->getDisabled()) { 459 459 continue; 460 460 } 461 461 if ($ref->getIsMaster()) { 462 - return $ref; 462 + $masters[] = $ref; 463 463 } 464 464 } 465 465 466 - return null; 466 + return $masters; 467 + } 468 + 469 + public static function getMasterDatabaseRef() { 470 + // TODO: Remove this method; it no longer makes sense with application 471 + // partitioning. 472 + 473 + return head(self::getMasterDatabaseRefs()); 474 + } 475 + 476 + public static function getMasterDatabaseRefForDatabase($database) { 477 + $masters = self::getMasterDatabaseRefs(); 478 + 479 + // TODO: Actually implement this. 480 + 481 + return head($masters); 467 482 } 468 483 469 484 public static function newIndividualRef() { ··· 480 495 ->setIsMaster(true); 481 496 } 482 497 483 - public static function getReplicaDatabaseRef() { 498 + public static function getReplicaDatabaseRefs() { 484 499 $refs = self::getLiveRefs(); 485 500 486 501 if (!$refs) { 487 - return null; 502 + return array(); 488 503 } 489 504 490 - // TODO: We may have multiple replicas to choose from, and could make 491 - // more of an effort to pick the "best" one here instead of always 492 - // picking the first one. Once we've picked one, we should try to use 493 - // the same replica for the rest of the request, though. 494 - 505 + $replicas = array(); 495 506 foreach ($refs as $ref) { 496 507 if ($ref->getDisabled()) { 497 508 continue; ··· 499 510 if ($ref->getIsMaster()) { 500 511 continue; 501 512 } 502 - return $ref; 513 + 514 + $replicas[] = $ref; 503 515 } 504 516 505 - return null; 517 + return $replicas; 518 + } 519 + 520 + public static function getReplicaDatabaseRefForDatabase($database) { 521 + $replicas = self::getReplicaDatabaseRefs(); 522 + 523 + // TODO: Actually implement this. 524 + 525 + // TODO: We may have multiple replicas to choose from, and could make 526 + // more of an effort to pick the "best" one here instead of always 527 + // picking the first one. Once we've picked one, we should try to use 528 + // the same replica for the rest of the request, though. 529 + 530 + return head($replicas); 506 531 } 507 532 508 533 private function newConnection(array $options) {
+17 -6
src/infrastructure/env/PhabricatorEnv.php
··· 223 223 $stack->pushSource($site_source); 224 224 } 225 225 226 - $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); 227 - if (!$master) { 226 + $masters = PhabricatorDatabaseRef::getMasterDatabaseRefs(); 227 + if (!$masters) { 228 228 self::setReadOnly(true, self::READONLY_MASTERLESS); 229 - } else if ($master->isSevered()) { 230 - $master->checkHealth(); 231 - if ($master->isSevered()) { 232 - self::setReadOnly(true, self::READONLY_SEVERED); 229 + } else { 230 + // If any master is severed, we drop to readonly mode. In theory we 231 + // could try to continue if we're only missing some applications, but 232 + // this is very complex and we're unlikely to get it right. 233 + 234 + foreach ($masters as $master) { 235 + // Give severed masters one last chance to get healthy. 236 + if ($master->isSevered()) { 237 + $master->checkHealth(); 238 + } 239 + 240 + if ($master->isSevered()) { 241 + self::setReadOnly(true, self::READONLY_SEVERED); 242 + break; 243 + } 233 244 } 234 245 } 235 246
+4 -2
src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
··· 114 114 } 115 115 116 116 private function newClusterConnection($database, $mode) { 117 - $master = PhabricatorDatabaseRef::getMasterDatabaseRef(); 117 + $master = PhabricatorDatabaseRef::getMasterDatabaseRefForDatabase( 118 + $database); 118 119 119 120 if ($master && !$master->isSevered()) { 120 121 $connection = $master->newApplicationConnection($database); ··· 130 131 } 131 132 } 132 133 133 - $replica = PhabricatorDatabaseRef::getReplicaDatabaseRef(); 134 + $replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForDatabase( 135 + $database); 134 136 if ($replica) { 135 137 $connection = $replica->newApplicationConnection($database); 136 138 $connection->setReadOnly(true);