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
Course cache #15965
Course cache #15965
Conversation
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 seems like a reasonable approach to establish the cache and associated tests, with a follow-up PR to remove the code duplication. Happy to do that PR if you want, though I don't think I'm any more "qualified" than you are.
dashboard/app/models/course.rb
Outdated
course_cache_from_cache || course_cache_from_db | ||
end | ||
|
||
def self.get_without_cache(id) |
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.
nit: Though this is simply duplication of the existing paradigm, the method API and the method implementation would be better documented if id
were replaced with id_or_name
or course_id_or_course_name
. Also below.
dashboard/app/models/course.rb
Outdated
# TODO(bjvanminnen): share this somehow with script.rb? | ||
def self.should_cache? | ||
return false if Rails.application.config.levelbuilder_mode | ||
return false if ENV['UNIT_TEST'] || ENV['CI'] |
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.
nit: The last two lines could just be ENV['UNIT_TEST'] || ENV['CI']
.
+💯 for this option!
I'm not sure anyone on the team has full knowledge of how we cache models (maybe @wjordan?). Cleaning up the various disperse caching methods into one understandable system would help! |
This is now ready for review, having got tests passing. One other significant change I made was to make it so that we always add scripts/courses to the cache the same way. Previously, if we added them when we first got data from the db, we did it using My plan for the time being is to punt on trying to move this all into a single file. I think this current step puts us in a better place (we get caching for courses) and though there's some duplication of pretty similar code, I don't think it really makes things any harder to understand. Feel free to push back. |
dashboard/app/models/course.rb
Outdated
|
||
# generates our course_cache from what is in the Rails cache | ||
def self.course_cache_from_cache | ||
Course.connection |
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.
What does this do? The returned connection isn't used.
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.
I do not know. I copied it because it was there in the script version. My assumption was that it perhaps somehow locked the db connection for this course of this method?
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.
Looks like it maybe ensures that a connection is established for this thread, but that should happen anyway in the below query. It doesn't appear to do anything special for the method. See http://api.rubyonrails.org/classes/ActiveRecord/Base.html
dashboard/app/models/course.rb
Outdated
Course.all.pluck(:id).each do |course_id| | ||
course = get_without_cache(course_id) | ||
cache[course.name] = course | ||
cache[course.id.to_s] = course |
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.
Why retrieve all the ids, then loop through and retrieve each Course via get_without_cache
, instead of one query?
If this is for the included associations, how about defining them in a scope and using that in both locations?
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.
Something like:
scope :with_associated_models, -> {includes([:plc_course, :course_scripts])}
def self.course_cache_from_db
{}.tap do |cache|
Course.with_associated_models.find_each do |course|
cache[course.name] = course
cache[course.id.to_s] = course
end
end
end
def self.get_without_cache(id_or_name)
# ...
find_by = (id_or_name.to_i.to_s == id_or_name.to_s) ? :id : :name
Course.with_associated_models.find_by(find_by => id_or_name)
end
def self.get_without_cache(id_or_name) | ||
# a bit of trickery so we support both ids which are numbers and | ||
# names which are strings that may contain numbers (eg. 2-3) | ||
find_by = (id_or_name.to_i.to_s == id_or_name.to_s) ? :id : :name |
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.
might be cleaner to use regex instead? id_or_name =~ /^\d+$/ ? :id : :name
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 was copied from what's used in Script, which seems to have been working for us. I'd be semi-worried about introducing a regression trying to derive an equivalent regex.
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.
I'm ok leaving this alone now, and revisiting when (if) we clean this up and consolidate the caching logic. I can help with that effort.
FWIW, we use this same regex for a similar purpose in other places.
The test that keeps failing for me (level_group.feature) passes pretty consistently on my local host. In addition, it's one that used to be marked @no_circle due to flakiness on circle. Josh just remarked it and a couple others as no_circle. I'm going to give this circle another attempt, but unless something new pops up would like to merge. Anyone willing to give me an approve? :) |
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.
SGTM.
Only failure was in weblab_submittable. This was disabled until recently on circle because of known flakiness, and passes for me locally, so I'm going to merge this PR. |
This PR adds a course cache, styled after the cache currently used by scripts. It also update a script method (
course_link
) to actually take advantage of the cache instead of hitting the db.My biggest concern with this PR is that it ends up duplicating a lot of code. I'm not sure if it makes sense to spend a lot of time cleaning that up or not.
I think the right way to do this (and I'm open to feedback) would be to have a single file that handles our model caches. This would extract various cacheing that exists in the Script model and the Course model, and instead have most of that logic live in our
model_cache.rb
. This would likely have a singleshould_cache?
method used, and there might be other opportunities to share helpers. My main concern is that I'm still only semi-competent when it comes to Ruby/Rails, and I'm not sure I'm the best person to try to build this. Would be interested in feedback from others as to how much they dislike the current repetitiveness.