Description
The Font Manager's "what's using this font" lookup (GET /fonts/?scope=usage) reads every active form on every request, and for a Multisite super admin, every form on every site. #2015 makes that scan about 2.5x faster and stops subsite admins walking the network, but the cost still grows with the number of forms and sites. Storing the result would remove the walk from the normal path.
Measurements
1,411 forms, 35 MB of display_meta, 602 PDFs, MariaDB 12.3 in Docker, single site. Multisite figures are projected from timing get_site_font_keys() on that site, not from a real network.
| Time | |
|---|---|
| Lookup before #2015 | 281 ms |
Lookup with the byte-wise LIKE in #2015 |
about 105 ms |
| Each other site of this size, before #2015 | about 270 ms |
| Each other site without Gravity Forms | 0.2 ms |
| Decoding a stored usage map | 0.3 ms |
| One query reading a key list per site, 10,000 simulated sites, plus totalling | under 10 ms |
Proposal
Per site: keep the font-to-PDF map that Model_Required_Fonts::get_font_pdfs() returns in a non-autoloaded option.
- Clear it on
gform_post_update_form_meta(display_metaonly),gform_post_form_activated,gform_post_form_deactivated,gform_post_form_trashed,gform_post_form_restored,gform_post_form_duplicatedandgform_after_delete_form. - Rebuild it the next time something needs it.
- Add a daily expiry as a backstop for direct database edits.
gf_form.date_updatedcan't serve as a fingerprint, becauseupdate_form_meta()doesn't touch it.
Network: each site writes its font keys (default font, locale fonts, PDF fonts) to its own sitemeta row, e.g. gfpdf_site_fonts_{blog_id}.
- One query on
meta_keyreads every site, with no per-site tables and noswitch_to_blog(). - One row per site means two sites saving at once can't overwrite each other.
- Also refresh a site's row when
gfpdf_settingsorWPLANGchanges.
Response size: the PDF list per font has no cap, so GET /fonts/?scope=usage grows with every PDF: 111 KB for 602 PDFs, about 185 bytes each, so around 1.8 MB before compression at 10,000 PDFs (projected).
- Once usage is stored, that endpoint can return only
reasonsandpdfs_labelfor each font, andGET /fonts/{id}?scope=usageon the existing route can returnpdfswhen a font is opened. - Don't truncate the list. A font can't be deleted until every PDF using it has changed, so each one needs to stay reachable.
- This depends on the stored map. Without it, each font click would walk the forms again.
Open points
- Backfill: existing sites have no row until something writes one. Fill them in through the existing sync cron (
Model_Font_Downloader::SYNC_EVENT), a bounded number of sites per run. - Sites without a row: count their core fonts as in use, so Delete stays locked until the backfill finishes. The Font Manager may need to say the count is still being worked out.
- Accuracy of the delete check:
delete_downloaded_font()and the custom font delete should still check the current site live. A stale "unused" from another site is low risk for core fonts, as a deleted font downloads again when a PDF needs it, but with downloads turned off it would stay missing. - Clean-up: remove a site's row when the site is deleted, and add the rows and the option to
Model_Uninstall. - Form title changes: the stored map includes form titles, so confirm every path that renames a form clears it.
7.0
7.0 adds its own font tables, so a usage table keyed by font may be a better home there. This issue is about what 6.x can do without a new table.
Activity
Store font usage instead of walking every form and site
Store font usage instead of walking every form and site
Store font usage instead of walking every form and site