Repository: WeblateOrg/weblate

Issue:
O(n²) on component removal
### Describe the issue

When removing a bunch of components from a project which has tens of them causes a huge backlog of `update_checks` tasks because it is called for every component in the project for each component.

### I already tried

- [X] I've read and searched [the documentation](https://docs.weblate.org/).
- [X] I've searched for similar issues in this repository.

### Steps to reproduce the behavior

1. Create a project with 100 components.
2. Remove 10 of them.
3. Notice there will be 10*100 `update_checks` tasks queued.
4. Many of them will consequently fail because they were fired for consequently removed component.

### Expected behavior

Small number of tasks caused by massive component removal.

### Screenshots

_No response_

### Exception traceback

_No response_

### How do you run Weblate?

weblate.org service

### Weblate versions

_No response_

### Weblate deploy checks

_No response_

### Additional context

_No response_


Discussion:
Sentry issue: [WEBLATE-Q1](https://sentry.weblate.org/organizations/weblate/issues/761/?referrer=github_integration)

Resolving patch:
diff --git a/weblate/trans/models/component.py b/weblate/trans/models/component.py
--- a/weblate/trans/models/component.py
+++ b/weblate/trans/models/component.py
@@ -13,6 +13,7 @@
 from typing import Any, Dict, List, Optional
 from urllib.parse import quote as urlquote
 from urllib.parse import urlparse
+from uuid import uuid4
 
 import sentry_sdk
 from celery import current_task
@@ -3560,7 +3561,17 @@ def suggest_repo_link(self):
     def enable_review(self):
         return self.project.enable_review
 
+    @property
+    def update_checks_key(self):
+        return f"component-update-checks-{self.pk}"
+
     def schedule_update_checks(self, update_state: bool = False):
         from weblate.trans.tasks import update_checks
 
-        update_checks.delay(self.pk, update_state=update_state)
+        update_token = uuid4().hex
+        cache.set(self.update_checks_key, update_token)
+        transaction.on_commit(
+            lambda: update_checks.delay(
+                self.pk, update_token, update_state=update_state
+            )
+        )
diff --git a/weblate/trans/tasks.py b/weblate/trans/tasks.py
--- a/weblate/trans/tasks.py
+++ b/weblate/trans/tasks.py
@@ -11,6 +11,7 @@
 from celery import current_task
 from celery.schedules import crontab
 from django.conf import settings
+from django.core.cache import cache
 from django.db import transaction
 from django.db.models import Count, F
 from django.utils import timezone
@@ -485,8 +486,14 @@ def create_component(addons_from=None, in_task=False, **kwargs):
 
 
 @app.task(trail=False)
-def update_checks(pk: int, update_state: bool = False):
+def update_checks(pk: int, update_token: str, update_state: bool = False):
     component = Component.objects.get(pk=pk)
+
+    # Skip when further updates are scheduled
+    latest_token = cache.get(component.update_checks_key)
+    if latest_token and update_token != latest_token:
+        return
+
     component.batch_checks = True
     for translation in component.translation_set.exclude(
         pk=component.source_translation.pk


Test patch: