alex 1708969616 fix: Update migration state with final count when migration completes
When migration finishes, we need to update migration_state with:
1. The final actual row count from PostgreSQL
2. The final last_migrated_id (MAX(id) from the table)
3. Mark status as 'completed' (handled by _update_migration_state)

Previously, the final state update was missing, so migration_state
was left with stale data from the periodic updates.

Now _update_migration_state is called at the end to record the
authoritative final state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-23 20:16:28 +01:00

MySQL to PostgreSQL Migration Tool

Un tool robusto per la migrazione di database MySQL a PostgreSQL con trasformazione di colonne multiple in JSONB, supporto per partizionamento nativo di PostgreSQL, e sistema completo di benchmark per confrontare le performance.

Caratteristiche

  • Migrazione Completa: Trasferimento di tutti i dati da MySQL a PostgreSQL
  • Migrazione Incrementale: Sincronizzazione periodica basata su timestamp
  • Trasformazione JSONB: Consolidamento automatico di colonne multiple in campi JSONB
  • Partizionamento: Supporto per partizioni per anno (2014-2031)
  • Indici Ottimizzati: GIN indexes per query efficienti su JSONB
  • Progress Tracking: Barra di avanzamento in tempo reale con ETA
  • Benchmark: Sistema completo per confrontare performance MySQL vs PostgreSQL
  • Logging: Logging strutturato con Rich per output colorato
  • Dry-Run Mode: Modalità test senza modificare i dati

Setup

1. Requisiti

  • Python 3.10+
  • MySQL 5.7+
  • PostgreSQL 13+
  • pip

2. Installazione

# Clonare il repository
cd mysql2postgres

# Creare virtual environment
python -m venv venv
source venv/bin/activate  # su Windows: venv\Scripts\activate

# Installare dipendenze
pip install -e .

3. Configurazione

Copiare .env.example a .env e configurare i dettagli di connessione:

cp .env.example .env

Modificare .env con i tuoi dettagli:

# MySQL Source Database
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=your_password
MYSQL_DATABASE=your_database

# PostgreSQL Target Database (container Incus)
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_password
POSTGRES_DATABASE=migrated_db

# Migration Settings
BATCH_SIZE=10000
LOG_LEVEL=INFO
DRY_RUN=false

# Benchmark Settings
BENCHMARK_ITERATIONS=5

Utilizzo

Comandi Disponibili

Info Configuration

python main.py info

Mostra la configurazione corrente di MySQL e PostgreSQL.

Setup Database

python main.py setup --create-schema

Crea lo schema PostgreSQL con:

  • Tabelle rawdatacor e elabdatadisp partizionate per anno
  • Indici ottimizzati per JSONB
  • Tabella di tracking migration_state

Migrazione Completa

# Migrare tutte le tabelle
python main.py migrate full

# Migrare una tabella specifica
python main.py migrate full --table RAWDATACOR

# Modalità dry-run (senza modificare i dati)
python main.py migrate full --dry-run

Migrazione Incrementale

# Migrare solo i cambiamenti dal last sync
python main.py migrate incremental

# Per una tabella specifica
python main.py migrate incremental --table ELABDATADISP

# Specificare file di stato personalizzato
python main.py migrate incremental --state-file custom_state.json

Benchmark Performance

# Eseguire benchmark con iterations da config (default: 5)
python main.py benchmark

# Benchmark con numero specifico di iterazioni
python main.py benchmark --iterations 10

# Salvare risultati in file specifico
python main.py benchmark --output my_results.json

Trasformazione Dati

RAWDATACOR

Da MySQL:

Val0, Val1, ..., ValF (16 colonne)
Val0_unitmisure, Val1_unitmisure, ..., ValF_unitmisure (16 colonne)

A PostgreSQL (JSONB measurements):

{
  "0": {"value": "123.45", "unit": "°C"},
  "1": {"value": "67.89", "unit": "bar"},
  ...
  "F": {"value": "11.22", "unit": "m/s"}
}

ELABDATADISP

Da MySQL: 25+ colonne di misure e calcoli

A PostgreSQL (JSONB measurements):

{
  "shifts": {
    "x": 1.234567, "y": 2.345678, "z": 3.456789,
    "h": 4.567890, "h_dir": 5.678901, "h_local": 6.789012
  },
  "coordinates": {
    "x": 10.123456, "y": 20.234567, "z": 30.345678,
    "x_star": 40.456789, "z_star": 50.567890
  },
  "kinematics": {
    "speed": 1.111111, "speed_local": 2.222222,
    "acceleration": 3.333333, "acceleration_local": 4.444444
  },
  "sensors": {
    "t_node": 25.5, "load_value": 100.5, "water_level": 50.5, "pressure": 1.013
  },
  "calculated": {
    "alfa_x": 0.123456, "alfa_y": 0.234567, "area": 100.5
  }
}

Query su JSONB

Esempi di query su PostgreSQL

-- Filtrare per valore specifico in RAWDATACOR
SELECT * FROM rawdatacor
WHERE measurements->>'0'->>'value' IS NOT NULL;

-- Range query su ELABDATADISP
SELECT * FROM elabdatadisp
WHERE (measurements->'kinematics'->>'speed')::NUMERIC > 10.0;

-- Aggregazione su JSONB
SELECT unit_name, AVG((measurements->'kinematics'->>'speed')::NUMERIC) as avg_speed
FROM elabdatadisp
GROUP BY unit_name;

-- Containment check
SELECT * FROM elabdatadisp
WHERE measurements @> '{"kinematics":{}}';

-- GIN index scan (veloce)
SELECT * FROM rawdatacor
WHERE measurements ? '0'
LIMIT 1000;

Partizionamento

Entrambe le tabelle sono partizionate per anno (RANGE partitioning su EXTRACT(YEAR FROM event_date)):

-- Partizioni create automaticamente per:
-- rawdatacor_2014, rawdatacor_2015, ..., rawdatacor_2031
-- elabdatadisp_2014, elabdatadisp_2015, ..., elabdatadisp_2031

-- Query partizionata (constraint exclusion automatico)
SELECT * FROM rawdatacor
WHERE event_date >= '2024-01-01' AND event_date < '2024-12-31';
-- PostgreSQL usa solo rawdatacor_2024

Indici

RAWDATACOR

idx_unit_tool_node_datetime  -- (unit_name, tool_name_id, node_num, event_date, event_time)
idx_unit_tool                -- (unit_name, tool_name_id)
idx_measurements_gin         -- GIN index su measurements JSONB
idx_event_date               -- (event_date)

ELABDATADISP

idx_unit_tool_node_datetime  -- (unit_name, tool_name_id, node_num, event_date, event_time)
idx_unit_tool                -- (unit_name, tool_name_id)
idx_measurements_gin         -- GIN index su measurements JSONB
idx_event_date               -- (event_date)

Benchmark

Il benchmark confronta le performance tra MySQL e PostgreSQL su:

  • SELECT semplici: By PK, date range, unit+tool
  • Query JSONB: Filtri su campi, range query, containment checks
  • Aggregazioni: Group by, AVG, COUNT
  • JOIN: Tra le due tabelle

Risultati salvati in: benchmark_results/benchmark_TIMESTAMP.json

Formato risultati:

{
  "timestamp": "2024-01-15T10:30:45.123456",
  "iterations": 5,
  "tables": {
    "RAWDATACOR": {
      "select_by_pk": {
        "mysql": {
          "min": 0.5,
          "max": 0.8,
          "mean": 0.65,
          "median": 0.65,
          "p95": 0.8
        },
        "postgres": {
          "min": 0.3,
          "max": 0.6,
          "mean": 0.45,
          "p95": 0.6
        }
      }
    }
  }
}

Struttura Progetto

mysql2postgres/
 main.py                           # CLI entry point
 config.py                         # Configurazione Pydantic
 .env.example                      # Template configurazione
 pyproject.toml                    # Dipendenze
 README.md                         # Questo file
 src/
     connectors/
        mysql_connector.py        # Connector MySQL
        postgres_connector.py     # Connector PostgreSQL
     transformers/
        schema_transformer.py     # Creazione schema PostgreSQL
        data_transformer.py       # Trasformazione JSONB
     migrator/
        full_migration.py         # Migrazione completa
        incremental_migration.py  # Migrazione delta
        state.py                  # Tracking stato
     benchmark/
        query_generator.py        # Generatore query test
        performance_test.py       # Runner benchmark
     utils/
         logger.py                 # Logging con Rich
         progress.py               # Progress bar

Workflow Consigliato

  1. Setup iniziale

    python main.py setup --create-schema
    
  2. Prima migrazione (completa)

    python main.py migrate full
    
  3. Migrazioni periodiche (incrementali)

    python main.py migrate incremental
    
  4. Benchmark di performance

    python main.py benchmark --iterations 10
    

Troubleshooting

Errore di connessione MySQL

  • Verificare credenziali in .env
  • Controllare che MySQL sia online: mysql -h localhost -u root -p

Errore di connessione PostgreSQL

  • Verificare che container Incus sia avviato
  • Verificare credenziali: psql -h localhost -U postgres

Timeout durante migrazione

  • Aumentare BATCH_SIZE in .env (default: 10000)
  • Verificare performance di rete tra MySQL e PostgreSQL

JSONB con valori NULL

  • Il tool esclude automaticamente valori NULL da JSONB (solo valori non-NULL vengono aggiunti)

Performance Tips

  1. Migration

    • Aumentare BATCH_SIZE per meno transazioni (es. 50000)
    • Disabilitare indici durante migrazione se possibile (non implementato)
  2. Queries on JSONB

    • Usare ->> per testo, ->per JSON
    • GIN indexes accelerano query ? e @>
    • Castare a NUMERIC/INT quando necessario per operazioni
  3. Partizionamento

    • PostgreSQL usa constraint exclusion per saltare partizioni
    • Query su date range sono automaticamente ottimizzate

Supporto

Per bug o suggerimenti, aprire una issue nel repository.

License

MIT

Description
No description provided
Readme 460 KiB
Languages
Python 98.7%
Shell 1.3%