Introduction
MediaWiki is a capable platform, but when used in high-traffic environments or enterprise deployments, performance bottlenecks can appear quickly, especially when extensions are involved. Many developers write extensions that work well locally or on small sites, only to find that under real-world usage, these same extensions cause slow pages, increased memory consumption, and excessive load on the database.
This article addresses the specific challenges of scaling MediaWiki extensions. We will cover how to identify performance problems, techniques for writing efficient code, and best practices for integrating with MediaWiki’s caching, job queue, and database layers. By the end, you will understand how to write extensions that scale horizontally and handle thousands of requests per minute without failing under pressure.
Profiling and Benchmarking
Before optimizing anything, you must know where the bottlenecks lie. MediaWiki includes a built-in profiling system. Set \$wgDebugToolbar = true
and \$wgDebugProfiling = true
in LocalSettings.php
to view detailed breakdowns of execution time, memory usage, and database queries in the debug toolbar.
For finer-grained measurement, insert profiling markers in your code using:
Profiler::instance()->scopedProfileIn( __METHOD__ );
Use these markers in hooks or controller methods to see how your custom logic affects overall performance. Avoid measuring performance based on load times in the browser alone. Backend performance must be tracked using logs and proper instrumentation.
Avoiding Expensive Queries
Database performance is often the primary constraint. A common anti-pattern is looping over large result sets or joining MediaWiki core tables in complex ways. Use MediaWiki’s SelectQueryBuilder
instead of raw SQL whenever possible. It provides safer abstraction and better integration with replication-aware read and write connections.
Instead of:
$dbr->query( "SELECT * FROM page WHERE page_namespace = 0" );
Use:
$res = MediaWikiServices::getInstance()->getDBLoadBalancer()->getConnection( DB_REPLICA )->newSelectQueryBuilder()->select( [ 'page_id', 'page_title' ] )->from( 'page' )->where( [ 'page_namespace' => 0 ] )->caller( __METHOD__ )->fetchResultSet();
Always benchmark the execution plan of queries using EXPLAIN
in the MySQL shell or MediaWiki’s query profiler. Avoid full table scans, unindexed fields, or frequent queries to volatile data.
Using ObjectCache
MediaWiki provides a powerful caching layer abstracted through ObjectCache
. Instead of recomputing results or querying the database, store expensive results in cache and reuse them when needed. Use WANObjectCache
to ensure consistency across multiple web servers and request contexts.
Example:
$cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
$key = $cache->makeKey( 'myextension', 'expensive-result', $user->getId() );
$data = $cache->getWithSetCallback( $key, 3600, function () use ( $user ) { return computeExpensiveResult( $user ); } );
This pattern caches the result for an hour and recomputes only if needed. Use it for rendering large tables, user-specific dashboards, or API responses.
Avoid using APCBagOStuff
or HashBagOStuff
in production. These are in-memory caches that do not scale across servers. Rely instead on RedisBagOStuff
or MemcachedBagOStuff
.
JobQueue for Deferred Processing
When a user action triggers something expensive, such as sending emails, updating logs, or writing to external APIs, avoid doing it synchronously. Instead, use MediaWiki’s JobQueue. Define a custom job by extending Job
and register it in your extension.
Example:
class MyExpensiveJob extends Job { public function run() { expensiveWork(); return true; } }
Queue the job with:
JobQueueGroup::singleton()->push( new MyExpensiveJob( $title, [ 'user' => $user->getId() ] ) );
Jobs run asynchronously via runJobs.php
or Redis queues. Monitor the job backlog and error logs. If your job fails silently or throws exceptions, they will appear in jobqueue.log
.
File and Output Caching
For static or semi-static content, consider using file-based output caching. Store generated HTML fragments in the file system or a persistent cache and serve them directly to improve performance. This is especially useful for infoboxes, timelines, or statistical dashboards that only change occasionally.
Use MediaWiki’s OutputPage::addHTML
and OutputPage::enableClientCache
to fine-tune how your extension affects the page cache. Avoid disabling caching globally unless absolutely necessary. Integrate with ParserCache
if your extension outputs parser content.
If your extension is modifying content that is cached, like templates or categories, use the correct parser options and cache keys. Otherwise, MediaWiki may serve stale or incorrect content.
Limiting API Load
If your extension exposes custom API endpoints, ensure they are fast and not easily abused. MediaWiki provides throttling hooks and rate limiting via $wgRateLimits
. Avoid exposing raw database results or non-paginated content.
Paginate results with continuation parameters:
$this->setContinueEnumParameter( 'offset', $newOffset ); $this->getResult()->addValue( null, 'continue', [ 'offset' => $newOffset ] );
For expensive calculations, defer to jobs or cache the output. Consider introducing an async=true
flag for large responses so that clients poll for results later.
Lazy Loading and ResourceLoader Optimization
Extensions that ship JavaScript and CSS should use ResourceLoader properly. Avoid loading scripts on every page if they are only used on special pages or specific namespaces. Register modules conditionally in hooks like BeforePageDisplay
.
Example:
if ( $title->inNamespace( NS_SPECIAL ) ) { $out->addModules( 'ext.myextension.special' ); }
Bundle small scripts together but avoid overly large payloads. Use targets
to scope modules to desktop or mobile. For dynamic interactions, use mw.loader.using
to lazy-load modules only when needed.
Minimize expensive client-side DOM manipulation. If your extension relies on AJAX or dynamic content, ensure that your JavaScript only runs when the relevant elements are present.
Horizontal Scaling Considerations
If your MediaWiki instance runs on multiple servers or in a Kubernetes cluster, ensure your extension does not rely on file system writes or session data stored locally. Use shared storage via Swift or S3 for uploads, and manage sessions with Redis or Memcached.
For logging or debugging, do not write to local tmp
or logs
directories. Instead, use MediaWiki’s LoggerFactory
to write logs to centralized systems like syslog, ELK stack, or structured JSON logs.
Example:
$logger = LoggerFactory::getInstance( 'MyExtension' ); $logger->info( 'User submitted form', [ 'user' => $user->getName() ] );
This ensures logs are aggregated and searchable without depending on local file access.
Deployment Best Practices
Avoid deploying large changes or new extensions directly to a production wiki. Use a staging environment and run MediaWiki’s test suite, especially if you modify existing hooks or services. Use phpunit
to run extension-specific tests.
For schema changes, always follow the migration pattern:
Add nullable fields
Backfill data using maintenance scripts
Make schema required only after validation
Coordinate database updates with DBAs if using replication or shared clusters.
Use deployment tools like Scap
or Helm
for MediaWiki deployments. Ensure your extension uses proper versioning and has rollback capability.
Monitoring and Alerting
After deployment, monitor your extension using logs, performance dashboards, and metrics. Integrate with Prometheus or StatsD to emit metrics like request time, cache hit rate, or job failures.
Add profiling hooks in key execution paths:
wfProfileIn( __METHOD__ ); // Do work wfProfileOut( __METHOD__ );
Track user-reported errors in a structured way using MediaWiki’s LoggerFactory
or third-party tools like Sentry. Do not rely on browser console logs or guesswork.
Summary
Scaling MediaWiki extensions is not just about writing code that works. It requires thoughtful design, attention to performance, and deep integration with MediaWiki's infrastructure. Avoid expensive operations, defer work to jobs, use caching layers effectively, and always test under real-world load conditions.
The techniques covered here from internal caching to API optimization and asynchronous job processing form the backbone of reliable and scalable extension development. They will allow your extension to perform under the demands of enterprise wikis, multi-user portals, and global deployments.
If you want to go deeper and learn how to build and maintain advanced MediaWiki extensions with confidence, download Mastering MediaWiki Extensions: Beyond the Manual. This guide is packed with proven solutions, patterns, and architecture insights not found in the official documentation.
Top comments (0)