@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 application partitioning across multiple masters

Summary:
Ref T11044. I'm going to hold this until after the release cut, but I think it's good to go.

This allows installs to configure multiple masters in `cluster.databases` and partition applications across them (for example, put Maniphest on a dedicated database).

When we make a Maniphest connection we go look up which master we should be hitting first, then connect to it.

This has at least approximately been planned for many years, so the actual change is largely just making sure that your config makes sense.

Test Plan:
- Configured `db001.epriestley.com` and `db002.epriestley.com` as master/master.
- Partitioned applications between them.
- Interacted with various applications, saw writes go to the correct host.
- Viewed "Database Servers" and saw partitioning information.
- Ran schema upgrades.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T11044

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

+571 -44
+2
src/__phutil_library_map__.php
··· 2465 2465 'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php', 2466 2466 'PhabricatorDatabaseHealthRecord' => 'infrastructure/cluster/PhabricatorDatabaseHealthRecord.php', 2467 2467 'PhabricatorDatabaseRef' => 'infrastructure/cluster/PhabricatorDatabaseRef.php', 2468 + 'PhabricatorDatabaseRefParser' => 'infrastructure/cluster/PhabricatorDatabaseRefParser.php', 2468 2469 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', 2469 2470 'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php', 2470 2471 'PhabricatorDatasourceEditType' => 'applications/transactions/edittype/PhabricatorDatasourceEditType.php', ··· 7399 7400 'PhabricatorDataNotAttachedException' => 'Exception', 7400 7401 'PhabricatorDatabaseHealthRecord' => 'Phobject', 7401 7402 'PhabricatorDatabaseRef' => 'Phobject', 7403 + 'PhabricatorDatabaseRefParser' => 'Phobject', 7402 7404 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', 7403 7405 'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField', 7404 7406 'PhabricatorDatasourceEditType' => 'PhabricatorPHIDListEditType',
+31
src/applications/config/controller/PhabricatorConfigClusterDatabasesController.php
··· 169 169 170 170 $messages = phutil_implode_html(phutil_tag('br'), $messages); 171 171 172 + $partition = null; 173 + if ($database->getIsMaster()) { 174 + if ($database->getIsDefaultPartition()) { 175 + $partition = id(new PHUIIconView()) 176 + ->setIcon('fa-circle sky') 177 + ->addSigil('has-tooltip') 178 + ->setMetadata( 179 + array( 180 + 'tip' => pht('Default Partition'), 181 + )); 182 + } else { 183 + $map = $database->getApplicationMap(); 184 + if ($map) { 185 + $list = implode(', ', $map); 186 + } else { 187 + $list = pht('Empty'); 188 + } 189 + 190 + $partition = id(new PHUIIconView()) 191 + ->setIcon('fa-adjust sky') 192 + ->addSigil('has-tooltip') 193 + ->setMetadata( 194 + array( 195 + 'tip' => pht('Partition: %s', $list), 196 + )); 197 + } 198 + } 199 + 172 200 $rows[] = array( 173 201 $role_icon, 202 + $partition, 174 203 $database->getHost(), 175 204 $database->getPort(), 176 205 $database->getUser(), ··· 188 217 ->setHeaders( 189 218 array( 190 219 null, 220 + null, 191 221 pht('Host'), 192 222 pht('Port'), 193 223 pht('User'), ··· 198 228 )) 199 229 ->setColumnClasses( 200 230 array( 231 + null, 201 232 null, 202 233 null, 203 234 null,
+6
src/docs/user/cluster/cluster_databases.diviner
··· 29 29 There are no current plans to support multi-master mode or autonomous failover, 30 30 although this may change in the future. 31 31 32 + Phabricator applications //can// be partitioned across multiple database 33 + masters. This does not provide redundancy and generally does not increase 34 + resiliance or resistance to data loss, but can help you scale and operate 35 + Phabricator. For details, see 36 + @{article:Cluster: Partitioning and Advanced Configuration}. 37 + 32 38 33 39 Setting up MySQL Replication 34 40 ============================
+187
src/docs/user/cluster/cluster_partitioning.diviner
··· 1 + @title Cluster: Partitioning and Advanced Configuration 2 + @group cluster 3 + 4 + Guide to partitioning Phabricator applications across multiple database hosts. 5 + 6 + Overview 7 + ======== 8 + 9 + WARNING: Partitioning is a prototype. 10 + 11 + You can partition Phabricator's applications across multiple databases. For 12 + example, you can move an application like Files or Maniphest to a dedicated 13 + database host. 14 + 15 + The advantages of doing this are: 16 + 17 + - moving heavily used applications to dedicated hardware can help you 18 + scale; and 19 + - you can match application workloads to hardware or configuration to make 20 + operating the cluster easier. 21 + 22 + This configuration is complex, and very few installs need to pursue it. 23 + Phabricator will normally run comfortably with a single database master even 24 + for large organizations. 25 + 26 + Partitioning generally does not do much to increase resiliance or make it 27 + easier to recover from disasters, and is primarily a mechanism for scaling. 28 + 29 + If you are considering partitioning, you likely want to configure replication 30 + with a single master first. Even if you choose not to deploy replication, you 31 + should review and understand how replication works before you partition. For 32 + details, see @{Cluster:Databases}. 33 + 34 + 35 + What Partitioning Does 36 + ====================== 37 + 38 + When you partition Phabricator, you move all of the data for one or more 39 + applications (like Maniphest) to a new master database host. This is possible 40 + because Phabricator stores data for each application in its own logical 41 + database (like `phabricator_maniphest`) and performs no joins between databases. 42 + 43 + If you're running into scale limits on a single master database, you can move 44 + one or more of your most commonly-used applications to a second database host 45 + and continue adding users. You can keep partitioning applications until all 46 + heavily used applications have dedicated database servers. 47 + 48 + Alternatively or additionally, you can partition applications to make operating 49 + the cluster easier. Some applications have unusual workloads or requirements, 50 + and moving them to separate hosts may make things easier to deal with overall. 51 + 52 + For example: if Files accounts for most of the data on your install, you might 53 + move it to a different host to make backing up everything else easier. 54 + 55 + 56 + Configuration Overview 57 + ====================== 58 + 59 + To configure partitioning, you will add multiple entries to `cluster.databases` 60 + with the `master` role. Each `master` should specify a new `partition` key, 61 + which contains a list of application databases it should host. 62 + 63 + One master may be specified as the `default` partition. Applications not 64 + explicitly configured to be assigned elsewhere will be assigned here. 65 + 66 + When you define multiple `master` databases, you must also specify which master 67 + each `replica` database follows. Here's a simple example config: 68 + 69 + ```lang=json 70 + ... 71 + "cluster.databases": [ 72 + { 73 + "host": "db001.corporation.com", 74 + "role": "master", 75 + "user": "phabricator", 76 + "pass": "hunter2!trustno1", 77 + "port": 3306, 78 + "partition": [ 79 + "default" 80 + ] 81 + }, 82 + { 83 + "host": "db002.corporation.com", 84 + "role": "replica", 85 + "user": "phabricator", 86 + "pass": "hunter2!trustno1", 87 + "port": 3306, 88 + "master": "db001.corporation.com:3306" 89 + }, 90 + { 91 + "host": "db003.corporation.com", 92 + "role": "master", 93 + "user": "phabricator", 94 + "pass": "hunter2!trustno1", 95 + "port": 3306, 96 + "partition": [ 97 + "file", 98 + "passphrase", 99 + "slowvote" 100 + ] 101 + }, 102 + { 103 + "host": "db004.corporation.com", 104 + "role": "replica", 105 + "user": "phabricator", 106 + "pass": "hunter2!trustno1", 107 + "port": 3306, 108 + "master": "db003.corporation.com:3306" 109 + } 110 + ], 111 + ... 112 + ``` 113 + 114 + In this configuration, `db001` is a master and `db002` replicates it. 115 + `db003` is a second master, replicated by `db004`. 116 + 117 + Applications have been partitioned like this: 118 + 119 + - `db003`/`db004`: Files, Passphrase, Slowvote 120 + - `db001`/`db002`: Default (all other applications) 121 + 122 + Not all of the database partition names are the same as the application 123 + names. You can get a list of databases with `bin/storage databases` to identify 124 + the correct database names. 125 + 126 + 127 + Launching a new Partition 128 + ========================= 129 + 130 + To add a new partition, follow these steps: 131 + 132 + - Set up the new database host or hosts. 133 + - Add the new database to `cluster.database`, but keep its "partition" 134 + configuration empty (just an empty list). If this is the first time you 135 + are partitioning, you will need to configure your existing master as the 136 + new "default". This will let Phabricator interact with it, but won't send 137 + any traffic to it yet. 138 + - Run `bin/storage upgrade` to initialize the schemata on the new hosts. 139 + - Stop writes to the applications you want to move by putting Phabricator 140 + in read-only mode, or shutting down the webserver and daemons, or telling 141 + everyone not to touch anything. 142 + - Dump the data from the application databases on the old master. 143 + - Load the data into the application databases on the new master. 144 + - Reconfigure the "partition" setup so that Phabricator knows the databases 145 + have moved. 146 + - While still in read-only mode, check that all the data appears to be 147 + intact. 148 + - Resume writes. 149 + 150 + You can do this with a small, rarely-used application first (on most installs, 151 + Slowvote might be a good candidate) if you want to run through the process 152 + end-to-end before performing a larger, higher-stakes migration. 153 + 154 + 155 + How Partitioning Works 156 + ====================== 157 + 158 + If you have multiple masters, Phabricator keeps the entire set of schemata up 159 + to date on all of them. When you run `bin/storage upgrade` or other storage 160 + management commands, they generally affect all masters (if they do not, they 161 + will prompt you to be more specific). 162 + 163 + When the application goes to read or write normal data (for example, to query a 164 + list of tasks) it only connects to the master which the application it is 165 + acting on behalf of is assigned to. 166 + 167 + In most cases, a masters will not have any data in most the databases which are 168 + not assigned to it. If they do (for example, because they previously hosted the 169 + application) the data is ignored. This approach (of maintaining all schemata on 170 + all hosts) makes it easier to move data and to quickly revert changes if a 171 + configuration mistake occurs. 172 + 173 + There are some exceptions to this rule. For example, all masters keep track 174 + of which patches have been applied to that particular master so that 175 + `bin/storage upgrade` can upgrade hosts correctly. 176 + 177 + Phabricator does not perform joins across logical databases, so there are no 178 + meaningful differences in runtime behavior if two applications are on the same 179 + physical host or different physical hosts. 180 + 181 + 182 + Next Steps 183 + ========== 184 + 185 + Continue by: 186 + 187 + - returning to @{article:Clustering Introduction}.
+2
src/infrastructure/cluster/PhabricatorClusterDatabasesConfigOptionType.php
··· 35 35 'user' => 'optional string', 36 36 'pass' => 'optional string', 37 37 'disabled' => 'optional bool', 38 + 'master' => 'optional string', 39 + 'partition' => 'optional list<string>', 38 40 )); 39 41 } catch (Exception $ex) { 40 42 throw new Exception(
+118 -38
src/infrastructure/cluster/PhabricatorDatabaseRef.php
··· 36 36 private $healthRecord; 37 37 private $didFailToConnect; 38 38 39 + private $isDefaultPartition; 40 + private $applicationMap = array(); 41 + private $masterRef; 42 + private $replicaRefs = array(); 43 + 39 44 public function setHost($host) { 40 45 $this->host = $host; 41 46 return $this; ··· 157 162 return $this->isIndividual; 158 163 } 159 164 165 + public function setIsDefaultPartition($is_default_partition) { 166 + $this->isDefaultPartition = $is_default_partition; 167 + return $this; 168 + } 169 + 170 + public function getIsDefaultPartition() { 171 + return $this->isDefaultPartition; 172 + } 173 + 174 + public function setApplicationMap(array $application_map) { 175 + $this->applicationMap = $application_map; 176 + return $this; 177 + } 178 + 179 + public function getApplicationMap() { 180 + return $this->applicationMap; 181 + } 182 + 183 + public function setMasterRef(PhabricatorDatabaseRef $master_ref) { 184 + $this->masterRef = $master_ref; 185 + return $this; 186 + } 187 + 188 + public function getMasterRef() { 189 + return $this->masterRef; 190 + } 191 + 192 + public function addReplicaRef(PhabricatorDatabaseRef $replica_ref) { 193 + $this->replicaRefs[] = $replica_ref; 194 + return $this; 195 + } 196 + 197 + public function getReplicaRefs() { 198 + return $this->replicaRefs; 199 + } 200 + 201 + 160 202 public function getRefKey() { 161 203 $host = $this->getHost(); 162 204 ··· 248 290 } 249 291 250 292 public static function newRefs() { 251 - $refs = array(); 252 - 253 293 $default_port = PhabricatorEnv::getEnvConfig('mysql.port'); 254 294 $default_port = nonempty($default_port, 3306); 255 295 ··· 259 299 $default_pass = new PhutilOpaqueEnvelope($default_pass); 260 300 261 301 $config = PhabricatorEnv::getEnvConfig('cluster.databases'); 262 - foreach ($config as $server) { 263 - $host = $server['host']; 264 - $port = idx($server, 'port', $default_port); 265 - $user = idx($server, 'user', $default_user); 266 - $disabled = idx($server, 'disabled', false); 267 302 268 - $pass = idx($server, 'pass'); 269 - if ($pass) { 270 - $pass = new PhutilOpaqueEnvelope($pass); 271 - } else { 272 - $pass = clone $default_pass; 273 - } 274 - 275 - $role = $server['role']; 276 - 277 - $ref = id(new self()) 278 - ->setHost($host) 279 - ->setPort($port) 280 - ->setUser($user) 281 - ->setPass($pass) 282 - ->setDisabled($disabled) 283 - ->setIsMaster(($role == 'master')); 284 - 285 - $refs[] = $ref; 286 - } 287 - 288 - return $refs; 303 + return id(new PhabricatorDatabaseRefParser()) 304 + ->setDefaultPort($default_port) 305 + ->setDefaultUser($default_user) 306 + ->setDefaultPass($default_pass) 307 + ->newRefs($config); 289 308 } 290 309 291 310 public static function queryAll() { ··· 471 490 return $refs; 472 491 } 473 492 474 - public static function getMasterDatabaseRefs() { 493 + public static function getAllMasterDatabaseRefs() { 475 494 $refs = self::getClusterRefs(); 476 495 477 496 if (!$refs) { ··· 491 510 return $masters; 492 511 } 493 512 494 - public static function getMasterDatabaseRefForDatabase($database) { 513 + public static function getMasterDatabaseRefs() { 514 + $refs = self::getAllMasterDatabaseRefs(); 515 + return self::getEnabledRefs($refs); 516 + } 517 + 518 + public function isApplicationHost($database) { 519 + return isset($this->applicationMap[$database]); 520 + } 521 + 522 + public static function getMasterDatabaseRefForApplication($application) { 495 523 $masters = self::getMasterDatabaseRefs(); 496 524 497 - // TODO: Actually implement this. 525 + $application_master = null; 526 + $default_master = null; 527 + foreach ($masters as $master) { 528 + if ($master->isApplicationHost($application)) { 529 + $application_master = $master; 530 + break; 531 + } 532 + if ($master->getIsDefaultPartition()) { 533 + $default_master = $master; 534 + } 535 + } 536 + 537 + if ($application_master) { 538 + $masters = array($application_master); 539 + } else if ($default_master) { 540 + $masters = array($default_master); 541 + } else { 542 + $masters = array(); 543 + } 544 + 545 + $masters = self::getEnabledRefs($masters); 546 + $master = head($masters); 498 547 499 - return head($masters); 548 + return $master; 500 549 } 501 550 502 551 public static function newIndividualRef() { ··· 513 562 ->setIsMaster(true); 514 563 } 515 564 516 - public static function getReplicaDatabaseRefs() { 565 + public static function getAllReplicaDatabaseRefs() { 517 566 $refs = self::getClusterRefs(); 518 567 519 568 if (!$refs) { ··· 522 571 523 572 $replicas = array(); 524 573 foreach ($refs as $ref) { 525 - if ($ref->getDisabled()) { 526 - continue; 527 - } 528 574 if ($ref->getIsMaster()) { 529 575 continue; 530 576 } ··· 535 581 return $replicas; 536 582 } 537 583 538 - public static function getReplicaDatabaseRefForDatabase($database) { 584 + public static function getReplicaDatabaseRefs() { 585 + $refs = self::getAllReplicaDatabaseRefs(); 586 + return self::getEnabledRefs($refs); 587 + } 588 + 589 + private static function getEnabledRefs(array $refs) { 590 + foreach ($refs as $key => $ref) { 591 + if ($ref->getDisabled()) { 592 + unset($refs[$key]); 593 + } 594 + } 595 + return $refs; 596 + } 597 + 598 + public static function getReplicaDatabaseRefForApplication($application) { 539 599 $replicas = self::getReplicaDatabaseRefs(); 540 600 541 - // TODO: Actually implement this. 601 + $application_replicas = array(); 602 + $default_replicas = array(); 603 + foreach ($replicas as $replica) { 604 + $master = $replica->getMaster(); 605 + 606 + if ($master->isApplicationHost($application)) { 607 + $application_replicas[] = $replica; 608 + } 609 + 610 + if ($master->getIsDefaultPartition()) { 611 + $default_replicas[] = $replica; 612 + } 613 + } 614 + 615 + if ($application_replicas) { 616 + $replicas = $application_replicas; 617 + } else { 618 + $replicas = $default_replicas; 619 + } 620 + 621 + $replicas = self::getEnabledRefs($replicas); 542 622 543 623 // TODO: We may have multiple replicas to choose from, and could make 544 624 // more of an effort to pick the "best" one here instead of always
+216
src/infrastructure/cluster/PhabricatorDatabaseRefParser.php
··· 1 + <?php 2 + 3 + final class PhabricatorDatabaseRefParser 4 + extends Phobject { 5 + 6 + private $defaultPort = 3306; 7 + private $defaultUser; 8 + private $defaultPass; 9 + 10 + public function setDefaultPort($default_port) { 11 + $this->defaultPort = $default_port; 12 + return $this; 13 + } 14 + 15 + public function getDefaultPort() { 16 + return $this->defaultPort; 17 + } 18 + 19 + public function setDefaultUser($default_user) { 20 + $this->defaultUser = $default_user; 21 + return $this; 22 + } 23 + 24 + public function getDefaultUser() { 25 + return $this->defaultUser; 26 + } 27 + 28 + public function setDefaultPass($default_pass) { 29 + $this->defaultPass = $default_pass; 30 + return $this; 31 + } 32 + 33 + public function getDefaultPass() { 34 + return $this->defaultPass; 35 + } 36 + 37 + public function newRefs(array $config) { 38 + $default_port = $this->getDefaultPort(); 39 + $default_user = $this->getDefaultUser(); 40 + $default_pass = $this->getDefaultPass(); 41 + 42 + $refs = array(); 43 + 44 + $master_count = 0; 45 + foreach ($config as $key => $server) { 46 + $host = $server['host']; 47 + $port = idx($server, 'port', $default_port); 48 + $user = idx($server, 'user', $default_user); 49 + $disabled = idx($server, 'disabled', false); 50 + 51 + $pass = idx($server, 'pass'); 52 + if ($pass) { 53 + $pass = new PhutilOpaqueEnvelope($pass); 54 + } else { 55 + $pass = clone $default_pass; 56 + } 57 + 58 + $role = $server['role']; 59 + $is_master = ($role == 'master'); 60 + 61 + $ref = id(new PhabricatorDatabaseRef()) 62 + ->setHost($host) 63 + ->setPort($port) 64 + ->setUser($user) 65 + ->setPass($pass) 66 + ->setDisabled($disabled) 67 + ->setIsMaster($is_master); 68 + 69 + if ($is_master) { 70 + $master_count++; 71 + } 72 + 73 + $refs[$key] = $ref; 74 + } 75 + 76 + $is_partitioned = ($master_count > 1); 77 + if ($is_partitioned) { 78 + $default_ref = null; 79 + $partition_map = array(); 80 + foreach ($refs as $key => $ref) { 81 + if (!$ref->getIsMaster()) { 82 + continue; 83 + } 84 + 85 + $server = $config[$key]; 86 + $partition = idx($server, 'partition'); 87 + if (!is_array($partition)) { 88 + throw new Exception( 89 + pht( 90 + 'Phabricator is configured with multiple master databases, '. 91 + 'but master "%s" is missing a "partition" configuration key to '. 92 + 'define application partitioning.', 93 + $ref->getRefKey())); 94 + } 95 + 96 + $application_map = array(); 97 + foreach ($partition as $application) { 98 + if ($application === 'default') { 99 + if ($default_ref) { 100 + throw new Exception( 101 + pht( 102 + 'Multiple masters (databases "%s" and "%s") specify that '. 103 + 'they are the "default" partition. Only one master may be '. 104 + 'the default.', 105 + $ref->getRefKey(), 106 + $default_ref->getRefKey())); 107 + } else { 108 + $default_ref = $ref; 109 + $ref->setIsDefaultPartition(true); 110 + } 111 + } else if (isset($partition_map[$application])) { 112 + throw new Exception( 113 + pht( 114 + 'Multiple masters (databases "%s" and "%s") specify that '. 115 + 'they are the partition for application "%s". Each '. 116 + 'application may be allocated to only one partition.', 117 + $partition_map[$application]->getRefKey(), 118 + $ref->getRefKey(), 119 + $application)); 120 + } else { 121 + // TODO: We should check that the application is valid, to 122 + // prevent typos in application names. However, we do not 123 + // currently have an efficient way to enumerate all of the valid 124 + // application database names. 125 + 126 + $partition_map[$application] = $ref; 127 + $application_map[$application] = $application; 128 + } 129 + } 130 + 131 + $ref->setApplicationMap($application_map); 132 + } 133 + } else { 134 + // If we only have one master, make it the default. 135 + foreach ($refs as $ref) { 136 + if ($ref->getIsMaster()) { 137 + $ref->setIsDefaultPartition(true); 138 + } 139 + } 140 + } 141 + 142 + $ref_map = array(); 143 + $master_keys = array(); 144 + foreach ($refs as $ref) { 145 + $ref_key = $ref->getRefKey(); 146 + if (isset($ref_map[$ref_key])) { 147 + throw new Exception( 148 + pht( 149 + 'Multiple configured databases have the same internal '. 150 + 'key, "%s". You may have listed a database multiple times.', 151 + $ref_key)); 152 + } else { 153 + $ref_map[$ref_key] = $ref; 154 + if ($ref->getIsMaster()) { 155 + $master_keys[] = $ref_key; 156 + } 157 + } 158 + } 159 + 160 + foreach ($refs as $key => $ref) { 161 + if ($ref->getIsMaster()) { 162 + continue; 163 + } 164 + 165 + $server = $config[$key]; 166 + 167 + $partition = idx($server, 'partition'); 168 + if ($partition !== null) { 169 + throw new Exception( 170 + pht( 171 + 'Database "%s" is configured as a replica, but specifies a '. 172 + '"partition". Only master databases may have a partition '. 173 + 'configuration. Replicas use the same configuration as the '. 174 + 'master they follow.', 175 + $ref->getRefKey())); 176 + } 177 + 178 + $master_key = idx($server, 'master'); 179 + if ($master_key === null) { 180 + if ($is_partitioned) { 181 + throw new Exception( 182 + pht( 183 + 'Database "%s" is configured as a replica, but does not '. 184 + 'specify which "master" it follows in configuration. Valid '. 185 + 'masters are: %s.', 186 + $ref->getRefKey(), 187 + implode(', ', $master_keys))); 188 + } else if ($master_keys) { 189 + $master_key = head($master_keys); 190 + } else { 191 + throw new Exception( 192 + pht( 193 + 'Database "%s" is configured as a replica, but there is no '. 194 + 'master configured.', 195 + $ref->getRefKey())); 196 + } 197 + } 198 + 199 + if (!isset($ref_map[$master_key])) { 200 + throw new Exception( 201 + pht( 202 + 'Database "%s" is configured as a replica and specifies a '. 203 + 'master ("%s"), but that master is not a valid master. Valid '. 204 + 'masters are: %s.', 205 + implode(', ', $master_keys))); 206 + } 207 + 208 + $master_ref = $ref_map[$master_key]; 209 + $ref->setMasterRef($ref_map[$master_key]); 210 + $master_ref->addReplicaRef($ref); 211 + } 212 + 213 + return array_values($refs); 214 + } 215 + 216 + }
+9 -6
src/infrastructure/storage/lisk/PhabricatorLiskDAO.php
··· 62 62 63 63 $is_cluster = (bool)PhabricatorEnv::getEnvConfig('cluster.databases'); 64 64 if ($is_cluster) { 65 - $connection = $this->newClusterConnection($database, $mode); 65 + $connection = $this->newClusterConnection( 66 + $this->getApplicationName(), 67 + $database, 68 + $mode); 66 69 } else { 67 70 $connection = $this->newBasicConnection($database, $mode, $namespace); 68 71 } ··· 113 116 )); 114 117 } 115 118 116 - private function newClusterConnection($database, $mode) { 117 - $master = PhabricatorDatabaseRef::getMasterDatabaseRefForDatabase( 118 - $database); 119 + private function newClusterConnection($application, $database, $mode) { 120 + $master = PhabricatorDatabaseRef::getMasterDatabaseRefForApplication( 121 + $application); 119 122 120 123 if ($master && !$master->isSevered()) { 121 124 $connection = $master->newApplicationConnection($database); ··· 131 134 } 132 135 } 133 136 134 - $replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForDatabase( 135 - $database); 137 + $replica = PhabricatorDatabaseRef::getReplicaDatabaseRefForApplication( 138 + $application); 136 139 if ($replica) { 137 140 $connection = $replica->newApplicationConnection($database); 138 141 $connection->setReadOnly(true);