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

Do not keep all association records in the memory #14066

Merged

Conversation

Ladas
Copy link
Contributor

@Ladas Ladas commented Feb 24, 2017

Depends on

This is optimization of second+ refresh, where we always load everything we have in our DB, in order to figure out what needs to be created/delete/updated by comparing to data we got from the API.

The biggest difference is visible on large table, e.g. a lot of templates, s3 objects, etc.

number of records before after
200k mocked MiqTemplates 2.6GB 1.3GB
100k mocked MiqTemplates 2.3GB 868MB
78k real AWS public images 1.5GB 1.1GB

so this can save a lot of memory for large tables (in the real example, the memoory spikes to 1.1GB and does not get GCed, because of aws-sdk)

@miq-bot
Copy link
Member

miq-bot commented Feb 24, 2017

This pull request is not mergeable. Please rebase and repush.

@Ladas Ladas force-pushed the do_not_keep_all_association_records_in_the_memory branch 2 times, most recently from 858fcdf to 61f4dd3 Compare February 24, 2017 16:13
@Ladas
Copy link
Contributor Author

Ladas commented Feb 24, 2017

@miq-bot assign @kbrock

@Ladas Ladas force-pushed the do_not_keep_all_association_records_in_the_memory branch from 61f4dd3 to dc824d2 Compare February 24, 2017 16:21
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.

this is looking good. not thrilled by the service case.

feel we may be able to extract that and put it somewhere that it makes sense to see this kind of hard coding.

if inventory_object.nil?
next unless inventory_collection.delete_allowed?
deleted_counter += 1
record.public_send(inventory_collection.delete_method)
Copy link
Member

Choose a reason for hiding this comment

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

does this read better for you?

delete_counter += 1 if inventory_collection.delete_record(record)

def delete_record(record)
  return false unless inventory_collection.delete_allowed?
  record.public_send(delete_method)
  true
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

method extracted

record = inventory_collection.model_class.create!(hash.except(:id))
created_counter += 1

inventory_object.id = record.try(:id)
Copy link
Member

Choose a reason for hiding this comment

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

think record will always have a value here (do not try, do)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

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 .try was there because of the old refresh (took it from there). We don't do create! and update! there, so it can happen the record is not created.

index = inventory_collection.object_index_with_keys(unique_index_keys, record)
if record_index[index]
# We have a duplicate in the DB, destroy it
record_for_destruction = nil
Copy link
Member

Choose a reason for hiding this comment

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

thinking out loud: (I might have swapped my logic)

record_for_destruction, record_index[index] = prune_duplicates(record, record_index[index])
if record_for_destruction
  log
  record_for_destruction.destroy
end

# typical:
def prune_duplicates(record, previous_record)
  if previous_record
    [previous_record, record]
  else
    return [record, record]
  end
end
# the service case would be different.

from here, may be able to consolidate the record.service code - not sure if you can move that into a inventory_collection or something. But at least it will be compact.

@miq-bot
Copy link
Member

miq-bot commented Feb 24, 2017

This pull request is not mergeable. Please rebase and repush.

@kbrock
Copy link
Member

kbrock commented Feb 24, 2017

cool. dependency is merged. rebase away

Do not keep a big amount of AR objects in memory, by turning
around the alghoritm, we can load AR objects in batches and
just keep our InventoryObject + hashes with data in memory.
Remove .try(:id) and just use .id, we create and update
records with ! method name, so it should never be a nil.
Extract methods for create/delete/update
@Ladas Ladas force-pushed the do_not_keep_all_association_records_in_the_memory branch from dc824d2 to b649f9e Compare February 27, 2017 11:15
@kbrock
Copy link
Member

kbrock commented Feb 27, 2017

Do not change. just something I noticed:

SaveCollection::Helper#save_inventory_collection!

unique_index_keys = inventory_collection.manager_ref_to_cols
# ...
index = inventory_collection.object_index_with_keys(unique_index_keys, record)

may want to keep in the inventory_collection?


Also, regarding those {create,update,delete}_record methods, I thought it was a nit to add them, but after you made that change, it looks very sweet

else
record_index[index] = record
end
attributes_index[index] = attributes
Copy link
Member

Choose a reason for hiding this comment

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

future: merge attributes = ... onto this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm I think I need to make the attributes!, the order here is important


def update_record!(inventory_collection, record, hash, inventory_object)
record.assign_attributes(hash.except(:id, :type))
if inventory_collection.check_changed?
Copy link
Member

Choose a reason for hiding this comment

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

not sure if this is easier to read:
(again for future PR if you like it)

record.save if !inventory_collection.check_changed? || record.changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

inventory_object = inventory_objects_index.delete(index)
hash = attributes_index.delete(index)

if inventory_object.nil?
Copy link
Member

Choose a reason for hiding this comment

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

this confuses me
I expect if inventory_object exists, then to possible delete.

could you put a comment above this check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

right so, this is a case when it was found in MIQ DB but not in API

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Add nice comments for refresh saving workflow
Doing the condition for check_changed? inline
@miq-bot
Copy link
Member

miq-bot commented Feb 27, 2017

Checked commits Ladas/manageiq@fb0d86d~...37c2b3f with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0
1 file checked, 0 offenses detected
Everything looks good. 👍

@kbrock kbrock added this to the Sprint 55 Ending Feb 27, 2017 milestone Feb 27, 2017
@kbrock kbrock merged commit a5e2789 into ManageIQ:master Feb 27, 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.

4 participants