Repository: WeblateOrg/weblate

Issue:
Improve other units lookup speed
### Describe the problem

Loading of "Other units" tab currently takes most of the time on translating strings what is not acceptable.

### Describe the solution you'd like

Improve the lookup performance.

### Describe alternatives you've considered

Load this tab on demand similarly as we do with other ones which take longer to load.

### Screenshots

_No response_

### Additional context

It can be seen measured at https://sentry.weblate.org/organizations/weblate/performance/weblate:716f79e6d30342e78770ec8aa0b711d5/, where this took 3 seconds for https://hosted.weblate.org/translate/weblate/application/tok/

74e62740a9a3add3b4e644d9efd147d71609feda improves the situation a bit as it reduces pressure on the database and 2e54bbb7351bc069fb063e4d7b1b14f00de13ddd adds sentry span for this operation so that it can be measured together with Python processing.




Discussion:
I'm curious what the sentry measurement looks like, could you post a screenshot?
This is the relevant part:

![image](https://github.com/WeblateOrg/weblate/assets/212189/ac0bf151-b50c-49eb-8e5b-e951668b08ad)


Resolving patch:
diff --git a/weblate/trans/views/edit.py b/weblate/trans/views/edit.py
--- a/weblate/trans/views/edit.py
+++ b/weblate/trans/views/edit.py
@@ -11,7 +11,8 @@
 from django.contrib.auth.decorators import login_required
 from django.contrib.messages import get_messages
 from django.core.exceptions import PermissionDenied
-from django.db.models import Case, IntegerField, Q, When
+from django.db.models import Case, IntegerField, Q, Value, When
+from django.db.models.functions import MD5
 from django.http import (
     HttpResponse,
     HttpResponseBadRequest,
@@ -102,39 +103,35 @@ def get_other_units(unit):
             return result
 
         if unit.target:
-            query = query | (Q(target=unit.target) & Q(state__gte=STATE_TRANSLATED))
-        units = Unit.objects.filter(
-            query,
-            translation__component__project=component.project,
-            translation__language=translation.language,
-        )
-        # Use memory_db for the query in case it exists. This is supposed
-        # to be a read-only replica for offloading expensive translation
-        # queries.
-        if "memory_db" in settings.DATABASES:
-            units = units.using("memory_db")
+            query |= Q(target__md5=MD5(Value(unit.target))) & Q(
+                state__gte=STATE_TRANSLATED
+            )
 
         units = (
-            units.annotate(
+            Unit.objects.filter(
+                query,
+                translation__component__project=component.project,
+                translation__language=translation.language,
+            )
+            .annotate(
                 matches_current=Case(
                     When(condition=match, then=1),
                     default=0,
                     output_field=IntegerField(),
                 )
             )
-            .select_related(
-                "translation",
-                "translation__language",
-                "translation__plural",
-                "translation__component",
-                "translation__component__project",
-                "translation__component__source_language",
-            )
+            .prefetch()
             .order_by("-matches_current")
         )
 
+        # Use memory_db for the query in case it exists. This is supposed
+        # to be a read-only replica for offloading expensive translation
+        # queries.
+        if "memory_db" in settings.DATABASES:
+            units = units.using("memory_db")
+
         max_units = 20
-        units_limited = units[:max_units].prefetch()
+        units_limited = units[:max_units]
         units_count = len(units_limited)
 
         # Is it only this unit?


Test patch: