Recent Releases of https://github.com/petergoldstein/dalli
https://github.com/petergoldstein/dalli - v5.0.6
Performance:
-
Skip the cas-return flag on quiet
meta_setrequests (#1131)- In quiet mode memcached suppresses the
msresponse entirely, so the CAS requested by thecflag can never be read; sending it only added two bytes to every request - Applies to the bulk-write paths, where quiet sets are emitted:
Dalli::Client#multiblocks and the pipelined setter - Extracted from #1130; thanks to Jianbin Chen for this contribution
- In quiet mode memcached suppresses the
-
Reduce allocations in
KeyRegularizerand multi-key request paths (#1120)- Decomposed
KeyRegularizer#encodeinto separateneeds_encoding?andencodecalls so the common happy path avoids allocating an intermediate array for the two-element return value - Refactored
multi_get/multi_set/multi_deletecommand generation intoRequestFormatterto share its key-encoding helpers - Thanks to Jean Boussier for this contribution
- Decomposed
-
Reduce allocations in
ResponseBufferpipelined getk parsing (#1117)process_single_getk_responsewas building a fresh array to return results alongside the updated offset; refactored to store the offset as the last element of the existing tokens array and pop it, saving one allocation per response- Also skips trailing nils in the token array
- Thanks to Jean Boussier for this contribution
-
Enable frozen string literals in
RequestFormatter(#1118)- Frozen string literals had been inadvertently disabled; re-enabling reduces allocations by ~300,000 objects in a 10,000-iteration
get_multi_casbenchmark (562 MB → 550 MB total allocated) - Thanks to Jean Boussier for this contribution
- Frozen string literals had been inadvertently disabled; re-enabling reduces allocations by ~300,000 objects in a 10,000-iteration
-
Reduce allocations in
ResponseProcessor#value_from_tokens(#1113)token[1..].to_iwas allocating a new string for every token parsed; replaced with in-placeslice!followed by a token reset to avoid poisoning subsequent token comparisons- Saves 4 allocations per entry in
get_multi_casworkloads (a hotspot for IdentityCache) - Thanks to Jean Boussier for this contribution
-
Reduce allocations in common operation paths (#1111)
- Use
Symbol#nameoverSymbol#to_sto return a frozen string without allocation - Skip trace attribute hash construction when OpenTelemetry instrumentation is disabled
- Use argument forwarding (
...) inClient#performandThreadsafe#requestto avoid splat array allocation - Use
match?inKeyRegularizer#encodeto avoidMatchDataobject allocation - Reduces objects allocated by ~26% and memory by ~6% for a simple
getworkload - Thanks to Jean Boussier for this contribution
- Use
-
Fix pathological memory behavior in
ResponseBuffer(#1114)compact_if_neededwas intended to reclaim memory by slicing off consumed bytes, butbuffer.byteslice(@offset..)on an unfrozen string causes Ruby to allocate a hidden third string as the copy-on-write owner rather than freeing the original- Redesigns buffer management to pass reusable buffer objects directly to
read/read_nonblock, avoiding reallocation on each response read - Reduces allocations from ~2.38 GB to ~649 MB in a
get_multi_casbenchmark over 10,000 iterations - Accompanied by new unit tests for
ResponseBuffer(#1115) - Thanks to Jean Boussier for this contribution
Features:
delete_multinow returns the number of keys found and deleted (#1126)- Previously the return value was unspecified; callers (e.g. Rails, see rails/rails#58071) had no way to tell how many keys were actually removed
- The count is derived from the meta protocol's quiet-mode delete responses with no extra round-trips: successful deletes are suppressed while misses report
NF, so any response received before the terminator is a key that was not deleted - The single-server fast path now shares the pipelined path's bounded retry on transient (
RetryableNetworkError) network errors, so both paths behave consistently; the returned count is best-effort and may under-report if a network error triggers a retry, since keys deleted before the error are not recounted - Thanks to Iliana Hadzhiatanasova for this contribution
Bug Fixes:
-
Raise instead of returning a truncated value when the peer closes mid-response (#1135)
IO#read(count)on a blocking socket accumulates across TCP chunks and hands back a shorter buffer (ornil) in only one case: the stream hit EOF. That short buffer was passed through as the response body, so a memcached restart, proxy drop, or load balancer timeout partway through a response could surface a truncated but still decodable value to the caller, indistinguishable from a real one- A short read is now treated as the premature EOF it is, raising and closing the dirty socket so the request is retried on a fresh connection
- CRuby only; the JRuby path already used
Socket#readfull, which enforces the same contract - Extracted from #1130; thanks to Ian Ker-Seymer for the original fix and Jianbin Chen for the port
-
Tear down the connection when a non-
StandardErroraborts a request (#1136)Async::StopandThread#killdescend fromExceptionrather thanStandardError, so the rescue clauses inProtocol::Base#requestnever saw them; a scheduler cancelling a fiber parked on a response read skippedcloseentirely, leaving the connection marked as having a request in progress with partial response bytes still unread on the wire, and returning that half-used client to the pool underconnection_poolProtocol::Base#requestnow closes in anensureunless the request ran to completion, andConnectionManager#closeperforms its state cleanup in anensureso a second cancellation landing inside@sock.closecannot leave the socket non-nil with the request still marked in progressDalli::DalliErrorandDalli::MarshalErrornow close the connection at the point of failure rather than at the start of the next request; those paths already left the request in progress andConnectionManager#confirm_ready!closed on the next call, so this changes when the close happens rather than adding one- Extracted from #1130; thanks to Dan Mayer for the original fix and Jianbin Chen for the port
-
Fix
ResponseBuffercompaction logic (#1119)COMPACT_THRESHOLDwas removed in #1116 as apparently unused, but the constant was referenced by the compaction guard; its absence silently disabled buffer compaction- Restored the constant, corrected the compaction condition, and improved the buffer-shrinking implementation to use
String#bytesplice(backed bymemmove) for true in-place compaction - Adds targeted tests covering the compaction threshold and shrink behavior
- Thanks to Jean Boussier for this contribution
Maintenance:
-
Scope
StrictWarningsto Dalli's own source (#1134)- The test suite runs under
-wand prepends a hook toWarning.singleton_classthat turns warnings into failures, but that hook is global: a warning emitted while loading any third-party gem aborted the whole suite before a single test ran json2.21.2's pure-Ruby generator (used on JRuby, where the C extension is unavailable) warnsmethod redefined; discarding old to_hashat require time, which took thejruby-10CI job red with no change to Dalli- Warnings are now attributed to a source file and only raise for
lib/andtest/; attribution prefers the location Ruby embeds in the message, since the stack at that point describes the require chain rather than the offending code - Portable attribution of
Kernel#warncallers also required walking the stack rather than indexing it (Ruby 3.3/3.4 push an<internal:warning>frame that 4.0 does not), skipping RubyGems'Kernel#warnshim (active on JRuby but not CRuby), and resolving relative backtrace paths
- The test suite runs under
-
Make raw and namespace fast path tests actually use those options (#1129)
- Followup to #1127: the
rawandnamespacevariants passed those options to the helper that starts memcached, which configures the client the tests then discarded, so neither option was ever exercised - Passes the options to the client under test and adds assertions that fail if they are absent
- Thanks to Iliana Hadzhiatanasova for this contribution
- Followup to #1127: the
-
Benchmark
set_multiand add adelete_multitarget (#1132)- Enables the two
set_multireports that were commented out pending the arrival ofset_multi, resolving the accompanying TODO - Adds a
delete_multitarget comparing the pipelined path against N single deletes - Extracted from #1130; thanks to Jianbin Chen for this contribution
- Enables the two
-
Bump CI memcached to 1.6.41 and run benchmarks on pull requests (#1133)
- The tests workflow moves from 1.6.40 to 1.6.41; the benchmarks and profile workflows had drifted back on 1.6.23
- Extracted from #1130; thanks to Jianbin Chen for this contribution
-
Disable RuboCop metrics cops (#1128)
- Thanks to Jean Boussier for this contribution
-
Remove
PIDCachemodule (#1125)Process.pidis cached natively by Ruby 3.3+ (via https://bugs.ruby-lang.org/issues/19443), making the manual cache unnecessary now that Dalli requires Ruby 3.3+- Thanks to Jean Boussier for this contribution
-
Remove unused
COMPACT_THRESHOLDconstant fromResponseBuffer(#1116)- Followup cleanup after the buffer management redesign in #1114
- Note: subsequently found to be in use; restored and corrected in #1119
- Thanks to Jean Boussier for this contribution
-
Use
String#byteindexinstead ofString#indexwhen searching for the response terminator ingetk_response_from_buffer(#1112)- The result feeds directly into
byteslice;byteindexmakes the intent explicit, though both return the same value since the buffer encoding is alwaysBINARY - Thanks to Jean Boussier for this contribution
- The result feeds directly into
-
Make single-server fast path tests actually exercise the fast path (#1127)
- The batch-operation tests built clients through a helper that registers two address aliases for the same memcached process, so every client had a 2-server ring and the tests always ran through the pipelined path instead of the single-server fast path
- Adds a
single_server_clienttest helper that builds a client with a single address, and uses it in the affectedget_multi,set_multi, anddelete_multitests - Thanks to Iliana Hadzhiatanasova for this contribution
- Ruby
Published by petergoldstein 15 days ago