Skip to content

v5.2.0

Choose a tag to compare

@petergoldstein petergoldstein released this 02 Oct 11:00
· 134 commits to main since this release
dc238e3

Adds Dalli::CacheResult (#1156) and the opt-in defer_drain option (#1168), several get_multi/set/delete performance improvements from radixdev, and a fix for multi-server get_multi with empty values (#1170), which could silently return another key's value. Upgrading is recommended.

Features:

  • Add Dalli::CacheResult, returned by the new #get_with_metadata_result and #get_multi_with_metadata_result (#1156)
    • An opt-in, typed view over the Hash that #get_with_metadata and #get_multi_with_metadata already return; those methods are unchanged, and changing their return type is left for a future major version (#1151)
    • value, cas, hit_before, last_access and ttl_remaining readers (the last three are nil unless requested), plus miss?, hit?, stale?, won_recache? and lost_recache? predicates
    • Results are frozen, and a Hash that claims to be both a miss and stale raises ArgumentError
    • #get_multi_with_metadata_result omits misses and accepts req_options:, like #get_multi_with_metadata; it has no block form
  • Add the opt-in defer_drain client option (#1168)
    • With defer_drain: true, a quiet/multi block no longer waits at its end for the replies to its requests. The requests are still sent right away; the replies are read with one noop per server just before the next non-quiet request to that server
    • Adds Client#drain_deferred_responses, to drain at a boundary of the caller's choosing (the end of a web request or job)
    • Tradeoff: an error reply to a quiet request surfaces (and is discarded) at that later point, not at the end of the block
    • Off by default, so existing behavior is unchanged
    • Replies memcached sends for quiet requests wait on the connection until they're drained, so a client that only makes quiet requests should call drain_deferred_responses periodically; see "Deferred Draining" in the README
    • Thanks to Julian Richard Contreras and Matt Dick for this contribution

Performance:

  • Skip the empty read at the end of each pipelined read_available call (#1164)
    • A read shorter than the chunk size means the socket is drained, so the extra read_nonblock that only returned :wait_readable is gone; the IO.select loop still picks up anything that arrives later
    • Saves one syscall per server per multi-server get_multi; on 4 servers a 3-key get_multi goes from 16% to 5% slower than 2.7.11 in the #930 benchmark
    • TLS sockets also check SSLSocket#pending, since IO.select cannot see data OpenSSL has buffered
    • Thanks to Julian Richard Contreras for this contribution
  • Reduce Ruby overhead on plain set and delete (#1166)
    • A set with no CAS, routing tokens or quiet mode builds its ms request with one string interpolation (RequestFormatter.plain_meta_set), and a delete with no options uses plain_meta_delete; both produce the same bytes as before
    • HD c<cas>, HD and NF replies are parsed without splitting them into tokens
    • Allocations per set drop from 22 to 18, and per delete from 12 to 7
    • Thanks to Julian Richard Contreras for this contribution
  • Only drain servers that were sent quiet requests when a quiet/multi block ends (#1167)
    • Ending the block used to send a noop to every server in the ring and wait for each reply, one after another, so its cost grew with ring size even when the block touched one server (or none)
    • Each connection now records when a quiet request is written, and the block's drain skips the others; servers that were never connected are no longer connected just to be drained
    • With ~300us of network round trip, multi { set } on a 16-server ring goes from 4.9ms to 0.32ms; an empty block no longer touches the network
    • Thanks to Julian Richard Contreras for this contribution
  • Reduce Ruby overhead in get_multi reply parsing and key routing (#1169)
    • VA reply headers are read in place instead of being split into tokens, on both the multi-server (pipelined) and single-server paths; results are unchanged, and unusual headers still take the token path
    • A key's server is found through a bucket table over the ring's continuum instead of a binary search, and the key's own server is tried before the failover loop; the server chosen for every key is unchanged
    • Allocations for a 100-key get_multi on 4 servers drop from 1,500 to 1,104; over loopback it goes from 301us to 243us
    • Thanks to Julian Richard Contreras for this contribution

Bug fixes:

  • Fix multi-server get_multi stopping at an empty value (#1170)
    • The pipelined reply parser took a hit on a zero-length value (VA 0) for the terminating MN, so that server's remaining keys were silently missing from the result
    • Its value terminator and any replies not yet parsed stayed on the connection and were read as the replies to later commands on it. Those commands could fail with Dalli::DalliError: Response error, or a single-key get could silently return another key's value
    • An empty value is now returned as '', like a single-key get and single-server get_multi
    • Affects the meta protocol since 3.2.0 (the default since 5.0.0)
    • Thanks to Julian Richard Contreras for this contribution