Skip to content
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

Merged
merged 2 commits into from
Mar 22, 2017
Merged

Conversation

hayesr
Copy link
Contributor

@hayesr hayesr commented Feb 23, 2017

This speeds up RBAC features tree significantly by adding cached methods that return full objects.

@miq-bot miq-bot added the wip label Feb 23, 2017
@hayesr hayesr force-pushed the feature_obj_cache branch 2 times, most recently from daf4b97 to d9ba43b Compare February 23, 2017 19:20
@hayesr hayesr changed the title [WIP] Add a cache for full Feature objects Add a cache for full Feature objects Feb 23, 2017
@hayesr
Copy link
Contributor Author

hayesr commented Feb 23, 2017

@kbrock This is what I ended up doing to improve RBAC Features tree perf.

@chessbyte
Copy link
Member

@kbrock @Fryguy @gtanzillo we need to review and potentially redesign this MiqProductFeature class vis-a-vis RBAC. It was originally created exclusively for the Classic UI. Now that we have REST API and the Service UI, it is time to re-think this.

end

def self.obj_features
@obj_cache ||= begin
Copy link
Member

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed below - thanks

@kbrock
Copy link
Member

kbrock commented Feb 24, 2017

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 feature_root/feature_children (or even feature_all_children)

Maybe now is the time to go from storing strings in a tree to storing a tree of AR objects?
Or maybe we just need to apply our previous handling of this to the ui

@Fryguy
Copy link
Member

Fryguy commented Feb 24, 2017

We already cache features, just not using AR objects.

Link plz?

@kbrock
Copy link
Member

kbrock commented Feb 25, 2017

@Fryguy see above -- These methods all access MiqProductFeature#features, which is where this is cached.

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 yaml and skips the db)

@kbrock
Copy link
Member

kbrock commented Feb 25, 2017

Also, for the record, we are not performing Rbac on features, but rather displaying this feature tree for an Rbac screen

@chrisarcand
Copy link
Member

Copy link
Member

@kbrock kbrock left a 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed below - thanks

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|
Copy link
Member

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)
Copy link
Member

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

Copy link
Contributor Author

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.

feature = obj_features[identifier.to_s]
ancestors = []

if feature
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe:

return [] unless feature

Copy link
Contributor Author

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.

Copy link
Member

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

Copy link
Member

@kbrock kbrock Mar 16, 2017

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

Copy link
Member

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"

Copy link
Member

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

@hayesr
Copy link
Contributor Author

hayesr commented Feb 27, 2017

The current hash-caching mechanism would be fine, except that the current TreeBuilder implementation wants real objects vs. hashes.

@hayesr hayesr force-pushed the feature_obj_cache branch from cddeeba to 0e4ef8c Compare March 9, 2017 18:59
@hayesr
Copy link
Contributor Author

hayesr commented Mar 9, 2017

@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.

@kbrock
Copy link
Member

kbrock commented Mar 16, 2017

👍 LGTM (even without those 2 edits)

end

def self.obj_feature_parent(identifier)
obj_features[identifier.to_s] && obj_features[identifier.to_s][:parent]
Copy link
Member

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)

end

def self.obj_feature_children(identifier)
obj_features[identifier.to_s] && obj_features[identifier.to_s][:children].select { |c| !c.hidden? }
Copy link
Member

@Fryguy Fryguy Mar 16, 2017

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?)

end

def self.obj_feature_all_children(identifier)
obj_features[identifier.to_s][:children] if obj_features[identifier.to_s]
Copy link
Member

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)

ancestors = []

if feature
prnt = obj_features[feature[:parent]][:feature]
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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
Copy link
Member

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)

@Fryguy
Copy link
Member

Fryguy commented Mar 16, 2017

@hayesr Sorry about not reviewing for a while...I got distracted and never got back to this one :/

@hayesr
Copy link
Contributor Author

hayesr commented Mar 16, 2017

@Fryguy No worries! I'll address these and ping you.

@hayesr hayesr force-pushed the feature_obj_cache branch from 0e4ef8c to 728352d Compare March 16, 2017 20:33
This speeds up RBAC features tree significantly
@hayesr hayesr force-pushed the feature_obj_cache branch from 728352d to b7f6668 Compare March 16, 2017 20:46
@hayesr
Copy link
Contributor Author

hayesr commented Mar 16, 2017

@Fryguy ready for another look.

@Fryguy
Copy link
Member

Fryguy commented Mar 16, 2017

@hayesr Did you see my follow up comment above? #14037 (comment)

@hayesr
Copy link
Contributor Author

hayesr commented Mar 16, 2017

@Fryguy Drat. Missed that sorry.

@miq-bot
Copy link
Member

miq-bot commented Mar 16, 2017

Checked commits hayesr/manageiq@b7f6668~...617b56b with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0
3 files checked, 0 offenses detected
Everything looks good. 🏆

@hayesr
Copy link
Contributor Author

hayesr commented Mar 16, 2017

@Fryguy Updated now

@Fryguy Fryguy merged commit 53e4995 into ManageIQ:master Mar 22, 2017
@Fryguy Fryguy added this to the Sprint 57 Ending Mar 27, 2017 milestone Mar 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants