— Artikel — № 058

058 —Magento

Magento 1.9 naar Magento 2: zes weekenden live cutover

Een Nederlands bureau moest een Magento 1.9-winkel naar 2.4.7 verhuizen zonder de catalogus te bevriezen. Dit is het zes-weekenden-plan dat hield.

Bovenaanzicht van papieren cutover-kalender, Magento-migratieplan, messing plaatje, lakzegel, liniaal en potlood op linnen.
Hero · gestileerd stilleven№ 058

De Loom van vrijdagavond kwam binnen om 23:41. Een bureau van 14 man in Utrecht draait een Magento 1.9.4.5-winkel voor een groothandel in verf, en had negentig dagen voordat hun PSP geen PHP 7.2-requests meer zou signen en Adobe de security-patches niet langer zou backporten. De winkel zette ongeveer €2,3 miljoen per jaar om. Catalogusupdates draaiden elke werkochtend vanuit een ERP-feed. De catalogus bevriezen voor een lang weekend zat er niet in, en een rebuild rond Black Friday al helemaal niet. We hadden zes weekenden om een live Magento 1.9-winkel naar Magento 2.4.7 te migreren zonder één bestelling of URL-rank te verliezen.

Dit is het rebuild-plan dat daadwerkelijk hield. Het Magento 2-migratieplaybook hieronder gaat ervan uit dat je werkende back-ups hebt, een kopie van de productiedatabase die je wekelijks kunt verversen, en minstens één engineer die de officiële documentatie van de Adobe Commerce data migration tool helemaal heeft gelezen zonder iets over te slaan.

De constraint die een catalogusbevriezing onmogelijk maakte

De klassieke Magento 1 naar Magento 2-migratietutorial gaat uit van een freeze-window: writes op de bron stoppen, de data migration tool draaien, valideren, DNS omzetten, weer openzetten. Prima voor een shop met 200 SKU's en wekelijkse catalogusbewerkingen. Onmogelijk voor een groothandel met 14.000 SKU's, een ERP die elke ochtend prijzen pusht en een B2B-portaal dat om 23:30 nog orders van €8k binnenneemt.

De data migration tool levert daarvoor een delta-mode. Na de initiële bulkmigratie volgt hij specifieke tabellen (sales_order, customer_entity, catalog_product_entity en nog een paar dozijn) via logtabellen en triggers die op de bron worden geïnstalleerd. Zolang de Magento 2-instantie aan de doelzijde bijblijft met de delta's, kun je in een DNS-swap van vijftien minuten omzetten in plaats van een freeze van twee dagen.

Het lastige is niet het draaien van de tool. Het lastige is de delta-loop zes weken gezond houden terwijl developers tegelijkertijd thema's herbouwen, extensies vervangen en de integratie-shims schrijven die de oude Magento 1-frontend uit gewoonte aan elkaar plakte.

Weekend 1: shadow-infrastructuur en de eerste bulk dump

Het eerste weekend was puur infrastructuur. We zetten een Hetzner-bak op, installeerden Magento 2.4.7 op PHP 8.2 met OpenSearch 2.12, MariaDB 10.6, Redis 7 en Varnish 7.4. We hingen die achter een staging-hostname met basic auth. Nog geen verkeer. Nog geen thema.

Daarna draaiden we de officiële data migration tool tegen een verse productie-dump. De bulkmigratie duurde 4u22m in de eerste pass en bracht de gebruikelijke rommel naar boven:

php bin/magento migrate:data \
  -r vendor/magento/data-migration-tool/etc/opensource-to-opensource/1.9.4.5/config.xml

Fouten in de eerste run zagen er zo uit:

[ERROR]: Source documents are not mapped: enterprise_logging_event
[ERROR]: Class "Vendor_Module_Helper_Data" not found
[ERROR]: Mismatch of entities in document: catalog_product_entity_varchar

Drie patronen om te kennen. De enterprise-tabellen doen er alleen toe als je Magento Enterprise gebruikte; in Open Source komen ze in ignore-blokken in map.xml. De Helper not-found-fouten komen van third-party M1-modules die EAV-attributen registreerden die de migration tool vervolgens probeert te type-mappen. Als je het attribuut niet nodig hebt op M2, haal het dan uit het bronschema vóór de volgende pass. De mismatch-fouten betekenen meestal dat een varchar-kolom op M1 door een extensie is verlengd en de tool wil dat je dat in het mapbestand bevestigt.

Zondagavond was de bulk-pass groen en bevatte de staging-instantie 14.103 producten, 28.440 klanten en 142.617 historische orders. Geen enkele jonger dan 48 uur. Daarvoor is de delta-loop bedoeld.

Weekend 2 en 3: de delta-loop draaiende houden

De delta-tool draait als een long-lived PHP-proces:

php bin/magento migrate:delta \
  -r vendor/magento/data-migration-tool/etc/opensource-to-opensource/1.9.4.5/config.xml

Wij draaiden hem in een tmux-sessie op de staging-bak, met een systemd-unit die het proces in de gaten hield en bij exit herstartte. De triggers die hij op de M1-bron installeert zijn niet triviaal. Kijk eens wat er wordt aangemaakt:

SHOW TRIGGERS LIKE 'sales_flat_order';

Je ziet dan trg_sales_flat_order_after_insert, _after_update, _after_delete, elk die de gewijzigde primary key wegschrijft naar een m2_cl_*-logtabel. Het delta-proces polt die logtabellen elke minuut en speelt de wijzigingen af op M2. Gaat de bron offline voor onderhoud en lopen de logtabellen vol, dan haalt het delta-proces de achterstand in. Groeien de logtabellen ongebreideld omdat het delta-proces dood is, dan kom je er op het slechtst mogelijke moment achter.

We leerden te alerten op twee metrics: rijaantal van een willekeurige m2_cl_*-tabel boven de 5.000, en tijd-sinds-laatste-poll boven de 180 seconden. Allebei in hetzelfde Grafana-board dat het ops-team toch al openstond.

In weekend 3 herbouwde de tweede engineer het thema. We hebben het RWD-thema van M1 niet geport. Doe dat niet. We zijn vanuit Luma begonnen, hebben de merk-tokens overgezet en de vier templates herbouwd die ertoe doen: PDP, PLP, cart en checkout. De rest erfde over.

Weekend 4: URL-rewrites en het SEO-grootboek

Verreweg de belangrijkste reden waarom Magento-migraties worden teruggedraaid, is URL-drift. De data migration tool kopieert je core_url_rewrite-rijen van M1 naar de M2-url_rewrite-tabel, maar het schema is niet één-op-één. M1 sloeg category- en product-rewrites apart op van CMS-rewrites. M2 trekt ze samen en voegt redirect_type toe.

We trokken de live sitemap uit Google Search Console, dedupliceerden tegen een verse crawl en bouwden een CSV met elke URL die in de laatste 90 dagen organisch was aangeklikt. 11.847 URL's. Daarna schreven we een kleine vergelijkingsquery tegen staging:

SELECT s.url AS source_url
FROM seo_ledger s
LEFT JOIN url_rewrite u
  ON u.request_path = TRIM(LEADING '/' FROM s.url)
WHERE u.url_rewrite_id IS NULL;

Dit gaf 312 URL's terug die in productieverkeer bestonden maar geen rewrite op M2 hadden. Ongeveer 200 waren CMS-pagina's gebouwd met een afgedankte M1-paginabouwer. De rest waren category-landingspagina's met custom URL-keys die jaren geleden in de admin waren gezet en nooit zijn doorgevoerd toen SKU's opnieuw werden getagd.

Voor elk schreven we een permanente redirect in url_rewrite met redirect_type = 301, naar de dichtstbijzijnde live pagina. De site verloor in de vier weken na de cutover nul rankingposities. Die ene zaterdag van grep en SQL was waarschijnlijk twee maanden post-launch SEO-werk waard.

Weekend 5: payments, btw en de cutover-rehearsal

Tegen weekend 5 draaide de M2-instantie, was het thema 90% rond en was de delta-loop al tien dagen schoon. Het overgebleven risico zat in de integratielaag: betalingen, ERP en btw.

De PSP-integratie was een custom M1-module uit 2018. Die hebben we niet geport. De M2-build gebruikt de officiële extensie van de PSP, die in 2023 is uitgebracht en 3DS2 correct afhandelt. Opgeslagen kaarttokens hebben we via de API van de PSP gemigreerd, niet via Magento, want dat is de enige weg die niet in strijd is met PCI.

De ERP-feed was de rommelige. De originele feed postte XML naar een SOAP-endpoint van een M1-module. We hadden de SOAP-endpoint op M2 kunnen herbouwen, maar het was 2026 en daar hadden we geen zin in. In plaats daarvan schreven we een kleine Symfony-service die tussen het ERP en Magento 2 ging zitten, het legacy-XML-formaat accepteerde en vertaalde naar M2 REST-calls. Het ERP-team hoefde nergens aan te komen. De vertaalservice is 412 regels PHP en een docker-compose-bestand.

Daarna deden we een volledige cutover-rehearsal. We zetten DNS om voor een intern testdomein, draaiden een gefaseerde checkout van winkelwagen tot PSP-bevestiging, controleerden of de order in het ERP verscheen en rolden DNS terug. De rehearsal bracht één bug naar boven: de standaard btw-berekening op M2 rondde btw anders af dan M1, op specifieke gemengde-btw-winkelwagens 0,01 EUR verschil. We patchten de afrondingsmodus in core_config_data (tax/calculation/algorithm op TOTAL_BASE_CALCULATION) en het verschil verdween.

Weekend 6: de cutover zelf

De daadwerkelijke cutover duurde 38 minuten. Het draaiboek:

  1. Pauzeer de ERP-feed om 22:00 op zaterdag.
  2. Laatste delta-run, controleer dat er nul rijen in de m2_cl_*-logtabellen staan.
  3. Zet M1 in maintenance mode.
  4. Laatste integriteits-SQL: ordertellingen, klanttellingen en producttellingen kloppen in beide databases.
  5. Zet DNS om naar de M2-bak. De TTL was de dinsdag ervoor al verlaagd naar 60s.
  6. Smoke-test checkout, admin, ERP-feed (gehervat) en search.
  7. Schakel de triggers van de delta-tool op de M1-bron uit. Ze laten draaien op een bevroren bron vreet schijf.

Stap 7 vergeten teams. De triggers die door de data migration tool zijn geïnstalleerd blijven staan tot je ze laat vallen. Maanden later, als iemand de oude M1-database opduikt voor analyse, schrijven die triggers vrolijk door naar logtabellen die geen afnemer meer hebben.

DROP TRIGGER IF EXISTS trg_sales_flat_order_after_insert;
DROP TRIGGER IF EXISTS trg_sales_flat_order_after_update;
DROP TRIGGER IF EXISTS trg_sales_flat_order_after_delete;

Wat we van de oude winkel hebben behouden

Drie dingen zijn letterlijk overgenomen en het team was blij dat we ze niet hebben gemoderniseerd:

  • De legacy klantgroep-ID's. Het B2B-portaal had prijsstaffel-logica gekoppeld aan specifieke integer-ID's. We hebben ze één-op-één gemapt.
  • De product-URL-keys. Niet de templates eromheen. Alleen de keys zelf. Search rankings zitten in die slugs.
  • De order-increment-ID's. Klantenservice had jarenlange muscle memory rond het M1-prefix. We hebben de bestaande sequence verlengd in plaats van opnieuw te beginnen.

De bredere les is dat een Magento 2-migratie vooral een oefening in terughoudendheid is. De verleiding om het datamodel te herschrijven, de catalogusboom op te schonen of de URL-keys te repareren is reëel, en elk van die beslissingen ruilt engineering-smaak in voor SEO-positie en verwarring bij klantenservice. Kies de kleinst mogelijke wijzigingsset.

De oude winkel bewerken terwijl de nieuwe gebouwd werd

Zes weekenden lang moest het bureau-team een verouderde site blijven bewerken terwijl er parallel een rebuild draaide. Dat is het deel van een Magento-migratie waar niemand over schrijft. Elk CMS-blok dat op M1 wordt aangeraakt, moet vóór de cutover op M2 zijn gereproduceerd. Elke catalogusregel. Elke promotie. De delta-tool doet gestructureerde data, geen redactie.

Die redactionele wijzigingen volgden we de eerste twee weekenden in een gedeeld Notion-board en dat werd een puinhoop. Vanaf weekend 3 werkten we direct op de M1-bestanden via Pier, wat ons één auditlog gaf van elke PHTML-, layout-XML- en CMS-blok-edit. Toen we Pier bouwden kwamen we precies dit patroon tegen bij een handvol Magento-klanten, en wat eruit kwam was een per-bestand version history gekoppeld aan een MySQL editor voor de cms_block- en cms_page-tabellen, zodat de M2-rebuild een definitieve lijst had van wat met diffs gespiegeld moest worden.

Het kleinste wat je vandaag kunt doen

Heb je nog een Magento 1-winkel draaien, dan zijn de eerste nuttige 30 minuten: dump core_config_data en grep op geserialiseerde PHP. Elk blok a:N:{...} is een toekomstige fatal in de M2-admin en een bekend unserialize-risico. Vind ze nu, terwijl je nog tijd hebt om te kiezen hoe je elk geval afhandelt. De migration tool is een stuk vrolijker over drie maanden, als je hem echt draait.

— Vragen —

Hoe lang duurt een migratie van Magento 1 naar Magento 2?

Voor een catalogus van middelgrote omvang met actieve orders moet je rekenen op zes tot acht weekenden avondwerk plus één cutover-nacht. De bulkmigratie is snel; de delta-loop en het themawerk vreten je kalender op.

Kun je Magento 1 en 2 echt parallel draaien?

Ja, via de delta-mode van de officiële data migration tool. Hij installeert triggers op de M1-bron en speelt wijzigingen elke minuut af op M2, zodat je voor de cutover alleen een korte DNS-swap nodig hebt.

Wat gebeurt er met URL-rankings tijdens de migratie?

Die overleven het als je vóór de cutover de rewrite-tabel toetst aan echt organisch verkeer. Bouw een grootboek van aangeklikte URL's uit Search Console, diff dat tegen de nieuwe url_rewrite-tabel en 301 de gaten.