PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Migracja danych prod → dev – mydumper, anonimizacja RODO, hooks automatyzujące setup

by Henryk Tews / wtorek, 05 sierpnia 2025 / Opublikowano w Magento 2

Każdy developer Magento 2 zna ten problem: produkcyjna baza danych ma 50GB, czas importu 3 godziny, a po imporcie konfiguracja wskazuje na produkcyjne URL-e i tokeny. Pokazuję kompletny workflow: jak wyciągnąć użyteczny dump z produkcji, automatycznie oczyścić dane wrażliwe, zmienić konfigurację środowiskową i skrócić czas importu z godzin do minut.

Dlaczego dump produkcji jest problematyczny

Bezpośredni dump produkcyjnej bazy Magento 2 ma kilka problemów:

  • Rozmiar – tabele logów, quote, session i catalog search indeksy mogą stanowić 80% rozmiaru
  • Dane wrażliwe – emaile klientów, hasła, dane kart (tokeny), adresy
  • Konfiguracja – URL-e, klucze API, hasła do zewnętrznych serwisów
  • Czas importu – 50GB MySQLdump przez standardowy import = kilka godzin

Skrypt dump z wykluczeniami – magedbm2 alternatywa

#!/bin/bash
# dump-prod.sh - inteligentny dump Magento 2

DB_HOST="db.prod.example.com"
DB_USER="magento_ro"   # użytkownik tylko do odczytu!
DB_NAME="magento_prod"
OUTPUT_FILE="magento_$(date +%Y%m%d_%H%M).sql.gz"

# Tabele które dumujemy bez danych (tylko struktura)
# Logi, sesje, cache i tabele EAV indeksów
NO_DATA_TABLES=(
    "admin_passwords"
    "cache"
    "cache_tag"
    "catalog_product_index_eav"
    "catalog_product_index_eav_decimal"
    "catalog_product_index_eav_decimal_idx"
    "catalog_product_index_eav_idx"
    "catalog_product_index_price"
    "catalog_product_index_price_bundle_idx"
    "catalog_product_index_price_idx"
    "catalogsearch_fulltext_scope1"
    "customer_log"
    "customer_visitor"
    "importexport_importdata"
    "oauth_nonce"
    "oauth_token"
    "persistent_session"
    "quote"
    "quote_address"
    "quote_id_mask"
    "quote_item"
    "quote_payment"
    "report_event"
    "report_viewed_product_index"
    "session"
)

echo "Tworzę dump Magento 2 z ${DB_NAME}..."

# 1. Dump z danymi (z wyłączeniem tabel bez danych)
IGNORE_ARGS=""
for table in "${NO_DATA_TABLES[@]}"; do
    IGNORE_ARGS="${IGNORE_ARGS} --ignore-table=${DB_NAME}.${table}"
done

mysqldump \
    --host="${DB_HOST}" \
    --user="${DB_USER}" \
    --single-transaction \
    --quick \
    --lock-tables=false \
    --no-tablespaces \
    ${IGNORE_ARGS} \
    "${DB_NAME}" | gzip > "${OUTPUT_FILE}.tmp"

# 2. Dodaj strukturę (bez danych) dla wykluczonych tabel
for table in "${NO_DATA_TABLES[@]}"; do
    mysqldump \
        --host="${DB_HOST}" \
        --user="${DB_USER}" \
        --no-data \
        --single-transaction \
        "${DB_NAME}" "${table}" >> "${OUTPUT_FILE}.tmp.sql" 2>/dev/null || true
done

echo "Dump gotowy: ${OUTPUT_FILE}"
echo "Rozmiar: $(du -sh ${OUTPUT_FILE} | cut -f1)"

Anonimizacja danych klientów – RODO compliance

<?php

declare(strict_types=1);

// Skrypt do uruchomienia po imporcie na dev/staging
// Maskuje dane osobowe klientów

class MagentoDataAnonymizer
{
    public function __construct(private \PDO $pdo) {}

    public function anonymize(): void
    {
        $this->pdo->beginTransaction();

        try {
            $this->anonymizeCustomers();
            $this->anonymizeOrders();
            $this->anonymizeNewsletterSubscribers();
            $this->clearSensitiveConfig();

            $this->pdo->commit();
            echo "Anonimizacja zakończona\n";
        } catch (\Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    private function anonymizeCustomers(): void
    {
        // Zaktualizuj emaile i dane osobowe zachowując strukturę
        $this->pdo->exec("
            UPDATE customer_entity
            SET
                email       = CONCAT('customer_', entity_id, '@example.com'),
                firstname   = CONCAT('Firstname', entity_id),
                lastname    = CONCAT('Lastname', entity_id)
        ");

        // Zamiast prawdziwych haseł – znane hasło dla dev/staging
        // Password: 'Password123!'
        $devHash = password_hash('Password123!', PASSWORD_BCRYPT, ['cost' => 10]);
        $this->pdo->prepare("UPDATE customer_entity SET password_hash = ?")->execute([$devHash]);

        echo "Zanonimizowano klientów: " . $this->pdo->query("SELECT ROW_COUNT()")->fetchColumn() . "\n";
    }

    private function anonymizeOrders(): void
    {
        // Anonimizuj adresy w zamówieniach
        $this->pdo->exec("
            UPDATE sales_order
            SET
                customer_email     = CONCAT('order_', entity_id, '@example.com'),
                customer_firstname = CONCAT('Order', entity_id),
                customer_lastname  = 'Customer'
        ");

        $this->pdo->exec("
            UPDATE sales_order_address
            SET
                email      = CONCAT('addr_', entity_id, '@example.com'),
                firstname  = 'Test',
                lastname   = 'Customer',
                street     = 'ul. Testowa 1',
                telephone  = '123456789'
        ");
    }

    private function anonymizeNewsletterSubscribers(): void
    {
        $this->pdo->exec("
            UPDATE newsletter_subscriber
            SET subscriber_email = CONCAT('newsletter_', subscriber_id, '@example.com')
        ");
    }

    private function clearSensitiveConfig(): void
    {
        // Wyczyść klucze API, tokeny, hasła do zewnętrznych serwisów
        $sensitiveKeys = [
            'payment/braintree/private_key',
            'payment/braintree/public_key',
            'payment/stripe/api_key',
            'system/smtp/password',
            'carriers/fedex/key',
            'carriers/dhl/id',
            'catalog/magento_catalogpermissions/enrich_product_security_data',
        ];

        foreach ($sensitiveKeys as $path) {
            $this->pdo->prepare("
                DELETE FROM core_config_data WHERE path = ?
            ")->execute([$path]);
        }
    }
}

Zmiana konfiguracji URL po imporcie

#!/bin/bash
# post-import.sh - uruchom po imporcie bazy na lokalnym środowisku

MAGE_ROOT="/var/www/magento"

echo "Ustawianie konfiguracji dev..."

# Zmień URL-e na lokalne
php "${MAGE_ROOT}/bin/magento" config:set web/unsecure/base_url "http://magento2-dev.ddev.site/"
php "${MAGE_ROOT}/bin/magento" config:set web/secure/base_url "https://magento2-dev.ddev.site/"

# Wyłącz cache na czas debugowania
php "${MAGE_ROOT}/bin/magento" config:set system/full_page_cache/caching_application 1

# Wyłącz płatności produkcyjne
php "${MAGE_ROOT}/bin/magento" config:set payment/braintree/active 0
php "${MAGE_ROOT}/bin/magento" config:set payment/stripe/active 0

# Ustaw tryb dev
php "${MAGE_ROOT}/bin/magento" deploy:mode:set developer

# Uruchom anonimizację danych
php "${MAGE_ROOT}/bin/console" vendor:anonymize-data

# Reindeksuj
php "${MAGE_ROOT}/bin/magento" indexer:reindex

# Flush cache
php "${MAGE_ROOT}/bin/magento" cache:flush

echo "Środowisko dev gotowe!"
echo "URL: https://magento2-dev.ddev.site/"
echo "Admin: https://magento2-dev.ddev.site/admin"

Szybszy import – mydumper zamiast mysqldump

# mydumper - wielowątkowy dump/restore, 5-10x szybszy od mysqldump

# Instalacja
apt-get install mydumper  # Ubuntu
brew install mydumper     # macOS

# Dump z 8 wątkami
mydumper \
    --host db.prod.example.com \
    --user magento_ro \
    --database magento_prod \
    --outputdir /tmp/magento-dump \
    --threads 8 \
    --compress \
    --regex "^(?!(magento_prod\.(cache|session|quote)))" \
    --verbose 3

# Czas: ~5 min zamiast ~40 min dla 20GB bazy

# Import z 8 wątkami
myloader \
    --host localhost \
    --user magento \
    --password secret \
    --database magento_dev \
    --directory /tmp/magento-dump \
    --threads 8 \
    --verbose 3

# Czas: ~8 min zamiast ~60 min

Automatyzacja przez Makefile

# Makefile - jedno polecenie do pełnego refresh lokalnej bazy

.PHONY: db-refresh

db-refresh:
    @echo "Pobieranie dumpu z produkcji..."
    ssh deploy@prod.example.com "cd /var/www && bash dump-prod.sh" && \
    scp deploy@prod.example.com:/var/www/magento_latest.sql.gz /tmp/

    @echo "Importowanie do lokalnej bazy..."
    ddev exec "gzip -dc /tmp/magento_latest.sql.gz | mysql magento"

    @echo "Post-import konfiguracja..."
    ddev exec "cd /var/www/magento && bash post-import.sh"

    @echo "Gotowe! Środowisko dev zaktualizowane."

Podsumowanie

Odświeżanie lokalnej bazy Magento 2 z produkcji nie musi trwać godzinami i narażać danych klientów. Kluczowe elementy: wykluczanie tabel-śmietników z dumpa (zmniejsza rozmiar 5-10x), mydumper dla równoległego eksportu/importu (5-10x szybszy), automatyczna anonimizacja danych klientów (RODO compliance) i skrypt post-import który ustawia konfigurację dev. Zmakefile-owane w jedno polecenie – refresh bazy to kwestia kilku minut, nie godzin.

About Henryk Tews

Co możesz przeczytać następne

PWA Studio vs Hyvä vs Luma – rzetelne porównanie TCO i kiedy który
DI w Magento 2 – kontener, Virtual Types, Factory, shared/non-shared
Podsumowanie 2025 – pipe operator, Hyvä mainstream, AI tooling obowiązkowy, 120 wpisów
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Zawsze aktywne
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}