From 5148ce16c6b98ef57c178a3c43b6133631673b9f Mon Sep 17 00:00:00 2001 From: Daniele Alessandri Date: Sat, 17 Dec 2011 10:58:37 +0100 Subject: [PATCH] Add the ability to mark specific commands or scripts for EVAL as read-only. As suggested by @Seldaek in ISSUE #21, it would be desiderable to have the ability to mark certain scripts for EVAL (and EVALSHA) as read-only to avoid switching to the master server when this is not really needed. When marking a script as read-only, only its SHA1 hash is saved to use less memory and to make this work transparently for both EVAL and EVALSHA. --- lib/Predis/Network/PredisReplication.php | 98 +++++++++++++++++++----- 1 file changed, 77 insertions(+), 21 deletions(-) diff --git a/lib/Predis/Network/PredisReplication.php b/lib/Predis/Network/PredisReplication.php index 9cd2afef..f971be46 100644 --- a/lib/Predis/Network/PredisReplication.php +++ b/lib/Predis/Network/PredisReplication.php @@ -23,6 +23,7 @@ use Predis\Commands\ICommand; class PredisReplication implements IConnectionReplication { private $readonly = array(); + private $readonlySHA1 = array(); private $current = null; private $master = null; private $slaves = array(); @@ -35,26 +36,6 @@ class PredisReplication implements IConnectionReplication $this->readonly = $this->getReadOnlyOperations(); } - /** - * Returns if the specified command performs a read-only operation - * against a key stored on Redis. - * - * @param ICommand $command Instance of Redis command. - * @return Boolean - */ - protected function isReadOperation(ICommand $command) - { - if (isset($this->readonly[$id = $command->getId()])) { - if (true === $readonly = $this->readonly[$id]) { - return true; - } - - return $readonly($command); - } - - return false; - } - /** * Checks if one master and at least one slave have been defined. */ @@ -255,7 +236,82 @@ class PredisReplication implements IConnectionReplication } /** - * Returns a list of commands that perform read-only operations. + * Returns if the specified command performs a read-only operation + * against a key stored on Redis. + * + * @param ICommand $command Instance of Redis command. + * @return Boolean + */ + protected function isReadOperation(ICommand $command) + { + if (isset($this->readonly[$id = $command->getId()])) { + if (true === $readonly = $this->readonly[$id]) { + return true; + } + + return $readonly($command); + } + + if (($eval = $id === 'EVAL') || $id === 'EVALSHA') { + $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0); + + if (isset($this->readonlySHA1[$sha1])) { + if (true === $readonly = $this->readonlySHA1[$sha1]) { + return true; + } + + return $readonly($command); + } + } + + return false; + } + + /** + * Marks a command as a read-only operation. When the behaviour of a + * command can be decided only at runtime depending on its arguments, + * a callable object can be provided to dinamically check if the passed + * instance of a command performs write operations or not. + * + * @param string $commandID ID of the command. + * @param mixed $readonly A boolean or a callable object. + */ + public function setCommandReadOnly($commandID, $readonly = true) + { + $commandID = strtoupper($commandID); + + if ($readonly) { + $this->readonly[$commandID] = $readonly; + } + else { + unset($this->readonly[$commandID]); + } + } + + /** + * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When + * the behaviour of a script can be decided only at runtime depending on + * its arguments, a callable object can be provided to dinamically check + * if the passed instance of EVAL or EVALSHA performs write operations or + * not. + * + * @param string $script Body of the Lua script. + * @param mixed $readonly A boolean or a callable object. + */ + public function setScriptReadOnly($script, $readonly = true) + { + $sha1 = sha1($script); + + if ($readonly) { + $this->readonlySHA1[$sha1] = $readonly; + } + else { + unset($this->readonlySHA1[$sha1]); + } + } + + /** + * Returns the default list of commands performing read-only operations. * * @return array */