Found a good excuse to run some queries before I forget everything…
Date: 2021-12-01
client |
cms |
origins |
pct_good_ttfb |
pct_good_lcp |
mobile |
WordPress |
2022166 |
5.71% |
30.64% |
mobile |
Drupal |
110077 |
12.41% |
53.12% |
Looking at the above table, apart from the big difference in number origins, the percentage of origins with a good TTFB on Drupal is more than double that on WP, so TTFB definitely plays a role.
client |
cms |
percentile |
total |
TTFB |
mobile |
Drupal |
50 |
133035 |
2173 |
mobile |
Drupal |
75 |
133035 |
2728 |
mobile |
WordPress |
50 |
2659697 |
2469 |
mobile |
WordPress |
75 |
2659697 |
3360 |
When checking the data from WebPageTest (CWV come from CrUX), the difference in TTFB isn’t disproportionately large at the 75th percentile. Not sure how you could make better use this TTFB data though.
client |
cms |
percentile |
size |
mobile |
Drupal |
50 |
63037 |
mobile |
Drupal |
75 |
97200 |
mobile |
WordPress |
50 |
58200 |
mobile |
WordPress |
75 |
81840 |
If my query is correct, when comparing the intrinsic size of the LCP elements (only taking <img>
elements into consideration), the median size on Drupal and WordPress is similar (slightly larger on Drupal actually). I haven’t checked download size in KBs (query is a bit tedious) so perhaps WP images are not compressed.
My guess is similar to patmeenan’s and the issue stems from TTFB.
Generated using the below queries
CREATE TEMP FUNCTION IS_GOOD (good FLOAT64, needs_improvement FLOAT64, poor FLOAT64) RETURNS BOOL AS (
good / (good + needs_improvement + poor) >= 0.75
);
CREATE TEMP FUNCTION IS_NON_ZERO (good FLOAT64, needs_improvement FLOAT64, poor FLOAT64) RETURNS BOOL AS (
good + needs_improvement + poor > 0
);
SELECT
client,
cms,
COUNT(DISTINCT origin) AS origins,
# Origins with good TTFB divided by origins with any TTFB.
SAFE_DIVIDE(
COUNT(DISTINCT IF(IS_GOOD(fast_ttfb, avg_ttfb, slow_ttfb), origin, NULL)),
COUNT(DISTINCT IF(IS_NON_ZERO(fast_ttfb, avg_ttfb, slow_ttfb), origin, NULL))) AS pct_good_ttfb,
# Origins with good LCP divided by origins with any LCP.
SAFE_DIVIDE(
COUNT(DISTINCT IF(IS_GOOD(fast_lcp, avg_lcp, slow_lcp), origin, NULL)),
COUNT(DISTINCT IF(IS_NON_ZERO(fast_lcp, avg_lcp, slow_lcp), origin, NULL))) AS pct_good_lcp,
FROM
`chrome-ux-report.materialized.device_summary`
JOIN (
SELECT
_TABLE_SUFFIX AS client,
url,
app AS cms
FROM
`httparchive.technologies.2021_12_01_*`
WHERE
category = 'CMS'
AND (
app = 'Drupal' OR app = 'WordPress'
)
)
ON
CONCAT(origin, '/') = url AND
IF(device = 'desktop', 'desktop', 'mobile') = client
WHERE
date = '2021-12-01'
GROUP BY
client,
cms
ORDER BY
origins DESC
WITH
lcp_stats AS (
SELECT
_TABLE_SUFFIX AS client,
url,
JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].nodeName") AS nodeName,
JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].url") AS elementUrl,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].size") AS INT64) AS size,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].loadTime") AS FLOAT64) AS loadTime,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].startTime") AS FLOAT64) AS startTime,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].renderTime") AS FLOAT64) AS renderTime,
JSON_EXTRACT(payload, "$._performance.lcp_elem_stats[0].attributes") AS attributes,
FROM
`httparchive.pages.2021_12_01_*`
)
SELECT
client,
nodeName,
COUNT(DISTINCT url) AS pages,
ANY_VALUE(total) AS total,
COUNT(DISTINCT url) / ANY_VALUE(total) AS pct,
COUNTIF(elementUrl != "") AS haveImages,
COUNTIF(elementUrl != "") / COUNT(DISTINCT url) AS pct_haveImages
FROM
lcp_stats
JOIN (
SELECT
_TABLE_SUFFIX AS client,
cms,
COUNT(0) AS total
FROM
`httparchive.summary_pages.2021_12_01_*`
JOIN (
SELECT
_TABLE_SUFFIX AS _client,
url AS _url,
app AS cms
FROM
`httparchive.technologies.2021_12_01_*`
WHERE
category = 'CMS'
AND (
app = 'Drupal' OR app = 'WordPress'
)
)
ON
(_url = url AND _client = _TABLE_SUFFIX)
GROUP BY
client,
cms
)
USING
(client)
GROUP BY
client,
cms,
nodeName
HAVING
pages > 1000
ORDER BY
client,
pct DESC
WITH
lcp_stats AS (
SELECT
_TABLE_SUFFIX AS client,
url,
JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].nodeName") AS nodeName,
JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].url") AS elementUrl,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].size") AS INT64) AS size,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].loadTime") AS FLOAT64) AS loadTime,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].startTime") AS FLOAT64) AS startTime,
CAST(JSON_EXTRACT_SCALAR(payload, "$._performance.lcp_elem_stats[0].renderTime") AS FLOAT64) AS renderTime,
JSON_EXTRACT(payload, "$._performance.lcp_elem_stats[0].attributes") AS attributes,
FROM
`httparchive.pages.2021_12_01_*`
)
SELECT
_TABLE_SUFFIX AS client,
app AS cms,
percentile,
APPROX_QUANTILES(size, 1000)[OFFSET(percentile * 10)] AS size,
FROM
`httparchive.technologies.2021_12_01_*` X,
UNNEST (
[10, 25, 50, 75, 90]
) AS percentile
JOIN
lcp_stats
ON
(lcp_stats.url = X.url AND lcp_stats.client = _TABLE_SUFFIX)
WHERE
category = 'CMS'
AND (
app = 'Drupal' OR app = 'WordPress'
)
AND
nodeName = "IMG"
GROUP BY
client,
cms,
percentile
ORDER BY
client,
cms,
percentile