Keeps the global "X links, Y clicks" counts on the YOURLS admin dashboard exact and cheap on large installs.
On a big table, the dashboard query
SELECT COUNT(keyword), SUM(clicks) FROM yourls_url scans the whole table and
can time out. A plain cache is not enough: every link add or delete would force
a fresh scan, which times out again (and a timed-out scan caches nothing, so the
dashboard stays broken on every reload).
This plugin avoids the rescan entirely for link add/delete, and bounds the only remaining scan.
- YOURLS 1.10.4 or later (all SQL fetches are filterable via
shunt_fetch_wrapper). - Optionally, a covering index on
clicks(see the trade-off below). Off by default on purpose: it speeds up an occasional admin scan but taxes every redirect.
- Copy the
high-volume-dashboard-statsfolder intouser/plugins/. - Activate High Volume Dashboard Stats in the admin plugins page.
- On large tables, add the covering index (see below).
The two numbers behave differently, so they are handled differently:
- Link count - changes only on add/delete. It is kept as an exact counter,
adjusted by +1 / -1 on the
insert_link/delete_linkactions. Adding or removing a link therefore never scans the table; the headline count updates instantly. - Click sum - grows on every redirect. It is kept as a snapshot, refreshed
by a full scan at most once per
HVDS_CACHE_TTL. Link add/delete do not trigger this scan: a new link has 0 clicks (sum unchanged), and a deleted link's clicks self-heal at the next refresh.
The global query is intercepted at the fetch level (shunt_fetch_wrapper),
matching the exact COUNT()/SUM() statement with the table name resolved at
runtime. On a fresh window the maintained values are returned and the query
never runs. When the window has elapsed, one scan refreshes the click sum and
reseeds the exact link count (healing any drift, eg after a raw-SQL bulk import).
Everything else - redirect lookups, filtered/search stats, perform() calls -
is passed through untouched, and the get_db_stats filter still runs so other
plugins keep working.
Add and delete only touch a counter in the options table. They never run the
COUNT()/SUM() query, so they complete instantly regardless of table size.
With this plugin the link count is maintained incrementally and never scans the
table, so the only remaining scan is the periodic click-sum refresh, at most once
per HVDS_CACHE_TTL and only when the dashboard is viewed. A covering index
speeds that scan up:
ALTER TABLE yourls_url ADD INDEX idx_clicks (clicks);In InnoDB this index implicitly includes the primary key (keyword), so both
COUNT(keyword) and SUM(clicks) are satisfied from it in a single narrow pass.
But it has a permanent cost on the hot path: every redirect runs
UPDATE ... SET clicks = clicks + 1, and with the index in place each increment
must also move the row's entry in the idx_clicks B-tree (remove the old value,
insert the new one). That is an extra secondary-index write on the busiest query
of the install, forever, to speed up a scan that runs at most once per window.
The trade-off is redirect volume vs dashboard views:
- Redirects vastly outnumber dashboard views, so paying every redirect to speed up a rare scan is usually the wrong direction. Default: no index.
- With logging on (the default) each redirect already inserts a log row and
maintains its index, so
idx_clicksonly adds incrementally. With logging off (YOURLS_NOSTATS) the redirect does just the clicks UPDATE, so the index weighs proportionally more. - Add the index only if the periodic sum scan is genuinely too slow for your table size and your redirect volume is low enough to absorb the write cost. Run the ALTER from phpMyAdmin or the SQL CLI, off-peak (online on MariaDB 10 / MySQL 5.6+, but copies the table under a lock on MySQL 5.5).
If the sum scan is a problem but you don't want the index, you can instead
maintain the sum incrementally by hooking update_clicks. That removes the scan
entirely, but replaces the index's distributed writes with a single shared
counter row updated on every redirect, which serializes under high concurrency.
At high redirect rates neither is ideal, and the off-hot-path periodic scan is
the least-bad option.
The click-sum refresh window defaults to 300 seconds. Override in the plugin or
in config.php:
define( 'HVDS_CACHE_TTL', 3600 ); // seconds