holidays / holidays

A collection of Ruby methods to deal with statutory and other holidays. You deserve a holiday!

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.

@ptrimble

I merged this PR and will release a bug update shortly.

Oh, and thank you again! 😄