Exemplo n.º 1
0
 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];
 }
Exemplo n.º 2
0
 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];
 }
Exemplo n.º 3
0
 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);
 }
Exemplo n.º 4
0
 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];
 }