We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Long-running space reclamation task
**Version**
```
  "versions": [
    {
      "component": "core",
      "version": "3.21.0",
      "package": "pulpcore"
    },
    {
      "component": "container",
      "version": "2.14.2",
      "package": "pulp-container"
    },
    {
      "component": "rpm",
      "version": "3.18.5",
      "package": "pulp-rpm"
    },
    {
      "component": "python",
      "version": "3.7.2",
      "package": "pulp-python"
    },
    {
      "component": "ostree",
      "version": "2.0.0a6",
      "package": "pulp-ostree"
    },
    {
      "component": "file",
      "version": "1.11.1",
      "package": "pulp-file"
    },
    {
      "component": "deb",
      "version": "2.20.0",
      "package": "pulp_deb"
    },
    {
      "component": "certguard",
      "version": "1.5.5",
      "package": "pulp-certguard"
    },
    {
      "component": "ansible",
      "version": "0.15.0",
      "package": "pulp-ansible"
    }
  ],
```

Katello nightly (4.7)

**Describe the bug**

I noticed my reclaim space task was taking over 20 minutes in an environment with 63 repositories and 91485 rpm content units (to give some perspective).  PostgresSQL was being heavily taxed and taking 100% of one CPU core.  I tried to cancel it, but the cancellation was stuck so I needed to restart Pulpcore to stop the space reclamation.

Here's the task output after it was canceled forcefully:

```
{
  "pulp_href": "/pulp/api/v3/tasks/bce46114-a5d9-445a-a898-217210bf1975/",
  "pulp_created": "2022-11-15T16:38:50.639518Z",
  "state": "failed",
  "name": "pulpcore.app.tasks.reclaim_space.reclaim_space",
  "logging_cid": "c658f06c-3b76-49f6-a514-b19dd3bfbe52",
  "started_at": "2022-11-15T16:38:50.688113Z",
  "finished_at": "2022-11-15T17:09:06.918179Z",
  "error": {
    "reason": "Worker has gone missing."
  },
  "worker": "/pulp/api/v3/workers/80173b0a-f731-4c7b-b3ec-ed993369044e/",
  "parent_task": null,
  "child_tasks": [],
  "task_group": null,
  "progress_reports": [],
  "created_resources": [],
  "reserved_resources_record": [
    "shared:/pulp/api/v3/repositories/rpm/rpm/4ad1fb8e-ef06-42e6-a83a-00da97551dce/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/3224e11b-ec85-4e3d-8d7b-fd44dcfd184d/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/d0f49692-31dd-4709-9e52-27be83167a3f/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/bef78a95-9555-467b-9fe6-66650c081757/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/e5838919-ba35-4497-b8a0-98c10af8941b/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/7987e671-61e6-4d07-9c9b-ca7a07367d91/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/acd01e87-640a-4584-b52f-c999e937b55f/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/b01a1f40-c195-48c0-a05c-77b7748d6338/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/504b40fe-5d7f-456e-bc95-683878609791/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/8a1a3998-ff6c-460c-b26b-010ac57023a9/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/a1a44856-a028-4a2e-a539-aa73d3ef9ff3/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/1cde5855-eab1-4ac3-ac2f-f02a22541619/",
    "shared:/pulp/api/v3/repositories/deb/apt/509de38c-7ae7-4f7b-a37c-db8404488a51/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/cdd44804-8324-48ce-9e61-4ae6770d0427/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/dfe18547-f2bf-4c41-9b9e-32d6cb1e2f5e/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/d867837e-c35f-475d-9bb5-9c9bde465b19/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/a0bcd8d6-8e6d-4e05-83d1-8cbfbc28d8d9/",
    "shared:/pulp/api/v3/repositories/rpm/rpm/b0169f69-55cc-4ce1-830c-f444152c6853/"
  ]
}
```

**To Reproduce**
Run space reclamation on an environment with a similar amount of repositories and content to what I posted above.

**Expected behavior**
After chatting with @dralley it sounds like this may be slower performance than expected.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pulpcore/app/tasks/reclaim_space.py`
Content:
```
1 from pulpcore.app.models import (
2     Artifact,
3     Content,
4     ContentArtifact,
5     ProgressReport,
6     PublishedMetadata,
7     Repository,
8     RepositoryVersion,
9 )
10 
11 
12 def reclaim_space(repo_pks, keeplist_rv_pks=None, force=False):
13     """
14     This task frees-up disk space by removing Artifact files from the filesystem for Content
15     exclusive to the list of provided repos.
16 
17     Note: content marked as `proctected` will be excluded from the reclaim disk space.
18 
19     Kwargs:
20         repo_pks (list): A list of repo pks the disk reclaim space is performed on.
21         keeplist_rv_pks (list): A list of repo version pks that will be excluded from the reclaim
22         disk space.
23         force (bool): If True, uploaded content will be taken into account.
24 
25     """
26     reclaimed_repos = Repository.objects.filter(pk__in=repo_pks)
27     for repo in reclaimed_repos:
28         repo.invalidate_cache(everything=True)
29 
30     rest_of_repos = Repository.objects.exclude(pk__in=repo_pks)
31     c_keep_qs = Content.objects.filter(repositories__in=rest_of_repos)
32     c_reclaim_qs = Content.objects.filter(repositories__in=repo_pks)
33     c_reclaim_qs = c_reclaim_qs.exclude(
34         pk__in=c_keep_qs, pulp_type=PublishedMetadata.get_pulp_type()
35     )
36 
37     if keeplist_rv_pks:
38         rv_qs = RepositoryVersion.objects.filter(pk__in=keeplist_rv_pks)
39         rv_content = Content.objects.none()
40         for rv in rv_qs.iterator():
41             rv_content |= rv.content
42         c_reclaim_qs = c_reclaim_qs.exclude(pk__in=rv_content)
43 
44     content_distinct = c_reclaim_qs.distinct("pulp_type")
45     unprotected = []
46     for content in content_distinct:
47         if not content.cast().PROTECTED_FROM_RECLAIM:
48             unprotected.append(content.pulp_type)
49 
50     ca_qs = ContentArtifact.objects.filter(
51         content__in=c_reclaim_qs.values("pk"), artifact__isnull=False
52     )
53     if not force:
54         ca_qs = ca_qs.filter(remoteartifact__isnull=False)
55     artifact_pks = set()
56     ca_to_update = []
57     for ca in ca_qs.iterator():
58         if ca.content.pulp_type in unprotected:
59             artifact_pks.add(ca.artifact.pk)
60             ca.artifact = None
61             ca_to_update.append(ca)
62 
63     ContentArtifact.objects.bulk_update(objs=ca_to_update, fields=["artifact"], batch_size=1000)
64     artifacts_to_delete = Artifact.objects.filter(pk__in=artifact_pks)
65     progress_bar = ProgressReport(
66         message="Reclaim disk space",
67         total=artifacts_to_delete.count(),
68         code="reclaim-space.artifact",
69         done=0,
70         state="running",
71     )
72     progress_bar.save()
73 
74     counter = 0
75     interval = 100
76     for artifact in artifacts_to_delete.iterator():
77         # we need to manually call delete() because it cleans up the file on the filesystem
78         artifact.delete()
79         progress_bar.done += 1
80         counter += 1
81 
82         if counter >= interval:
83             progress_bar.save()
84             counter = 0
85 
86     progress_bar.state = "completed"
87     progress_bar.save()
88
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pulpcore/app/tasks/reclaim_space.py b/pulpcore/app/tasks/reclaim_space.py
--- a/pulpcore/app/tasks/reclaim_space.py
+++ b/pulpcore/app/tasks/reclaim_space.py
@@ -47,7 +47,7 @@
         if not content.cast().PROTECTED_FROM_RECLAIM:
             unprotected.append(content.pulp_type)
 
-    ca_qs = ContentArtifact.objects.filter(
+    ca_qs = ContentArtifact.objects.select_related("content", "artifact").filter(
         content__in=c_reclaim_qs.values("pk"), artifact__isnull=False
     )
     if not force:

{"golden_diff": "diff --git a/pulpcore/app/tasks/reclaim_space.py b/pulpcore/app/tasks/reclaim_space.py\n--- a/pulpcore/app/tasks/reclaim_space.py\n+++ b/pulpcore/app/tasks/reclaim_space.py\n@@ -47,7 +47,7 @@\n         if not content.cast().PROTECTED_FROM_RECLAIM:\n             unprotected.append(content.pulp_type)\n \n-    ca_qs = ContentArtifact.objects.filter(\n+    ca_qs = ContentArtifact.objects.select_related(\"content\", \"artifact\").filter(\n         content__in=c_reclaim_qs.values(\"pk\"), artifact__isnull=False\n     )\n     if not force:\n", "issue": "Long-running space reclamation task\n**Version**\r\n```\r\n  \"versions\": [\r\n    {\r\n      \"component\": \"core\",\r\n      \"version\": \"3.21.0\",\r\n      \"package\": \"pulpcore\"\r\n    },\r\n    {\r\n      \"component\": \"container\",\r\n      \"version\": \"2.14.2\",\r\n      \"package\": \"pulp-container\"\r\n    },\r\n    {\r\n      \"component\": \"rpm\",\r\n      \"version\": \"3.18.5\",\r\n      \"package\": \"pulp-rpm\"\r\n    },\r\n    {\r\n      \"component\": \"python\",\r\n      \"version\": \"3.7.2\",\r\n      \"package\": \"pulp-python\"\r\n    },\r\n    {\r\n      \"component\": \"ostree\",\r\n      \"version\": \"2.0.0a6\",\r\n      \"package\": \"pulp-ostree\"\r\n    },\r\n    {\r\n      \"component\": \"file\",\r\n      \"version\": \"1.11.1\",\r\n      \"package\": \"pulp-file\"\r\n    },\r\n    {\r\n      \"component\": \"deb\",\r\n      \"version\": \"2.20.0\",\r\n      \"package\": \"pulp_deb\"\r\n    },\r\n    {\r\n      \"component\": \"certguard\",\r\n      \"version\": \"1.5.5\",\r\n      \"package\": \"pulp-certguard\"\r\n    },\r\n    {\r\n      \"component\": \"ansible\",\r\n      \"version\": \"0.15.0\",\r\n      \"package\": \"pulp-ansible\"\r\n    }\r\n  ],\r\n```\r\n\r\nKatello nightly (4.7)\r\n\r\n**Describe the bug**\r\n\r\nI noticed my reclaim space task was taking over 20 minutes in an environment with 63 repositories and 91485 rpm content units (to give some perspective).  PostgresSQL was being heavily taxed and taking 100% of one CPU core.  I tried to cancel it, but the cancellation was stuck so I needed to restart Pulpcore to stop the space reclamation.\r\n\r\nHere's the task output after it was canceled forcefully:\r\n\r\n```\r\n{\r\n  \"pulp_href\": \"/pulp/api/v3/tasks/bce46114-a5d9-445a-a898-217210bf1975/\",\r\n  \"pulp_created\": \"2022-11-15T16:38:50.639518Z\",\r\n  \"state\": \"failed\",\r\n  \"name\": \"pulpcore.app.tasks.reclaim_space.reclaim_space\",\r\n  \"logging_cid\": \"c658f06c-3b76-49f6-a514-b19dd3bfbe52\",\r\n  \"started_at\": \"2022-11-15T16:38:50.688113Z\",\r\n  \"finished_at\": \"2022-11-15T17:09:06.918179Z\",\r\n  \"error\": {\r\n    \"reason\": \"Worker has gone missing.\"\r\n  },\r\n  \"worker\": \"/pulp/api/v3/workers/80173b0a-f731-4c7b-b3ec-ed993369044e/\",\r\n  \"parent_task\": null,\r\n  \"child_tasks\": [],\r\n  \"task_group\": null,\r\n  \"progress_reports\": [],\r\n  \"created_resources\": [],\r\n  \"reserved_resources_record\": [\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/4ad1fb8e-ef06-42e6-a83a-00da97551dce/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/3224e11b-ec85-4e3d-8d7b-fd44dcfd184d/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/d0f49692-31dd-4709-9e52-27be83167a3f/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/bef78a95-9555-467b-9fe6-66650c081757/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/e5838919-ba35-4497-b8a0-98c10af8941b/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/7987e671-61e6-4d07-9c9b-ca7a07367d91/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/acd01e87-640a-4584-b52f-c999e937b55f/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/b01a1f40-c195-48c0-a05c-77b7748d6338/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/504b40fe-5d7f-456e-bc95-683878609791/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/8a1a3998-ff6c-460c-b26b-010ac57023a9/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/a1a44856-a028-4a2e-a539-aa73d3ef9ff3/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/1cde5855-eab1-4ac3-ac2f-f02a22541619/\",\r\n    \"shared:/pulp/api/v3/repositories/deb/apt/509de38c-7ae7-4f7b-a37c-db8404488a51/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/cdd44804-8324-48ce-9e61-4ae6770d0427/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/dfe18547-f2bf-4c41-9b9e-32d6cb1e2f5e/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/d867837e-c35f-475d-9bb5-9c9bde465b19/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/a0bcd8d6-8e6d-4e05-83d1-8cbfbc28d8d9/\",\r\n    \"shared:/pulp/api/v3/repositories/rpm/rpm/b0169f69-55cc-4ce1-830c-f444152c6853/\"\r\n  ]\r\n}\r\n```\r\n\r\n**To Reproduce**\r\nRun space reclamation on an environment with a similar amount of repositories and content to what I posted above.\r\n\r\n**Expected behavior**\r\nAfter chatting with @dralley it sounds like this may be slower performance than expected.\r\n\n", "before_files": [{"content": "from pulpcore.app.models import (\n    Artifact,\n    Content,\n    ContentArtifact,\n    ProgressReport,\n    PublishedMetadata,\n    Repository,\n    RepositoryVersion,\n)\n\n\ndef reclaim_space(repo_pks, keeplist_rv_pks=None, force=False):\n    \"\"\"\n    This task frees-up disk space by removing Artifact files from the filesystem for Content\n    exclusive to the list of provided repos.\n\n    Note: content marked as `proctected` will be excluded from the reclaim disk space.\n\n    Kwargs:\n        repo_pks (list): A list of repo pks the disk reclaim space is performed on.\n        keeplist_rv_pks (list): A list of repo version pks that will be excluded from the reclaim\n        disk space.\n        force (bool): If True, uploaded content will be taken into account.\n\n    \"\"\"\n    reclaimed_repos = Repository.objects.filter(pk__in=repo_pks)\n    for repo in reclaimed_repos:\n        repo.invalidate_cache(everything=True)\n\n    rest_of_repos = Repository.objects.exclude(pk__in=repo_pks)\n    c_keep_qs = Content.objects.filter(repositories__in=rest_of_repos)\n    c_reclaim_qs = Content.objects.filter(repositories__in=repo_pks)\n    c_reclaim_qs = c_reclaim_qs.exclude(\n        pk__in=c_keep_qs, pulp_type=PublishedMetadata.get_pulp_type()\n    )\n\n    if keeplist_rv_pks:\n        rv_qs = RepositoryVersion.objects.filter(pk__in=keeplist_rv_pks)\n        rv_content = Content.objects.none()\n        for rv in rv_qs.iterator():\n            rv_content |= rv.content\n        c_reclaim_qs = c_reclaim_qs.exclude(pk__in=rv_content)\n\n    content_distinct = c_reclaim_qs.distinct(\"pulp_type\")\n    unprotected = []\n    for content in content_distinct:\n        if not content.cast().PROTECTED_FROM_RECLAIM:\n            unprotected.append(content.pulp_type)\n\n    ca_qs = ContentArtifact.objects.filter(\n        content__in=c_reclaim_qs.values(\"pk\"), artifact__isnull=False\n    )\n    if not force:\n        ca_qs = ca_qs.filter(remoteartifact__isnull=False)\n    artifact_pks = set()\n    ca_to_update = []\n    for ca in ca_qs.iterator():\n        if ca.content.pulp_type in unprotected:\n            artifact_pks.add(ca.artifact.pk)\n            ca.artifact = None\n            ca_to_update.append(ca)\n\n    ContentArtifact.objects.bulk_update(objs=ca_to_update, fields=[\"artifact\"], batch_size=1000)\n    artifacts_to_delete = Artifact.objects.filter(pk__in=artifact_pks)\n    progress_bar = ProgressReport(\n        message=\"Reclaim disk space\",\n        total=artifacts_to_delete.count(),\n        code=\"reclaim-space.artifact\",\n        done=0,\n        state=\"running\",\n    )\n    progress_bar.save()\n\n    counter = 0\n    interval = 100\n    for artifact in artifacts_to_delete.iterator():\n        # we need to manually call delete() because it cleans up the file on the filesystem\n        artifact.delete()\n        progress_bar.done += 1\n        counter += 1\n\n        if counter >= interval:\n            progress_bar.save()\n            counter = 0\n\n    progress_bar.state = \"completed\"\n    progress_bar.save()\n", "path": "pulpcore/app/tasks/reclaim_space.py"}], "after_files": [{"content": "from pulpcore.app.models import (\n    Artifact,\n    Content,\n    ContentArtifact,\n    ProgressReport,\n    PublishedMetadata,\n    Repository,\n    RepositoryVersion,\n)\n\n\ndef reclaim_space(repo_pks, keeplist_rv_pks=None, force=False):\n    \"\"\"\n    This task frees-up disk space by removing Artifact files from the filesystem for Content\n    exclusive to the list of provided repos.\n\n    Note: content marked as `proctected` will be excluded from the reclaim disk space.\n\n    Kwargs:\n        repo_pks (list): A list of repo pks the disk reclaim space is performed on.\n        keeplist_rv_pks (list): A list of repo version pks that will be excluded from the reclaim\n        disk space.\n        force (bool): If True, uploaded content will be taken into account.\n\n    \"\"\"\n    reclaimed_repos = Repository.objects.filter(pk__in=repo_pks)\n    for repo in reclaimed_repos:\n        repo.invalidate_cache(everything=True)\n\n    rest_of_repos = Repository.objects.exclude(pk__in=repo_pks)\n    c_keep_qs = Content.objects.filter(repositories__in=rest_of_repos)\n    c_reclaim_qs = Content.objects.filter(repositories__in=repo_pks)\n    c_reclaim_qs = c_reclaim_qs.exclude(\n        pk__in=c_keep_qs, pulp_type=PublishedMetadata.get_pulp_type()\n    )\n\n    if keeplist_rv_pks:\n        rv_qs = RepositoryVersion.objects.filter(pk__in=keeplist_rv_pks)\n        rv_content = Content.objects.none()\n        for rv in rv_qs.iterator():\n            rv_content |= rv.content\n        c_reclaim_qs = c_reclaim_qs.exclude(pk__in=rv_content)\n\n    content_distinct = c_reclaim_qs.distinct(\"pulp_type\")\n    unprotected = []\n    for content in content_distinct:\n        if not content.cast().PROTECTED_FROM_RECLAIM:\n            unprotected.append(content.pulp_type)\n\n    ca_qs = ContentArtifact.objects.select_related(\"content\", \"artifact\").filter(\n        content__in=c_reclaim_qs.values(\"pk\"), artifact__isnull=False\n    )\n    if not force:\n        ca_qs = ca_qs.filter(remoteartifact__isnull=False)\n    artifact_pks = set()\n    ca_to_update = []\n    for ca in ca_qs.iterator():\n        if ca.content.pulp_type in unprotected:\n            artifact_pks.add(ca.artifact.pk)\n            ca.artifact = None\n            ca_to_update.append(ca)\n\n    ContentArtifact.objects.bulk_update(objs=ca_to_update, fields=[\"artifact\"], batch_size=1000)\n    artifacts_to_delete = Artifact.objects.filter(pk__in=artifact_pks)\n    progress_bar = ProgressReport(\n        message=\"Reclaim disk space\",\n        total=artifacts_to_delete.count(),\n        code=\"reclaim-space.artifact\",\n        done=0,\n        state=\"running\",\n    )\n    progress_bar.save()\n\n    counter = 0\n    interval = 100\n    for artifact in artifacts_to_delete.iterator():\n        # we need to manually call delete() because it cleans up the file on the filesystem\n        artifact.delete()\n        progress_bar.done += 1\n        counter += 1\n\n        if counter >= interval:\n            progress_bar.save()\n            counter = 0\n\n    progress_bar.state = \"completed\"\n    progress_bar.save()\n", "path": "pulpcore/app/tasks/reclaim_space.py"}]}