protected function getShardLink($shardID, $forWriting = false) { if (isset($this->links[$shardID])) { return $this->links[$shardID]; } $shardInfo = Zotero_Shards::getShardInfo($shardID); if (!$shardInfo) { throw new Exception("Invalid shard {$shardID}"); } if ($shardInfo['state'] == 'down') { throw new Exception("Shard {$shardID} is down", Z_ERROR_SHARD_UNAVAILABLE); } else { if ($shardInfo['state'] == 'readonly') { if ($forWriting) { throw new Exception("Cannot write to read-only shard {$shardID}", Z_ERROR_SHARD_READ_ONLY); } } } $auth = Zotero_DBConnectAuth('shard'); $config = array('host' => $shardInfo['address'], 'port' => $shardInfo['port'], 'username' => $auth['user'], 'password' => $auth['pass'], 'dbname' => $shardInfo['db'], 'charset' => 'utf8'); // For admin, use user/pass from master if (get_called_class() == 'Zotero_Admin_DB') { $auth = Zotero_DBConnectAuth($this->db); $config['username'] = $auth['user']; $config['password'] = $auth['pass']; } $this->links[$shardID] = new Zend_Db_Adapter_Mysqli($config); return $this->links[$shardID]; }
protected function getShardLink($shardID, $forWriting = false) { // TEMP if (get_called_class() == 'Zotero_FullText_DB') { $linkID = "FT" . $shardID; } else { $linkID = $shardID; } if (isset($this->links[$linkID])) { return $this->links[$linkID]; } $shardInfo = Zotero_Shards::getShardInfo($shardID); if (!$shardInfo) { throw new Exception("Invalid shard {$shardID}"); } if ($shardInfo['state'] == 'down') { throw new Exception("Shard {$shardID} is down", Z_ERROR_SHARD_UNAVAILABLE); } else { if ($shardInfo['state'] == 'readonly') { if ($forWriting && get_called_class() != 'Zotero_Admin_DB') { throw new Exception("Cannot write to read-only shard {$shardID}", Z_ERROR_SHARD_READ_ONLY); } } } $auth = Zotero_DBConnectAuth('shard'); $config = array('host' => $shardInfo['address'], 'port' => $shardInfo['port'], 'username' => $auth['user'], 'password' => $auth['pass'], 'dbname' => $shardInfo['db'], 'charset' => !empty($auth['charset']) ? $auth['charset'] : 'utf8', 'driver_options' => array("MYSQLI_OPT_CONNECT_TIMEOUT" => 5)); // TEMP: For now, use separate host if (get_called_class() == 'Zotero_FullText_DB') { $auth = Zotero_DBConnectAuth('fulltext'); $config['host'] = $auth['host']; $config['port'] = $auth['port']; } else { if (get_called_class() == 'Zotero_Admin_DB') { $auth = Zotero_DBConnectAuth($this->db); $config['username'] = $auth['user']; $config['password'] = $auth['pass']; } } $this->links[$linkID] = new Zend_Db_Adapter_Mysqli($config); $conn = $this->links[$linkID]->getConnection(); $conn->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5); // If profile was previously enabled, enable it for this link if ($this->profilerEnabled) { $this->links[$linkID]->getProfiler()->setEnabled(true); } return $this->links[$linkID]; }
public function logTotalRequestTime() { if (!Z_CONFIG::$STATSD_ENABLED) { return; } try { if (!empty($this->objectLibraryID)) { $shardID = Zotero_Shards::getByLibraryID($this->objectLibraryID); $shardInfo = Zotero_Shards::getShardInfo($shardID); $shardHostID = (int) $shardInfo['shardHostID']; StatsD::timing("api.request.total_by_shard.{$shardHostID}", (microtime(true) - $this->startTime) * 1000, 0.25); } } catch (Exception $e) { error_log("WARNING: " . $e); } StatsD::timing("api.memcached", Z_Core::$MC->requestTime * 1000, 0.25); StatsD::timing("api.request.total", (microtime(true) - $this->startTime) * 1000, 0.25); }
protected function getShardConnection($shardID, array $options = []) { if (!is_numeric($shardID)) { throw new Exception('$shardID must be an integer'); } $isWriteQuery = !empty($options['isWriteQuery']); $lastLinkFailed = !empty($options['lastLinkFailed']); $writeInReadMode = !empty($options['writeInReadMode']); // TEMP if (get_called_class() == 'Zotero_FullText_DB') { $linkID = "FT" . $shardID; } else { $linkID = $shardID; } if ($this->isReadOnly($shardID) && $isWriteQuery && !$writeInReadMode) { throw new Exception("Cannot get link for writing to shard {$shardID} in read-only mode"); } // Master queries always use the cached link that was created at class construction if ($shardID == 0) { //error_log($this->connections[$linkID]->link->getConnection()->host_info); return $this->connections[$linkID]; } // For read-only mode and read queries, use a cached link if available. Since this is // done before checking the latest shard info, it's possible for subsequent read queries // in a request to go through even if the shard was since disabled, but that's generally // not a big deal, and new requests will check the shard info again and throw. // // Read-only mode if ($this->isReadOnly($shardID) && !$writeInReadMode) { // Use a cached replica link if available. if (isset($this->replicaConnections[$linkID])) { // If the last link failed, try the next one. If no more, that's fatal. if ($lastLinkFailed) { $lastHost = $this->replicaConnections[$linkID][0]->host; if (sizeOf($this->replicaConnections[$linkID]) == 1) { throw new Exception("Read failed from replica {$lastHost} -- no more replica connections", Z_ERROR_SHARD_UNAVAILABLE); } error_log("WARNING: Read failed from replica {$lastHost} -- retrying on another replica"); array_shift($this->replicaConnections[$linkID]); } //error_log($this->replicaConnections[$linkID][0]->link->getConnection()->host_info); return $this->replicaConnections[$linkID][0]; } } else { if (!$isWriteQuery && isset($this->connections[$linkID])) { //error_log($this->connections[$linkID]->link->getConnection()->host_info); return $this->connections[$linkID]; } } $shardInfo = Zotero_Shards::getShardInfo($shardID); if (!$shardInfo) { throw new Exception("Invalid shard {$shardID}"); } if ($shardInfo['state'] == 'down') { throw new Exception("Shard {$shardID} is down", Z_ERROR_SHARD_UNAVAILABLE); } else { if ($shardInfo['state'] == 'readonly') { if ($isWriteQuery && get_called_class() != 'Zotero_Admin_DB') { throw new Exception("Cannot write to read-only shard {$shardID}", Z_ERROR_SHARD_READ_ONLY); } } } if ($this->isReadOnly($shardID) && !$writeInReadMode) { $replicas = Zotero_Shards::getReplicaInfo($shardInfo['shardHostID']); if ($replicas) { $writerCacheKey = 'shardHostReplicasWriter_' . $shardInfo['shardHostID']; $writerAddress = Z_Core::$MC->get($writerCacheKey); $writerConn = null; // Randomize replica order shuffle($replicas); foreach ($replicas as $replica) { $info = $shardInfo; $info['address'] = $replica['address']; $info['port'] = $replica['port']; $conn = new Zotero_DB_Connection(); $conn->host = $info['address']; $conn->shardID = $linkID; $conn->link = $this->getLinkFromConnectionInfo($info); if ($replica['address'] == $writerAddress) { $writerConn = $conn; } else { $this->replicaConnections[$linkID][] = $conn; } } // If we know the writer, add it last, so that all read replicas are tried first if ($writerConn) { $this->replicaConnections[$linkID][] = $writerConn; } else { try { $results = $this->replicaConnections[$linkID][0]->link->query("SHOW GLOBAL VARIABLES LIKE 'innodb_read_only'"); $row = $results->fetch(Zend_Db::FETCH_ASSOC); $connReadOnly = $row ? $row['Value'] : false; // If we found the writer if ($connReadOnly == "OFF") { // Store host in memcached so that every request doesn't need to check // the variables to sort the writer last. // // This can probably be increased, because the only consequence of not // knowing the writer is that a few requests could use the writer // for reads. Z_Core::$MC->set($writerCacheKey, $this->replicaConnections[$linkID][0]->host, 60); // If more than one connection, move writer connection to end and // close it if (sizeOf($this->replicaConnections[$linkID]) > 1) { $conn = array_shift($this->replicaConnections[$linkID]); $this->replicaConnections[$linkID][] = $conn; $conn->link->closeConnection(); } } } catch (Exception $e) { error_log("WARNING: Failed checking replica state: {$e}"); } } //error_log($this->replicaConnections[$linkID][0]->link->getConnection()->host_info); return $this->replicaConnections[$linkID][0]; } } // Host isn't read-only or down, so write queries can use a cached link if available. // Otherwise, make a new link. if (!isset($this->connections[$linkID])) { $conn = new Zotero_DB_Connection(); $conn->host = $shardInfo['address']; $conn->shardID = $linkID; $conn->link = $this->getLinkFromConnectionInfo($shardInfo); $this->connections[$linkID] = $conn; } //error_log($this->connections[$linkID]->link->getConnection()->host_info); return $this->connections[$linkID]; }