The front page of atlasmuseum.net as the Internet Archive captured it on 28 September 2022, next to the same page as the wiki shows it on 11 September 2026. A seam runs between the two; whoever moves it sees the commands, queries and log lines of the migration.
1.26.4 to 1.39.10
- PHP 7.4 to 8.3, MySQL 5.7 to 8.0
- 43 extensions, each on PHP 8
- database rescued, not rebuilt
- 26 to 64 times faster on the measured pages
The lines in the seam, from the delivery document and the log files:
- MediaWiki: 1.26.4
- PHP: 7.4
- MySQL: 5.7
- am_archive.ar_rev_id: 149315 x 8000+ (duplicate)
- am_revision: index rev_timestamp missing (error 1176)
- update.php: a username was usable and an anonymous actor could not be created
- $ php maintenance/update.php
- $ php maintenance/cleanupUsersWithNoId.php --assign --prefix legacy --force
- $ php maintenance/cleanupUsersWithNoId.php --assign --prefix legacy --table revision --force
- $ php maintenance/cleanupUsersWithNoId.php --assign --prefix legacy --table oldimage --force
- $ php maintenance/deduplicateArchiveRevId.php --force
- SET @start := GREATEST(
- (SELECT IFNULL(MAX(rev_id),0) FROM am_revision),
- (SELECT IFNULL(MAX(ar_rev_id),0) FROM am_archive)
- ) + 1;
- SET @bad := 149315;
- UPDATE am_archive a JOIN tmp_dups t ON t.ar_id = a.ar_id
- SET a.ar_rev_id = IF((@i := @i + 1) = 1, a.ar_rev_id, (@n := @n + 1));
- $ php maintenance/sql.php --query "CREATE INDEX rev_timestamp ON am_revision (rev_timestamp);"
- $ php maintenance/populateContentTables.php
- INSERT IGNORE INTO am_updatelog (ul_key, ul_value) VALUES ('patch-change_tag-change_tag_rc_tag_id.sql','applied');
- $ php maintenance/rebuildrecentchanges.php
- $ php maintenance/runJobs.php --maxjobs 500
- $ mysqldump wiki --single-transaction --routines --triggers > wiki_after_mw139.sql
- $ php extensions/SemanticMediaWiki/maintenance/rebuildData.php
- $ php extensions/SemanticMediaWiki/maintenance/updateEntityCollation.php
- $ php maintenance/rebuildLocalisationCache.php --force
- $ MW_NO_EXTENSION_HOOKS=1 php maintenance/importImages.php --overwrite --search-recursively images_clean
- $ php maintenance/rebuildImages.php
- $ php extensions/AtlasQualite/maintenance/rebuildQualityIndex.php
- $ php extensions/AtlasRepair/maintenance/repairFromWikidata.php
- $ php extensions/AtlasRepair/maintenance/repairFromWikidata.php --apply
- "manifest_version": 2,
- "requires": { "MediaWiki": ">= 1.39.0" },
- "AutoloadNamespaces": { "WikidataBundle\\": "includes/" },
- execute(): signature aligned with SpecialPage in 1.39
- trim(null) -> trim((string) $value)
- str_replace on null -> coalesced to empty string
- string concatenation: + replaced by .
- duplicate class definitions removed, autoloader regenerated
- Semantic MediaWiki: 5.1.0
- MediaWiki: 1.39.10
- Storage engine: SMW\SQLStore\SQLStore
- ... checking outdated entities ... ok
- ... checking invalid entities by namespace ... ok
- ... refreshed (IDs) 51277
- ... skipped (IDs) 0
- finished: 2026-09-09 02:03:28 CEST exit=0
- [10:05:37] Map rebuilt: 42713 points written to am_map in 1771 ms.
- PHP 7.4 -> 8.1 -> 8.3
- MySQL 5.7 -> 8.0
- MediaWiki 1.26.4 -> 1.31 -> 1.35 -> 1.39.10
- repeated page load: 26 to 64 times faster
- memoisation within one render + 30 minute disk cache
- loopback removed: the server no longer called its own web API
2025 to 2026 · MediaWiki · A-Pack, France
The job nobody wanted
In October 2025 I was called about a site that was broken. Atlasmuseum is a public atlas of public art in France, run by the association A-Pack for the French Ministry of Culture.
Prove it first, quote afterwards
The live site had been compromised by pirate accounts and was running MediaWiki 1.26.4, released in 2015. A previous migration attempt had been abandoned halfway and left the database with mixed and missing tables.
More than one party had looked at it and said it could not be saved, and that starting again would be the sensible thing.
I did not say yes or no straight away. From the client’s database dump I rebuilt the whole site in a fully offline environment on my own machine, with no connection to the live site and therefore no risk. Only once that test showed the content could be saved did a plan and a quote follow.
That is how I work. I do not sell certainty I do not have yet.
The work was in the database
The path ran from MediaWiki 1.26.4 to 1.31, to 1.35, to 1.39.10, with MySQL from 5.7 to 8.0 and PHP from 7.4 through 8.1 to 8.3. But the version numbers are not the work.
Over 8,000 duplicate archive revisions renumbered, on revision 149315 alone. A missing revision timestamp index restored. The actor model migration completed. The Multi Content Revision tables, the logging, the change tags, the file archive and the user talk pages repaired one by one.
Tens of thousands of spam pages removed, with the deletion list reviewed three times before anything went, because you do not get back what you throw away by mistake.
Then the content. On the order of 100,000 semantic entities regenerated. Over 4,200 images re-imported with their metadata rebuilt, after the media backend turned out to be hanging off the wrong tables.
A rescued site that is slow is only half rescued
Once all of that stood, the site was still slow. Repeat page loads ended up 26 to 64 times faster than the starting point, on the pages that were benchmarked.
That did not come from a trick. A two level cache around the upstream API and the SPARQL calls: memoisation within one page render, plus an on-disk cache that keeps for thirty minutes. Parser and page cache switched on, with a cache epoch to force a clean redraw without turning the cache off. Heavy galleries behind an IntersectionObserver.
And the removal of a construction where the server called its own web API. That one was slow, and under load it could exhaust the whole worker pool.
The third thing: six tools that did not exist
A migration is finished when the site runs. I did not think that was enough. I built six tools, and they all have the same purpose: that the platform keeps itself clean once I am gone.
AtlasQualite scans the whole catalogue, about 4,600 artwork notices, and scores each one for completeness against the fields that matter. Image, coordinates, author, date, description, Wikidata link. The result is a prioritised worklist for the editors. You cannot improve what you do not measure.
AtlasRepair clears up what old imports left behind. The pattern is deliberate: first a dry run that changes nothing and writes a report, then an overview with every proposed change from current to corrected value, and only then an application with attribution and a rate limit. On the last dry run it flagged 1,343 records.
Wikidata Enrich fills empty fields in one click, and only empty fields, always with a preview before anything is saved. AMAPIStatus calls the self test and shows per endpoint whether it works. For a platform being handed to a public institution, being able to point at a page where everything is green is worth a great deal.
MapSync rebuilds the map automatically on every save or import, in process, transactionally, with its own lock so two rebuilds cannot collide. AtlasNova is the new skin: 1,478 lines against 3,527 in its predecessor. Less than half, for something that is more modern and easier to maintain.
Together I wrote over 27,000 lines of my own code, inside an installation of more than two million lines and 43 extensions that each had to come along to 1.39 and to PHP 8.3.
The rules I held myself to
This is the part a client can use. Every tool that writes is restricted to logged in editors. Nothing anonymous can start a change.
Bulk operations always run dry first and only write after a person has read the report. There is no path where a mass edit happens without someone confirming it. Every file I edited was backed up first with a timestamp, so each individual change can be rolled back. Error display is off for visitors and on in the logs for the administrator.
And one design rule that carries the rest: my own API layer reads MediaWiki only through the stable action API and never directly from SQL. A future upgrade therefore cannot break the backend. That is exactly what went wrong in the installation I inherited.
What is still open
The project document also keeps track of what is not finished. The map table holds 988 rows where 4,537 have been in it, because a batch that timed out was once read as no more results. The guard against that truncation is in place now, so a rebuild is safe. And the coordinate property is an ordinary page property rather than a geographic one, so the radius search over our own data returns nothing.
I delivered a sixty page runbook with it, containing the full error catalogue and the open points. Not because I had to, but so the next developer does not have to work out again what I already know.