This document describes the direct SQL implementation for loading statistics from wiki replica databases, replacing the previous Pywikibot SupersetQuery approach.
The direct SQL implementation was created to address connection pool exhaustion issues when querying wiki replica databases. Following Zache's recommendation, this approach opens database connections only when needed and closes them immediately after use.
Important: Not all Wikimedia projects have the FlaggedRevs extension enabled. This statistics implementation only works with wikis that have FlaggedRevs tables in their replica databases.
These Wikipedias have been tested and confirmed to have FlaggedRevs enabled:
- ✅ Finnish Wikipedia (fi) - 169 FlaggedRevs statistics records
- ✅ German Wikipedia (de) - 172 FlaggedRevs statistics records
Based on MediaWiki documentation, these Wikipedias should also work (not yet tested):
- Polish Wikipedia (pl)
- Russian Wikipedia (ru)
- Czech Wikipedia (cs)
These Wikipedias do not have FlaggedRevs enabled and will return "Table doesn't exist" errors:
- ❌ English Wikipedia (en) - No FlaggedRevs
- ❌ Swedish Wikipedia (sv) - No FlaggedRevs
Before attempting to load statistics for a new wiki:
- Check the FlaggedRevs configuration page
- Or try loading a small dataset and check for errors:
TOOLFORGE_DEPLOYMENT=true python manage.py load_flaggedrevs_statistics_direct_sql --wiki <code>
- If you see
Table 'xxxwiki_p.flaggedrevs_statistics' doesn't exist, the wiki doesn't have FlaggedRevs
-
WikiReplicaConnection (
review_statistics/wiki_replica_connection.py)- Manages connections to wiki-specific replica databases
- Uses context manager pattern for automatic cleanup
- Connects to hosts like
fiwiki.analytics.db.svc.wikimedia.cloud
-
DirectSQLStatisticsClient (
review_statistics/direct_sql_services.py)- Provides high-level methods for fetching statistics
- Executes SQL queries against replica databases
- Returns structured data for Django models
-
Management Commands
load_flaggedrevs_statistics_direct_sql- Load monthly aggregatesload_review_statistics_direct_sql- Load individual review records
Wiki replica databases follow this naming pattern:
- Database name:
{wiki_code}{family}_p(e.g.,fiwiki_p) - Hostname:
{wiki_code}{family}.analytics.db.svc.wikimedia.cloud(e.g.,fiwiki.analytics.db.svc.wikimedia.cloud)
For Wikipedia projects, family is converted to wiki:
- Finnish Wikipedia:
fi+wiki=fiwiki_p - English Wiktionary:
en+wiktionary=enwiktionary_p
Monthly aggregates from the flaggedrevs_statistics table:
# Load statistics for Finnish Wikipedia
TOOLFORGE_DEPLOYMENT=true python manage.py load_flaggedrevs_statistics_direct_sql --wiki fi
# Load with specific date range
TOOLFORGE_DEPLOYMENT=true python manage.py load_flaggedrevs_statistics_direct_sql \
--wiki fi \
--start-date 2020-01-01 \
--end-date 2024-12-31
# Full refresh (delete and reload)
TOOLFORGE_DEPLOYMENT=true python manage.py load_flaggedrevs_statistics_direct_sql \
--wiki fi \
--full-refresh
# Change resolution to daily or yearly
TOOLFORGE_DEPLOYMENT=true python manage.py load_flaggedrevs_statistics_direct_sql \
--wiki fi \
--resolution dailyAuto-continuation: If no date parameters are provided, the command automatically continues from the last loaded month.
Detailed review records from the logging table:
# Load 10,000 records
TOOLFORGE_DEPLOYMENT=true python manage.py load_review_statistics_direct_sql \
--wiki fi \
--limit 10000
# Load 50,000 records
TOOLFORGE_DEPLOYMENT=true python manage.py load_review_statistics_direct_sql \
--wiki fi \
--limit 50000
# Clear existing data and reload
TOOLFORGE_DEPLOYMENT=true python manage.py load_review_statistics_direct_sql \
--wiki fi \
--limit 10000 \
--clearIncremental loading: The command tracks max_log_id and automatically continues from where it left off.
Monthly aggregate statistics:
total_pages_ns0- Total articles in main namespacesynced_pages_ns0- Articles reviewed to current revisionreviewed_pages_ns0- Articles with at least one reviewed revisionpending_lag_average- Average time articles wait for reviewpending_changes- Calculated as reviewedPages - syncedPages
Monthly reviewer activity:
number_of_reviewers- Unique reviewersnumber_of_reviews- Total reviewsnumber_of_pages- Pages reviewedreviews_per_reviewer- Average reviews per reviewer
Individual review records:
reviewer_name- Who performed the reviewreviewed_user_name- Whose edit was reviewedpage_title- Article namereviewed_timestamp- When review occurredpending_timestamp- When edit was madereview_delay_days- Days between edit and review
Aggregates data from the flaggedrevs_statistics table:
SELECT
FLOOR(d/100) as yearmonth,
AVG(totalPages_ns0) AS totalPages_ns0_avg,
AVG(syncedPages_ns0) AS syncedPages_ns0_avg,
AVG(reviewedPages_ns0) AS reviewedPages_ns0_avg,
AVG(pendingLag_average) AS pendingLag_average_avg
FROM (
SELECT
total_ns0.d,
totalPages_ns0,
syncedPages_ns0,
reviewedPages_ns0,
pendingLag_average
FROM
(
SELECT
floor(frs_timestamp/1000000) as d,
AVG(frs_stat_val) AS totalPages_ns0
FROM flaggedrevs_statistics
WHERE frs_stat_key = "totalPages-NS:0"
GROUP BY d
) AS total_ns0
LEFT JOIN ...
) as t
GROUP BY yearmonth
ORDER BY yearmonthQueries the flaggedrevs table for reviewer activity:
SELECT
FLOOR(d/100) as yearmonth,
AVG(number_of_reviewers) AS number_of_reviewers_avg,
AVG(number_of_reviews) AS number_of_reviews_avg,
AVG(number_of_pages) AS number_of_pages_avg
FROM (
SELECT
FLOOR(fr_timestamp/1000000) AS d,
COUNT(DISTINCT(fr_user)) AS number_of_reviewers,
SUM(1) AS number_of_reviews,
COUNT(DISTINCT(fr_page_id)) AS number_of_pages
FROM flaggedrevs
WHERE fr_flags NOT LIKE "%auto%"
AND fr_timestamp >= {start_date}
GROUP BY d
) as t
GROUP BY yearmonth
ORDER BY yearmonthFetches individual review records with delay calculations:
SELECT
l.log_id,
l.log_page AS page_id,
l.log_title AS page_title,
l.log_user_name AS reviewer_name,
a2.actor_name AS reviewed_user_name,
l.reviewed_revision_id,
r.rev_id AS pending_revision_id,
l.log_timestamp AS reviewed_timestamp,
r.rev_timestamp AS pending_timestamp,
TIMESTAMPDIFF(DAY, r.rev_timestamp, l.log_timestamp) AS review_delay_days
FROM (
SELECT
log_id,
log_page,
log_title,
log_timestamp,
a1.actor_name AS log_user_name,
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(log_params, 'i:0;i:', -1), ';', 1) AS UNSIGNED) AS reviewed_revision_id,
CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(log_params, 'i:1;i:', -1), ';', 1) AS UNSIGNED) AS extracted_id
FROM logging AS lg
JOIN actor_logging AS a1 ON lg.log_actor = a1.actor_id
WHERE lg.log_namespace = 0
AND lg.log_type = 'review'
AND lg.log_action IN ('approve', 'approve2')
ORDER BY lg.log_id ASC
LIMIT {limit}
) AS l
INNER JOIN flaggedrevs AS fr ON fr.fr_rev_id = l.reviewed_revision_id
JOIN revision AS r ON r.rev_page = l.log_page
AND r.rev_id = (
SELECT r2.rev_id FROM revision AS r2
WHERE r2.rev_page = l.log_page AND r2.rev_id > l.extracted_id
ORDER BY r2.rev_id ASC LIMIT 1
)
JOIN actor_revision AS a2 ON a2.actor_id = r.rev_actor
ORDER BY l.log_id ASCConnections are managed using Python's context manager protocol:
with connection_manager.get_connection() as conn:
cursor = conn.cursor()
cursor.execute(sql)
results = cursor.fetchall()
# Connection automatically closed hereConnections use the ~/replica.my.cnf file for authentication, which should already exist on Toolforge.
The implementation includes comprehensive error handling:
- DNS resolution errors (invalid hostname)
- Connection timeouts
- Query execution failures
- Data parsing errors
The /api/wikis/<pk>/statistics/refresh/ endpoint uses direct SQL:
@csrf_exempt
@require_http_methods(["POST"])
def api_statistics_refresh(request: HttpRequest, pk: int) -> JsonResponse:
"""Incrementally refresh review statistics using direct SQL."""
# Get metadata for incremental loading
metadata, _ = ReviewStatisticsMetadata.objects.get_or_create(wiki=wiki)
# Fetch new records (limit to 10k per refresh)
sql_client = get_direct_sql_client(wiki)
payload = sql_client.fetch_review_statistics_from_logging(
limit=10000,
min_log_id=metadata.max_log_id,
)
# Process and save records
# Update metadata
# Return JSON responseError: Name or service not known
Cause: Incorrect hostname construction
Solution: Ensure hostname includes family:
- ❌ Wrong:
fi.analytics.db.svc.wikimedia.cloud - ✅ Correct:
fiwiki.analytics.db.svc.wikimedia.cloud
Error: Access denied for user 's57224'@'%' to database 's57230__pendingchangesbot'
Cause: Incorrect database name in settings
Solution: Verify database name matches your Toolforge tool account:
TOOLSDB_NAME = os.environ.get("TOOLSDB_NAME", "s57224__pendingchangesbot")Error: Table 'xxxwiki_p.flaggedrevs_statistics' doesn't exist or Table 'xxxwiki_p.flaggedrevs' doesn't exist
Cause: The wiki you're trying to load doesn't have the FlaggedRevs extension enabled
Solution:
- Check the Supported Wikis section at the top of this document
- Only use wikis that are confirmed to have FlaggedRevs (fi, de, pl, ru, cs)
- Avoid wikis like English (en) and Swedish (sv) Wikipedia which don't have FlaggedRevs
Example:
# ✅ This works - Finnish has FlaggedRevs
TOOLFORGE_DEPLOYMENT=true python manage.py load_flaggedrevs_statistics_direct_sql --wiki fi
# ❌ This fails - Swedish doesn't have FlaggedRevs
TOOLFORGE_DEPLOYMENT=true python manage.py load_flaggedrevs_statistics_direct_sql --wiki svPossible causes:
- No data exists in the specified date range
- Wiki replica database is empty (test wiki)
- SQL query filters are too restrictive
Debug steps:
- Check logs:
tail -100 ~/uwsgi.log - Verify connection: Test with simple query
- Check date filters in management command
Direct SQL avoids connection pool exhaustion by:
- Opening connections only when needed
- Closing immediately after use
- Not maintaining persistent connections
Queries are optimized for:
- Indexed column filtering (
log_id,fr_timestamp) - Limited result sets (LIMIT clause)
- Efficient JOINs on primary keys
Both commands support incremental loading:
- FlaggedRevs: Auto-continues from last month
- Reviews: Uses
max_log_idfor pagination
This allows loading large datasets in manageable chunks.
If migrating from the old Pywikibot approach:
- Stop using
StatisticsClientfromreviews.services.wiki_client - Start using
get_direct_sql_client()fromreview_statistics.direct_sql_services - Replace
client.fetch_review_statistics()with management commands - Update refresh endpoints to use direct SQL methods
Benefits:
- No connection pool exhaustion
- Faster query execution
- Better error handling
- No Pywikibot authentication required
Potential improvements:
- Add support for more wikis beyond Wikipedia
- Implement parallel loading for multiple wikis
- Add data validation and quality checks
- Create admin interface for managing loads
- Add monitoring and alerting for failed loads
- Support for custom date ranges in API