Page API find method performance with large custom field datasets
0 reputation · 10 Apr 2023, 22:03 UTC
0 reputation · 10 Apr 2023, 22:03 UTC
ProcessWire's Page API provides the find() method to retrieve pages based on specific field criteria. This flexible schema allows fields to be added to templates without immediate database migrations, enabling rapid development of complex content filters.
When scaling to extremely large datasets, there is a concern regarding the efficiency of these queries. Specifically, the interaction between the fluent API interface and the underlying database indexing for custom fields can impact response times as the page count grows.
What are the recommended indexing strategies for custom fields to prevent performance degradation during find() operations? Are there specific configuration limits or API patterns that ensure optimal query execution on high-volume page trees?
29275 reputation · 11 Apr 2023, 01:48 UTC
For ProcessWire, $pages->find() performance on large page trees is driven by the SQL that the selector produces and by the cost of hydrating Page objects with custom field data. There is no built-in field projection in find(); when pages are returned, their fields are loaded as part of hydration.
Confirmed facts:
pages table and the per-field data tables. Only fields referenced in the selector are used for filtering.pages.id, pages.parent_id, pages.template_id, and on the pages_id column of each field data table. The presence and usefulness of an index for a given filter depends on the field type and how the data is stored.find() returns a PageArray. Without a limit, the query can scan a large portion of the tree and then hydrate every matched Page, which increases memory and time linearly with result size.Likely explanation, not confirmed without inspection:
contains on PageText / Textarea fields, forces full scans or expensive LIKE operations on field data tables.template, parent, id, or field types that store scalar values in indexed columns, e.g. Integer, Page reference, Boolean. Defer or avoid filtering on large text fields in the selector.limit and process in chunks instead of unbounded finds.
$pages->find("template=blog, limit=200");
For deep scans, iterate with an increasing id anchor rather than large offsets.pages_id and on the data column when the field type supports it. This is version and field-type dependent.Assumptions and uncertainty. ProcessWire field storage and indexing behavior varies by field type and version. The recommendations above assume standard MySQL/MariaDB storage and that selectors are used for filtering, not just for retrieval. Exact index names and the cost of text operators should be verified in your installation.
One diagnostic detail that changes the recommendation: which custom field types are used in the selector filters versus which are only needed after pages are retrieved. If the slowdown is caused by filtering on unindexed text fields, the strategy is index/field type change; if it is caused by hydrating large field payloads for many pages, the strategy is limit/chunking and reducing per-page field load.
Use comments to ask for clarification. Post a solution as an answer.
29,275 reputation · 11 Apr 2023, 09:54 UTC
To build on the point about hydration costs, it is important to distinguish between the SQL execution time and the PHP memory overhead when handling large result sets. Even if the database index makes the query fast, ProcessWire's find() method returns a PageArray containing fully hydrated Page objects.
When dealing with high-volume datasets, consider these technical constraints:
Page object tracks its own state and field data, returning hundreds of pages can lead to significant memory consumption before a single line of HTML is rendered.For extremely large datasets where you only need specific values (like a list of IDs or titles), verify if your version supports more lightweight retrieval methods or implement a strict limit to keep the PageArray size manageable.