-
Notifications
You must be signed in to change notification settings - Fork 12
OSIDB-4678: Improve promote performance #1156
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| import logging | ||
| import uuid | ||
| from collections import defaultdict | ||
| from functools import cached_property | ||
| from itertools import chain | ||
| from itertools import batched, chain | ||
|
|
||
| import pghistory | ||
| import pgtrigger | ||
|
|
@@ -366,6 +367,7 @@ def set_public(self): | |
| >>> my_flaw.acl_read | ||
| ... [UUID(...), UUID(...)] | ||
| """ | ||
|
|
||
| self.set_acl_read(*settings.PUBLIC_READ_GROUPS) | ||
| self.set_acl_write(settings.PUBLIC_WRITE_GROUP) | ||
| # Update the embargoed annotation to reflect the new ACL state | ||
|
|
@@ -669,32 +671,76 @@ def unembargo(self): | |
| if related_instance: | ||
| related_instance.unembargo() | ||
|
|
||
| def set_public_nested(self): | ||
| def set_public_nested(self, max_chunk_size=5000): | ||
| """ | ||
| Change internal ACLs to public ACLs for all related Flaw objects and save them. | ||
| The only exception is "snippets", which should always have internal ACLs. | ||
| The Flaw itself will be saved later to avoid duplicate operations. | ||
|
|
||
| This method collects all objects that need to be updated and performs | ||
| chunked queryset updates to minimize database queries and avoid storing | ||
| model instances in memory. | ||
| """ | ||
| objects_to_update = defaultdict(set) # {ModelClass: {pk, ...}} | ||
|
|
||
| visited = set() | ||
| self._collect_objects_for_public_update(objects_to_update, visited) | ||
|
|
||
| if not any(objects_to_update.values()): | ||
| return | ||
|
|
||
| # Cut off microseconds to match TrackingMixin.save() behavior | ||
| now = timezone.now().replace(microsecond=0) | ||
|
|
||
| public_acl_read = [ | ||
| uuid.UUID(acl) for acl in generate_acls(settings.PUBLIC_READ_GROUPS) | ||
| ] | ||
| public_acl_write = [ | ||
| uuid.UUID(acl) for acl in generate_acls([settings.PUBLIC_WRITE_GROUP]) | ||
| ] | ||
|
|
||
| for model_class, object_ids in objects_to_update.items(): | ||
| if not object_ids: | ||
| continue | ||
|
|
||
| if not issubclass(model_class, ACLMixin): | ||
| continue | ||
|
Elkasitu marked this conversation as resolved.
|
||
|
|
||
| update_kwargs = {"acl_read": public_acl_read, "acl_write": public_acl_write} | ||
| if issubclass(model_class, TrackingMixin): | ||
| update_kwargs["updated_dt"] = now | ||
|
Comment on lines
+709
to
+711
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You assume every related model has acl_read/acl_write, and while that currently may be the case, this can easily break in the future. Instead of checking individual attribute presence, I'd check whether the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't get rid of that, It wont work because using objects_to_update as a βseenβ structure bacause it doesn't have non-Flaw objects that are currently in is_internal. So Cycles can go through the Flaw itself and/or already-public related objects (or other typer), which will never be in objects_to_update, so using it for cycle detection can lead to infinite recursion or force me into something messier to avoid it.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I missed that this is being defined in |
||
|
|
||
| for pk_chunk in batched(object_ids, max_chunk_size): | ||
| model_class.objects.filter(pk__in=pk_chunk).update(**update_kwargs) | ||
|
|
||
| for pk in object_ids: | ||
| model_class(pk=pk).set_history_public() | ||
|
|
||
| def _collect_objects_for_public_update(self, objects_to_update, visited): | ||
| """ | ||
| Recursively collect all related objects that need to have their ACLs | ||
| updated to public. The Flaw itself is not collected as it's saved separately. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: The |
||
|
|
||
| Uses a defaultdict(set) keyed by model class to collect pks. | ||
| Uses a set of (type, pk) to prevent infinite recursion. | ||
| """ | ||
| from osidb.models import Flaw | ||
|
|
||
| if self.pk is None: | ||
| return | ||
|
|
||
| visit_key = (type(self), self.pk) | ||
|
|
||
| if visit_key in visited: | ||
| return | ||
|
|
||
| visited.add(visit_key) | ||
|
|
||
| if not isinstance(self, Flaw): | ||
| if not self.is_internal: | ||
| return | ||
| kwargs = {} | ||
| if issubclass(type(self), AlertMixin): | ||
| # suppress the validation errors as we expect that during | ||
| # the update the parent and child ACLs will not equal | ||
| kwargs["raise_validation_error"] = False | ||
| if issubclass(type(self), TrackingMixin): | ||
| # do not auto-update the updated_dt timestamp as the | ||
| # followup update would fail on a mid-air collision | ||
| kwargs["auto_timestamps"] = False | ||
|
Comment on lines
-684
to
-691
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The commit removes these adjustments but they might be covered in the bulk update. I think it might be a good idea to double-check that the removal of these lines won't cause issues.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. set_public_nested() uses QuerySet.update(...), which does not call save() and does not run model validation, so thereβs nothing to suppress and also I explicitly set updated_dt when bulk-updating to do the corresponding as TrackingMixin models with out the trigger. |
||
| self.set_public() | ||
| self.set_history_public() | ||
| self.save(**kwargs) | ||
| objects_to_update[type(self)].add(self.pk) | ||
|
|
||
| # chain all the related instances in reverse relationships (o2m, m2m) | ||
| # as we only care for the ACLs which are unified | ||
| for related_instance in chain.from_iterable( | ||
| getattr(self, name).all() | ||
| for name in [ | ||
|
|
@@ -707,17 +753,18 @@ def set_public_nested(self): | |
| ) | ||
| ] | ||
| ): | ||
| # continue deeper into the related context | ||
| related_instance.set_public_nested() | ||
|
|
||
| # chain related instances in forward relationships (m2o, o2o) | ||
| related_instance._collect_objects_for_public_update( | ||
| objects_to_update, visited | ||
| ) | ||
| for field in self._meta.concrete_fields: | ||
| if isinstance( | ||
| field, (models.ForeignKey, models.OneToOneField) | ||
| ) and issubclass(field.related_model, ACLMixin): | ||
| related_instance = getattr(self, field.name) | ||
| if related_instance: | ||
| related_instance.set_public_nested() | ||
| related_instance._collect_objects_for_public_update( | ||
| objects_to_update, visited | ||
| ) | ||
|
|
||
|
|
||
| class AlertManager(ACLMixinManager): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.