Caching of Holidays has either a negligible or a negative performance impact.
AnotherJoSmith opened this issue · comments
Hello! Thanks for your work maintaining this great gem!
I've been looking into how the caching functionality affects performance. I have noticed that setting up the cache has at best no performance benefits and at worst can slow down lookups for holidays. I've been using this script with minor tweaks to get the results. This is with ruby 2.3.1 and Holidays 5.1.
Cached regions | Time (s) for 10000 random lookups (1 year cache) |
---|---|
None | 4.59 |
:us, :observed | 4.82 |
:us, :ca, :observed | 4.9 |
:us, :ca, :gb, :ie, :observed | 5.18 |
It may be worth it to remove the recommendation from the README to cache the results until this is addressed.
Ooof! Nice catch! I inherited this particular piece of functionality and I will readily admit that I have done zero testing around it. Let me dig in and get back to you.
Found out what the issue is. There is a misuse of the splat operator for options which results in the options array getting mutated everytime it gets passed through a method.
For example when I call cache_between, the following methods get called
Holidays.cache_between(Date.today, 1.year.from_now, :ca)
def cache_between(start_date, end_date, *options)
# options = [:ca]
Factory::Definition.cache_repository.cache_between(start_date, end_date, cache_data, options)
end
def cache_between(start_date, end_date, cache_data, *options)
# options = [[:ca]]
@cache_range[options] = start_date..end_date
end
However when I call Holidays.on(Date.today, :ca)
, the following happens:
Holidays.on(Date.today, :ca)
def on(date, *options)
# options = [:ca]
between(date, date, options)
end
def between(start_date, end_date, *options)
# options = [[:ca]]
if cached_holidays = Factory::Definition.cache_repository.find(start_date, end_date, options)
end
def find(start_date, end_date, *options)
# options = [[[:ca]]]
# @cache_range only has a key for [[:ca]] so nothing is found.
if range = @cache_range[options]
...
end
end
The fix will involve using the splat operator when passing the options hash to convert the array to method arguments. For example Holidays.on()
should be the following
def on(date, *options)
between(date, date, *options)
end
This will correctly pass the arguments to the between method.
I merged this PR and will release a bug update shortly.
Oh, and thank you again! 😄