diff --git a/activities/migrations/0029_post_url_db_index.py b/activities/migrations/0029_post_url_db_index.py new file mode 100644 index 00000000..c6357a11 --- /dev/null +++ b/activities/migrations/0029_post_url_db_index.py @@ -0,0 +1,17 @@ +from django.contrib.postgres.operations import AddIndexConcurrently +from django.db import migrations, models + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("activities", "0028_previewcard_post_preview_card"), + ] + + operations = [ + AddIndexConcurrently( + model_name="post", + index=models.Index(fields=["url"], name="activities_post_url_idx"), + ), + ] diff --git a/activities/models/post.py b/activities/models/post.py index f4cfea11..764c48ff 100644 --- a/activities/models/post.py +++ b/activities/models/post.py @@ -518,6 +518,7 @@ class Meta: fields=["visibility", "local", "created"], name="ix_post_local_public_created", ), + models.Index(fields=["url"], name="activities_post_url_idx"), ] class urls(urlman.Urls): diff --git a/api/views/statuses.py b/api/views/statuses.py index 4d11157d..8a1dd100 100644 --- a/api/views/statuses.py +++ b/api/views/statuses.py @@ -2,7 +2,6 @@ from datetime import timedelta from typing import Literal -from django.db.models import Q from django.http import HttpRequest from api.views import get_object_or_404 from django.utils import timezone @@ -123,12 +122,12 @@ def _extract_quote_from_trailing_url( if not match: return None, text url = match.group(2) - post = ( - Post.objects.not_hidden() - .visible_to(identity, include_replies=True) - .filter(Q(object_uri=url) | Q(url=url)) - .first() - ) + # Look up by object_uri first (unique index), then fall back to url. + # Avoids an OR query that causes a full table scan on large tables. + base_qs = Post.objects.not_hidden().visible_to(identity, include_replies=True) + post = base_qs.filter(object_uri=url).first() + if post is None: + post = base_qs.filter(url=url).first() if not post: return None, text cleaned = text[: match.start(2)].rstrip("\n ")