-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fetch fill lock #46
base: main
Are you sure you want to change the base?
Fetch fill lock #46
Conversation
improve support for meta flags and fetch_with_lock
@@ -4,7 +4,7 @@ on: [push, pull_request] | |||
|
|||
jobs: | |||
test: | |||
runs-on: ubuntu-20.04 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
github actions needed this update to run
@@ -150,10 +150,10 @@ def quiet? | |||
alias multi? quiet? | |||
|
|||
# NOTE: Additional public methods should be overridden in Dalli::Threadsafe | |||
ALLOWED_QUIET_OPS = %i[add replace set delete incr decr append prepend flush noop].freeze |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rubocop update required moving this outside of private
|
||
assert_equal val1, val2 | ||
# not yet hit, and last accessed 0 from set | ||
assert_equal({ c: 0, l: 0, h: false, t: -1, bitflag: nil }.sort, meta_flags.sort) | ||
assert meta_flags.delete(:c) | ||
assert_equal({ l: 0, h: false, t: -1, bitflag: nil }.sort, meta_flags.sort) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at the moment bitflags is always present as it is a bit outside of a legacy flag... we could try to refactor it a away it is a bit awkward as a legacy flag, but there is meta flag option -f
to request it so we could get rid of it, some of the dalli methods expect it so it might require a bit larger refactoring.
# rubocop:disable Metrics/CyclomaticComplexity | ||
# rubocop:disable Metrics/PerceivedComplexity | ||
def fetch_with_lock(key, ttl = nil, req_options = nil) | ||
req_options = {} if req_options.nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need an early return here since there is a default lock TTL and interval?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this just sets to an empty hash opposed to nil as it is annoying to keep checking for if the value is in the hash or if the options don't exist at all.
lib/dalli/client.rb
Outdated
new_val = yield | ||
set(key, new_val, ttl_or_default(ttl), clean_req_options) | ||
return new_val | ||
elsif meta_flags[:z] && (!val.nil? || val != '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why we need the !val.nil? || val != ''
check. Would you mind explaining plz?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when you get a W
or Z
flag returned it will create a memcached entry with '' empty string as the value. So if you call get it will return with '' which isn't what you expect for a regular get, but when you passed the N
flag you will expect the client to ignore and not use the '' value. Given re-ordering this set of if statements, it can now just be if there is a z
response ignore the val.
lib/dalli/client.rb
Outdated
lock_ttl = req_options.delete(:lock_ttl) || LOCK_TTL | ||
fill_lock_interval = req_options.delete(:fill_lock_interval) || FILL_LOCK_INTERVAL | ||
|
||
raise ArgumentError, 'lock_ttl must be a positive integer' if !lock_ttl.is_a?(Integer) && lock_ttl <= 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise ArgumentError, 'lock_ttl must be a positive integer' if !lock_ttl.is_a?(Integer) && lock_ttl <= 1 | |
raise ArgumentError, 'lock_ttl must be a positive integer' if !lock_ttl.is_a?(Integer) && lock_ttl < 1 |
Since a lock TTL of 1s is valid
improve support for meta flags and fetch_with_lock
This starts to drive more usage of optional meta flags for specific features and cleans up the existing interface.
The benchmarks are for now in another repo where we are integrating and testing this branch...
This benchmark includes many competing threads which will hit the race condition and thundering herd around a fill lock. It shows this new method vs a more naive lock implemented outside of the dalli client. Outside of just benchmark performance it records the total count of the cache being refilled, the lock prevents many duplicate fills which can stress underlying systems like the database query. As the number of workers on a hot key gross the importance of the fill lock is more clear. The
Dalli fetch with locking
is this new method, it is the most efficient in terms of refilling, the only lower total fills wasdalli_store_lock_fills
but only because it was so slow it completed far less iterations.This is a benchmark with a single thread just looping fetch while a key will expire and need to be refilled often. Note that the with lock is basically same-ish difference when there is no competition: