Server Overview: every visit reloads fontconfig and probes every offered script #1133

Closed
opened 2026-10-31 03:04:16 +00:00 by tiagoagueda · 0 comments
Owner

What exists today

core/server_views.py:210-260 OverviewView.get_context_data calls _font_scripts() (core/server_views.py:161-176) on every visit, which calls documents/fonts.py:164-206 renderable_scripts(). That calls _handles() (documents/fonts.py:239-259), which runs fontconfig.FcInitLoadConfigAndFonts() and builds a new PangoFT2FontMap: fontconfig reads its configuration and the cache of every installed font, and the image installs fonts-dejavu-core fonts-noto-core fonts-noto-cjk (docker/Dockerfile:273), several hundred faces. Then, for each of the 25 PROBES and 1 STYLE_PROBES entries the offered languages need, _script_drawable creates a Pango context, loads a font and reads its cmap table.

The answer depends only on the fonts installed on the machine and the offered languages, neither of which changes while a gunicorn worker runs in the image (the worker is recycled after about 500 requests, docker/Dockerfile:380). The page is staff-only and visited occasionally, so the win is latency on that page, not throughput.

_theme_fonts() (core/server_views.py:179-196, documents/theme_assets.py:565-588) also reads every theme font file on each visit; that is plain file reading and cheaper, but has the same "cannot change while running" property.

Measurement: not possible locally. Importing weasyprint.text.ffi on the development machine fails with cannot load library 'libgobject-2.0-0', so the cost of FcInitLoadConfigAndFonts with the image's fonts could not be timed within the brief's limits. FontConfiguration() in WeasyPrint does the same load once per render, which is the order of cost involved.

What would change

Memoise renderable_scripts() for the life of the process, keyed on the offered scripts and styles (functools.lru_cache on a helper taking frozenset(languages.scripts_offered()) and frozenset(languages.styles_offered())), and the _handles() font map with it. The check_fonts management command (documents/management/commands/check_fonts.py:36) keeps asking afresh by calling the uncached function, so an operator who has just installed a font package and runs the command gets the live answer, and the page shows the fresh answer from the next worker restart onwards.

script_draws and family_draws (documents/fonts.py:209-236), used by tests to decide skips, would share the cached font map too, which speeds the test files that call them once per test.

No library is needed.

What it saves or protects

small: one fontconfig load and 26 Pango font lookups per Overview visit become one per worker process; not measured (WeasyPrint's native libraries are absent on the development machine)

Accessibility

none

Decisions

  • Cache for the process, or for a fixed time? Recommended: for the process; the image's workers recycle, and the command remains the fresh check.
  • Cache _theme_fonts() too? Recommended: yes, keyed on the installed themes, in the same change.

Found by the efficiency and reliability audit of 2026-10-30 (finding P1; index postulo/postulo#1108). Checked against main at daee30b15 (2026-10-30).

## What exists today `core/server_views.py:210-260` `OverviewView.get_context_data` calls `_font_scripts()` (`core/server_views.py:161-176`) on every visit, which calls `documents/fonts.py:164-206` `renderable_scripts()`. That calls `_handles()` (`documents/fonts.py:239-259`), which runs `fontconfig.FcInitLoadConfigAndFonts()` and builds a new `PangoFT2FontMap`: fontconfig reads its configuration and the cache of every installed font, and the image installs `fonts-dejavu-core fonts-noto-core fonts-noto-cjk` (`docker/Dockerfile:273`), several hundred faces. Then, for each of the 25 `PROBES` and 1 `STYLE_PROBES` entries the offered languages need, `_script_drawable` creates a Pango context, loads a font and reads its cmap table. The answer depends only on the fonts installed on the machine and the offered languages, neither of which changes while a gunicorn worker runs in the image (the worker is recycled after about 500 requests, `docker/Dockerfile:380`). The page is staff-only and visited occasionally, so the win is latency on that page, not throughput. `_theme_fonts()` (`core/server_views.py:179-196`, `documents/theme_assets.py:565-588`) also reads every theme font file on each visit; that is plain file reading and cheaper, but has the same "cannot change while running" property. Measurement: not possible locally. Importing `weasyprint.text.ffi` on the development machine fails with `cannot load library 'libgobject-2.0-0'`, so the cost of `FcInitLoadConfigAndFonts` with the image's fonts could not be timed within the brief's limits. `FontConfiguration()` in WeasyPrint does the same load once per render, which is the order of cost involved. ## What would change Memoise `renderable_scripts()` for the life of the process, keyed on the offered scripts and styles (`functools.lru_cache` on a helper taking `frozenset(languages.scripts_offered())` and `frozenset(languages.styles_offered())`), and the `_handles()` font map with it. The `check_fonts` management command (`documents/management/commands/check_fonts.py:36`) keeps asking afresh by calling the uncached function, so an operator who has just installed a font package and runs the command gets the live answer, and the page shows the fresh answer from the next worker restart onwards. `script_draws` and `family_draws` (`documents/fonts.py:209-236`), used by tests to decide skips, would share the cached font map too, which speeds the test files that call them once per test. No library is needed. ## What it saves or protects small: one fontconfig load and 26 Pango font lookups per Overview visit become one per worker process; not measured (WeasyPrint's native libraries are absent on the development machine) ## Accessibility none ## Decisions - **Cache for the process, or for a fixed time?** Recommended: for the process; the image's workers recycle, and the command remains the fresh check. - **Cache `_theme_fonts()` too?** Recommended: yes, keyed on the installed themes, in the same change. --- Found by the efficiency and reliability audit of 2026-10-30 (finding P1; index postulo/postulo#1108). Checked against `main` at `daee30b15` (2026-10-30).
tiagoagueda added this to the 0.6.0 milestone 2026-10-31 03:04:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#1133
No description provided.