From 7de6a6d56a89acc1dbe21748813cb160fabf31d1 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Sun, 1 Nov 2026 23:36:11 +0100 Subject: [PATCH] perf: pipeline HMSET and EXPIREAT in Redis and Predis cache handlers Send HMSET and EXPIREAT together in a single pipeline round trip rather than issuing two sequential network requests on every cache save. For normal cache usage, behavior and data structures remain identical. In error scenarios where HMSET fails (e.g. key contains a non-hash type), the pipelined EXPIREAT will still execute on the existing key, while save() returns false as expected. --- system/Cache/Handlers/PredisHandler.php | 15 ++++++++++----- system/Cache/Handlers/RedisHandler.php | 19 ++++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/system/Cache/Handlers/PredisHandler.php b/system/Cache/Handlers/PredisHandler.php index 79e7ae98f916..bc7a092441af 100644 --- a/system/Cache/Handlers/PredisHandler.php +++ b/system/Cache/Handlers/PredisHandler.php @@ -118,15 +118,20 @@ public function save(string $key, mixed $value, int $ttl = 60): bool return false; } - if (! $this->redis->hmset($key, ['__ci_type' => $dataType, '__ci_value' => $value]) instanceof Status) { - return false; - } + $fields = ['__ci_type' => $dataType, '__ci_value' => $value]; if ($ttl !== 0) { - $this->redis->expireat($key, Time::now()->getTimestamp() + $ttl); + // Send HMSET and EXPIREAT in a single round trip. + $expiresAt = Time::now()->getTimestamp() + $ttl; + $results = $this->redis->pipeline(static function ($pipe) use ($key, $fields, $expiresAt): void { + $pipe->hmset($key, $fields); + $pipe->expireat($key, $expiresAt); + }); + + return is_array($results) && ($results[0] ?? null) instanceof Status; } - return true; + return $this->redis->hmset($key, $fields) instanceof Status; } public function delete(string $key): bool diff --git a/system/Cache/Handlers/RedisHandler.php b/system/Cache/Handlers/RedisHandler.php index ccbb29bcd032..3e62f2b1ab83 100644 --- a/system/Cache/Handlers/RedisHandler.php +++ b/system/Cache/Handlers/RedisHandler.php @@ -134,15 +134,24 @@ public function save(string $key, mixed $value, int $ttl = 60): bool return false; } - if (! $this->redis->hMset($key, ['__ci_type' => $dataType, '__ci_value' => $value])) { - return false; - } + $fields = ['__ci_type' => $dataType, '__ci_value' => $value]; if ($ttl !== 0) { - $this->redis->expireAt($key, Time::now()->getTimestamp() + $ttl); + // Send HMSET and EXPIREAT in a single round trip. + $pipeline = $this->redis->multi(Redis::PIPELINE); + + if ($pipeline === false) { + return false; + } + + $pipeline->hMset($key, $fields); + $pipeline->expireAt($key, Time::now()->getTimestamp() + $ttl); + $results = $pipeline->exec(); + + return is_array($results) && (bool) ($results[0] ?? false); } - return true; + return (bool) $this->redis->hMset($key, $fields); } public function delete(string $key): bool