Skip to content

Commit ecc598f

Browse files
author
epriestley
committed
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
1 parent 745429a commit ecc598f

File tree

6 files changed

+123
-82
lines changed

6 files changed

+123
-82
lines changed

scripts/sql/manage_storage.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@
9090
// Include the master in case the user is just specifying a redundant
9191
// "--host" flag for no reason and does not actually have a database
9292
// cluster configured.
93-
$refs[] = PhabricatorDatabaseRef::getMasterDatabaseRef();
93+
foreach (PhabricatorDatabaseRef::getMasterDatabaseRefs() as $master_ref) {
94+
$refs[] = $master_ref;
95+
}
9496

9597
foreach ($refs as $possible_ref) {
9698
if ($possible_ref->getHost() == $host) {

src/applications/config/check/PhabricatorDatabaseSetupCheck.php

Lines changed: 60 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,52 @@ public function getExecutionOrder() {
1212
}
1313

1414
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) {
1748
// If we're implicitly in read-only mode during disaster recovery,
1849
// don't bother with these setup checks.
1950
return;
2051
}
2152

53+
foreach ($masters as $master) {
54+
if ($this->checkMasterDatabase($master)) {
55+
break;
56+
}
57+
}
58+
}
59+
60+
private function checkMasterDatabase(PhabricatorDatabaseRef $master) {
2261
$conn_raw = $master->newManagementConnection();
2362

2463
try {
@@ -34,7 +73,7 @@ protected function executeChecks() {
3473
$issue = PhabricatorSetupIssue::newDatabaseConnectionIssue(
3574
$database_exception);
3675
$this->addIssue($issue);
37-
return;
76+
return true;
3877
}
3978

4079
$engines = queryfx_all($conn_raw, 'SHOW ENGINES');
@@ -54,7 +93,7 @@ protected function executeChecks() {
5493
->setName(pht('MySQL InnoDB Engine Not Available'))
5594
->setMessage($message)
5695
->setIsFatal(true);
57-
return;
96+
return true;
5897
}
5998

6099
$namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
@@ -72,59 +111,29 @@ protected function executeChecks() {
72111
->setMessage($message)
73112
->setIsFatal(true)
74113
->addCommand(hsprintf('<tt>phabricator/ $</tt> ./bin/storage upgrade'));
75-
} else {
76-
$conn_meta = $master->newApplicationConnection(
77-
$namespace.'_meta_data');
78-
79-
$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
80-
$applied = ipull($applied, 'patch', 'patch');
81-
82-
$all = PhabricatorSQLPatchList::buildAllPatches();
83-
$diff = array_diff_key($all, $applied);
84-
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-
}
114+
return true;
96115
}
97116

98-
$host = PhabricatorEnv::getEnvConfig('mysql.host');
99-
$matches = null;
100-
if (preg_match('/^([^:]+):(\d+)$/', $host, $matches)) {
101-
$host = $matches[1];
102-
$port = $matches[2];
117+
$conn_meta = $master->newApplicationConnection(
118+
$namespace.'_meta_data');
103119

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'))
120+
$applied = queryfx_all($conn_meta, 'SELECT patch FROM patch_status');
121+
$applied = ipull($applied, 'patch', 'patch');
122+
123+
$all = PhabricatorSQLPatchList::buildAllPatches();
124+
$diff = array_diff_key($all, $applied);
125+
126+
if ($diff) {
127+
$this->newIssue('storage.patch')
128+
->setName(pht('Upgrade MySQL Schema'))
111129
->setMessage(
112130
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))))
119134
->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;
127137
}
128-
129138
}
130139
}

src/infrastructure/cluster/PhabricatorClusterDatabasesConfigOptionType.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,6 @@ public function validateOption(PhabricatorConfigOption $option, $value) {
8585
$map[$key] = true;
8686
}
8787

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-
}
9688
}
9789

9890
}

src/infrastructure/cluster/PhabricatorDatabaseRef.php

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -446,24 +446,39 @@ public function getHealthRecord() {
446446
return $this->healthRecord;
447447
}
448448

449-
public static function getMasterDatabaseRef() {
449+
public static function getMasterDatabaseRefs() {
450450
$refs = self::getLiveRefs();
451451

452452
if (!$refs) {
453-
return self::getLiveIndividualRef();
453+
return array(self::getLiveIndividualRef());
454454
}
455455

456-
$master = null;
456+
$masters = array();
457457
foreach ($refs as $ref) {
458458
if ($ref->getDisabled()) {
459459
continue;
460460
}
461461
if ($ref->getIsMaster()) {
462-
return $ref;
462+
$masters[] = $ref;
463463
}
464464
}
465465

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);
467482
}
468483

469484
public static function newIndividualRef() {
@@ -480,29 +495,39 @@ public static function newIndividualRef() {
480495
->setIsMaster(true);
481496
}
482497

483-
public static function getReplicaDatabaseRef() {
498+
public static function getReplicaDatabaseRefs() {
484499
$refs = self::getLiveRefs();
485500

486501
if (!$refs) {
487-
return null;
502+
return array();
488503
}
489504

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();
495506
foreach ($refs as $ref) {
496507
if ($ref->getDisabled()) {
497508
continue;
498509
}
499510
if ($ref->getIsMaster()) {
500511
continue;
501512
}
502-
return $ref;
513+
514+
$replicas[] = $ref;
503515
}
504516

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);
506531
}
507532

508533
private function newConnection(array $options) {

src/infrastructure/env/PhabricatorEnv.php

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,24 @@ private static function buildConfigurationSourceStack($config_optional) {
223223
$stack->pushSource($site_source);
224224
}
225225

226-
$master = PhabricatorDatabaseRef::getMasterDatabaseRef();
227-
if (!$master) {
226+
$masters = PhabricatorDatabaseRef::getMasterDatabaseRefs();
227+
if (!$masters) {
228228
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+
}
233244
}
234245
}
235246

src/infrastructure/storage/lisk/PhabricatorLiskDAO.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ private function newBasicConnection($database, $mode, $namespace) {
114114
}
115115

116116
private function newClusterConnection($database, $mode) {
117-
$master = PhabricatorDatabaseRef::getMasterDatabaseRef();
117+
$master = PhabricatorDatabaseRef::getMasterDatabaseRefForDatabase(
118+
$database);
118119

119120
if ($master && !$master->isSevered()) {
120121
$connection = $master->newApplicationConnection($database);
@@ -130,7 +131,8 @@ private function newClusterConnection($database, $mode) {
130131
}
131132
}
132133

133-
$replica = PhabricatorDatabaseRef::getReplicaDatabaseRef();
134+
$replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForDatabase(
135+
$database);
134136
if ($replica) {
135137
$connection = $replica->newApplicationConnection($database);
136138
$connection->setReadOnly(true);

0 commit comments

Comments
 (0)