丰富的文本页面链接读取站点根路径缓存,并查询 Llocale, 每链接一次而不是每页一次
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 inPage._get_site_root_paths(), which memoizes on thecache_objectit is given, and on the page instance otherwise. Rich text rewriting has no request in scope, andget_many()builds fresh page instances for every rich text value, so each linked page is a separatecache.get("wagtail_site_root_paths")— a network round trip on Redis or Memcached..localizedcallsLocale.get_active(), andLocaleManager.get_for_language()is an uncached.get(), so every link is also aSELECT ... FROM wagtailcore_locale WHERE language_code = %swhenWAGTAIL_I18N_ENABLEDis 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_pathswagtailcore_localequeriesnone (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 Localequeries 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()inget_many(), and oneLocalequery, 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
- Start a new project with
wagtail start myproject, and setWAGTAIL_I18N_ENABLED = True. - Give
HomePagethreeRichTextFields,body_one,body_twoandbody_three, and render all three with therichtextfilter inhome_page.html. - 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'
' 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