-
Notifications
You must be signed in to change notification settings - Fork 897
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
Add a cache for full Feature objects #14037
Conversation
daf4b97
to
d9ba43b
Compare
@kbrock This is what I ended up doing to improve RBAC Features tree perf. |
@kbrock @Fryguy @gtanzillo we need to review and potentially redesign this |
end | ||
|
||
def self.obj_features | ||
@obj_cache ||= begin |
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.
Could this cause test contamination? If so, can you add it to EvmSpecHelper.clear_caches so it will be cleared on every test automatically?
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.
fixed below - thanks
We already cache features, just not using AR objects. Every time I've seen performance problems with features, it is because we were using traditional AR access into features instead of the Maybe now is the time to go from storing strings in a tree to storing a tree of AR objects? |
5987936
to
cddeeba
Compare
Link plz? |
@Fryguy see above -- These methods all access History: I didn't come up with this caching mechanism, but I did fix a bunch of bugs and changed the n+1 query to a single query. @lgalis helped me track down nuances in some bugs. Also, @chrisarcand and @jrafanie helped when we were performing up to 3 feature queries on some pages. Not sure if they remember those. tangent: You know, since this data isn't changed and is shipped with the product, it seems odd that we are loading into the db, not editing, and bringing back to cache in memory locally. Gems like ActiveHash were made to solve this problem. (it just loads the data from |
Also, for the record, we are not performing |
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.
didn't mean to apply the brakes.
We've had to fix code because people were doing things more of an object/rails way.
Now is a good time to get closer to rails here
end | ||
|
||
def self.obj_features | ||
@obj_cache ||= begin |
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.
fixed below - thanks
app/models/miq_product_feature.rb
Outdated
def self.obj_features | ||
@obj_cache ||= begin | ||
# create hash with parent identifier and details | ||
features = select('*').select(arel_attribute(:parent_identifier).as("parent_identifier")).each_with_object({}) do |f, h| |
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.
Think select
api has been enhanced since this was written. Could you test with:
select('*').select(:parent_identifier).each_with_object({})
end | ||
end | ||
|
||
def self.obj_feature_all_children(identifier) |
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.
would have been nice if we didn't have to prefix with obj_
a followup PR may be able to remove the string/hash only methods
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.
Agreed. I believe there are several places in both repos that lean on the hash method, but I'd be interested in moving that to objects instead.
app/models/miq_product_feature.rb
Outdated
feature = obj_features[identifier.to_s] | ||
ancestors = [] | ||
|
||
if feature |
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.
maybe:
return [] unless feature
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 think I would need an array-building alternative to using until
, which I would prefer but haven't come up with yet.
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 was talking about line 195... but it is minor
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.
ooh, and in response to your comment:
def self.obj_feature_ancestors(identifier)
feature = obj_features[identifier.to_s]
return [] unless feature
ancestors = []
while (prnt = obj_features[feature[:parent]].try(:[], :feature))
feature = obj_features[prnt.identifier]
ancestors << prnt
end
ancestors
end
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.
An array building alternative could be to just use recursion.
def self.obj_feature_ancestors(identifier)
feature = obj_features[identifier.to_s]
return [] unless feature
parent_feature = obj_features[feature[:parent]].try(:[], :feature)
return [] unless parent_feature
obj_feature_ancestors(parent_feature.identifier).unshift(parent_feature)
end
This seems a bit more intuitive to me as well, as the code is saying "get me all of the ancestors , and then just return all of that plus my parent"
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.
heh. yea. I like iterators over recursion. especially while trying to debug. but up to you
The current hash-caching mechanism would be fine, except that the current TreeBuilder implementation wants real objects vs. hashes. |
cddeeba
to
0e4ef8c
Compare
@Fryguy @kbrock Can you take another look at this one? ManageIQ/manageiq-ui-classic#137 is blocked on this. If we don't like it in core, I'll come up with some way of doing it on the UI side. |
👍 LGTM (even without those 2 edits) |
app/models/miq_product_feature.rb
Outdated
end | ||
|
||
def self.obj_feature_parent(identifier) | ||
obj_features[identifier.to_s] && obj_features[identifier.to_s][:parent] |
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 pattern is simplified with fetch_path
obj_feature.fetch_path(identifier.to_s, :parent)
app/models/miq_product_feature.rb
Outdated
end | ||
|
||
def self.obj_feature_children(identifier) | ||
obj_features[identifier.to_s] && obj_features[identifier.to_s][:children].select { |c| !c.hidden? } |
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 pattern is simplified with fetch_path and using reject
over select { !
obj_features.fetch_path(:identifier.to_s, :children).try(:reject, &:hidden?)
app/models/miq_product_feature.rb
Outdated
end | ||
|
||
def self.obj_feature_all_children(identifier) | ||
obj_features[identifier.to_s][:children] if obj_features[identifier.to_s] |
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 pattern is simplified with fetch_path
obj_feature.fetch_path(identifier.to_s, :children)
app/models/miq_product_feature.rb
Outdated
ancestors = [] | ||
|
||
if feature | ||
prnt = obj_features[feature[:parent]][:feature] |
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.
Please avoid abbreviations. Prefer parent
over prnt
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.
The reason for this was to avoid collision with Ancestry's parent
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.
it won't collide as it will be an instance variable, but even so, then I would prefer something like parent_feature
over prnt
expect(MiqProductFeature.obj_feature_parent("f4")).to eq("f3") | ||
end | ||
|
||
it "finds the ancestors of a feature" do |
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.
Can you add a test to handle the edge case of passing an invalid feature name? (which is what the if feature
is meant to handle)
@hayesr Sorry about not reviewing for a while...I got distracted and never got back to this one :/ |
@Fryguy No worries! I'll address these and ping you. |
0e4ef8c
to
728352d
Compare
This speeds up RBAC features tree significantly
728352d
to
b7f6668
Compare
@Fryguy ready for another look. |
@hayesr Did you see my follow up comment above? #14037 (comment) |
@Fryguy Drat. Missed that sorry. |
Checked commits hayesr/manageiq@b7f6668~...617b56b with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0 |
@Fryguy Updated now |
This speeds up RBAC features tree significantly by adding cached methods that return full objects.