#14620·wagtail

丰富的文本页面链接读取站点根路径缓存,并查询 Llocale, 每链接一次而不是每页一次

作者: jpmckinney创建于 2026年9月16日更新于 2026年9月16日
标签status:Needs Reviewstatus:Needs Infocomponent:Rich text🚀 Performance

Issue summary

PageLinkHandler.expand_db_attributes_many() batches the database access for the pages it links to, via get_many(), but then resolves each URL individually with page.localized.url. Both halves of that expression repeat work that the pass could share:

  • Page.get_url() ends in Page._get_site_root_paths(), which memoizes on the cache_object it is given, and on the page instance otherwise. Rich text rewriting has no request in scope, and get_many() builds fresh page instances for every rich text value, so each linked page is a separate cache.get("wagtail_site_root_paths") — a network round trip on Redis or Memcached.
  • .localized calls Locale.get_active(), and LocaleManager.get_for_language() is an uncached .get(), so every link is also a SELECT ... FROM wagtailcore_locale WHERE language_code = %s when WAGTAIL_I18N_ENABLED is true. On a fresh Wagtail 8.0 project (steps below), rendering a page whose body holds three links costs:
    links cache reads of wagtail_site_root_paths wagtailcore_locale queries
    none (baseline) 0 1
    3 links to 3 pages, in 3 rich text values 3 4
    3 links to 3 pages, in 1 rich text value 3 4
    3 links to the same page, in 1 rich text value 1 4
    The cache reads scale with the distinct pages linked from each rich text value, and the Locale queries with the links themselves. A single lookup per render would serve all of them.
    This surfaced as a Sentry N+1 alert on a production page, whose repeating group was one cache read, the two queries from PageQuerySet.specific() in get_many(), and one Locale query, repeated for each rich text value in the body. The per-request cost is small, but it scales with how many links an editor writes into a page's body, and a project cannot reach it: {% pageurl %} fixes URL resolution in a project's own templates, but not inside rich text rewriting.

Steps to reproduce

  1. Start a new project with wagtail start myproject, and set WAGTAIL_I18N_ENABLED = True.
  2. Give HomePage three RichTextFields, body_one, body_two and body_three, and render all three with the richtext filter in home_page.html.
  3. Run the following, which creates a page to link to, puts one link in each rich text field, then counts the cache reads and the queries of a render: Python import collections from django.core.cache import cache from django.db import connection from django.test import Client from django.test.utils import CaptureQueriesContext, setup_test_environment from home.models import HomePage setup_test_environment()

home = HomePage.objects.get(slug="home") if not HomePage.objects.filter(slug="target").exists(): home.add_child(instance=HomePage(title="Target", slug="target")) target = HomePage.objects.get(slug="target")

home.body_one = home.body_two = home.body_three = f'

Target

' home.save()

cache.clear() reads = collections.Counter() with CaptureQueriesContext(connection) as queries: with Client() as client: client.get("/home/") reads.update(queries.cache_reads) reads.update(queries.query_counts) print(f"cache reads: {reads}") print(f"query counts: {queries.query_counts}")

内容来源: wagtail/wagtail