Skip to content

perf: pipeline HMSET and EXPIREAT in Redis and Predis cache handlers - #10707

Open
gr8man wants to merge 1 commit into
codeigniter4:developfrom
gr8man:perf/redis-pipeline-save
Open

gr8man wants to merge 1 commit into
codeigniter4:developfrom
gr8man:perf/redis-pipeline-save

Conversation

@gr8man

@gr8man gr8man commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Description
In both RedisHandler::save() and PredisHandler::save(), saving a cache item with a TTL previously executed two sequential network round trips:

  1. hMset (hmset)
  2. expireAt (expireat)

This PR executes both commands in a single network round trip using Redis pipelining ($this->redis->multi(Redis::PIPELINE) in RedisHandler and $this->redis->pipeline(...) in PredisHandler).

Behavior & Compatibility Analysis

  • Data Structure: Preserves the existing Hash structure (['__ci_type' => ..., '__ci_value' => ...]). This ensures 100% compatibility with existing cache keys, hMget(), hIncrBy() (increment()/decrement()), and getMetaData().
  • Permanent TTL ($ttl === 0): Continues to execute a single hMset without expiration.
  • Edge-case / Error path consideration: In normal usage, BC is completely preserved. The only semantic difference is on error paths: if HMSET fails (e.g. if an existing key holds a non-hash data type), EXPIREAT in the pipeline batch will still execute on that existing key, while save() returns false as expected (previously expireAt was not reached if hMset failed). Using a Lua script to conditionally expire was considered, but avoided to prevent cluster and proxy portability complexity.

Benchmark (5,000 save() operations, local loopback)

  • Sequential (2 round trips): 528.25 ms (~0.106 ms per save)
  • Pipelined (1 round trip): 298.33 ms (~0.060 ms per save)
  • Local speedup: 1.77x (43.5% latency reduction). Over network links (e.g. cloud/separate container with 0.5–2 ms RTT), this eliminates an entire network round trip per save (~50% network latency reduction).

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

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.
@carson-codeigniter4 carson-codeigniter4 Bot added the enhancement PRs that improve existing functionalities label Nov 1, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement PRs that improve existing functionalities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant