Files
mysql2postgres/README.md
2026-01-11 21:56:50 +01:00

538 lines
16 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
- **Web GUI**: Interfaccia grafica basata su Gradio per monitoraggio e controllo visuale
- **Migrazione Completa**: Trasferimento di tutti i dati da MySQL a PostgreSQL
- **Migrazione Incrementale**: Sincronizzazione periodica basata su consolidation keys
- **Migrazione Parallela**: Supporto per worker multipli per velocizzare le migrazioni
- **Consolidamento Dati**: Raggruppa multiple righe MySQL in singoli record PostgreSQL
- **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
- **Performance Optimization**: Usa `mysql_max_id` per evitare full table scans
- **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
- **State Management**: Tracking affidabile con tabella `migration_state` in PostgreSQL
- **Error Logging**: Tracciamento automatico di chiavi di consolidamento non valide
## Setup
### 1. Requisiti
- Python 3.10+
- MySQL 5.7+
- PostgreSQL 13+
- pip
### 2. Installazione
```bash
# 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:
```bash
cp .env.example .env
```
Modificare `.env` con i tuoi dettagli:
```env
# MySQL Source Database
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=your_mysql_password
MYSQL_DATABASE=your_database_name
# PostgreSQL Target Database (container Incus)
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=your_postgres_password
POSTGRES_DATABASE=migrated_db
# Migration Settings
LOG_LEVEL=INFO
DRY_RUN=false
CONSOLIDATION_GROUP_LIMIT=40000
PROGRESS_LOG_INTERVAL=10000
# Performance Testing
BENCHMARK_OUTPUT_DIR=benchmark_results
BENCHMARK_ITERATIONS=5
```
## Utilizzo
### Comandi Disponibili
#### Info Configuration
```bash
python main.py info
```
Mostra la configurazione corrente di MySQL e PostgreSQL.
#### Setup Database
```bash
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`
#### Web GUI (Interfaccia Grafica) 🎨
Lancia l'interfaccia web per monitorare e controllare le migrazioni in modo visuale:
```bash
# Avvia la GUI sulla porta predefinita (7860)
uv run main.py web
# Avvia su una porta specifica
uv run main.py web --port 8080
# Crea un link pubblico condivisibile (utile per accesso remoto)
uv run main.py web --share
```
Poi apri il browser su `http://localhost:7860` per accedere alla dashboard.
**Funzionalità della GUI:**
- **📊 Overview Tab**: Visualizza lo stato di tutte le partizioni con grafici e tabelle in tempo reale
- **▶️ Start Migration Tab**: Avvia nuove migrazioni full con controlli per:
- Selezione tabella (RAWDATACOR, ELABDATADISP, o entrambe)
- Modalità parallela (numero di worker)
- Resume da checkpoint
- Dry-run mode
- Migrazione di partizione singola
- **🔄 Incremental Sync Tab**: Sincronizza solo i nuovi dati aggiunti dopo l'ultima migrazione full
- Selezione tabella
- Dry-run mode
- Spiegazione di come funziona l'incremental
- **📝 Logs Tab**: Visualizza i log in tempo reale delle migrazioni in corso
- **⚠️ Error Logs Tab**: Esplora i file di log degli errori di validazione
**Vantaggi rispetto alla CLI:**
- Monitoraggio visuale dello stato delle migrazioni
- Grafici interattivi per statistiche e progress
- Controllo centralizzato di più migrazioni
- Nessun bisogno di terminali multipli
- Accessibile anche da remoto con `--share`
#### Migrazione Completa (CLI)
```bash
# Migrare tutte le tabelle
python main.py migrate full
# Migrare una tabella specifica
python main.py migrate full --table RAWDATACOR
# Modalit<69> dry-run (senza modificare i dati)
python main.py migrate full --dry-run
```
#### Migrazione Incrementale
```bash
# Migrare solo i cambiamenti dal last sync
python main.py migrate incremental
# Per una tabella specifica
python main.py migrate incremental --table ELABDATADISP
# Dry-run per vedere cosa verrebbe migrato
python main.py migrate incremental --dry-run
```
#### Benchmark Performance
```bash
# 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
```
## Come Funziona il Consolidamento
Il tool non migra le righe MySQL 1:1 in PostgreSQL. Invece, **consolida** multiple righe MySQL in singoli record PostgreSQL raggruppati per:
```
(UnitName, ToolNameID, EventDate, EventTime)
```
### Validazione e Gestione Dati Corrotti
La migrazione valida automaticamente le chiavi di consolidamento e gestisce dati corrotti:
**Validazioni applicate:**
- `EventDate IS NULL` o `EventDate = '0000-00-00'`
- `ToolNameID IS NULL` o `ToolNameID = ''` (stringa vuota)
- `UnitName IS NULL` o `UnitName = ''` (stringa vuota)
- `UnitName` che inizia con `[L` (stringhe Java corrotte come `[Ljava.lang.String;@...`)
- `EventTime IS NULL`
**Comportamento:**
- Le chiavi non valide vengono **saltate automaticamente** per evitare interruzioni
- Ogni chiave scartata viene **loggata in file dedicati** per tracciabilità
- Il numero totale di chiavi scartate viene **riportato alla fine** della migrazione
**File di log degli errori:**
- Full migration: `migration_errors_<table>_<partition>.log` (es. `migration_errors_rawdatacor_p2024.log`)
- Incremental migration: `migration_errors_<table>_incremental_<timestamp>.log` (es. `migration_errors_rawdatacor_incremental_20260101_194500.log`)
Ogni esecuzione incrementale crea un nuovo file con timestamp per mantenere lo storico.
Questo approccio garantisce che la migrazione non si interrompa per dati corrotti, permettendo comunque di tracciare e analizzare le anomalie.
### Perché Consolidare?
**MySQL** ha molte righe per lo stesso momento:
```
id | UnitName | ToolNameID | EventDate | EventTime | Val0 | Val1
100 | Unit1 | Tool1 | 2024-01-01 | 10:00:00 | 23.5 | 45.2
101 | Unit1 | Tool1 | 2024-01-01 | 10:00:00 | 23.6 | 45.3
102 | Unit1 | Tool1 | 2024-01-01 | 10:00:00 | 23.7 | 45.1
```
**PostgreSQL** ottiene 1 record consolidato con tutti i valori in JSONB:
```json
{
"unit_name": "Unit1",
"tool_name_id": "Tool1",
"event_timestamp": "2024-01-01 10:00:00",
"mysql_max_id": 102,
"measurements": {
"0": [
{"value": 23.5, "unit": "°C"},
{"value": 23.6, "unit": "°C"},
{"value": 23.7, "unit": "°C"}
],
"1": [
{"value": 45.2, "unit": "bar"},
{"value": 45.3, "unit": "bar"},
{"value": 45.1, "unit": "bar"}
]
}
}
```
**Vantaggi:**
- 🔥 Meno righe → query più veloci
- 💾 Storage più efficiente (compressione JSONB)
- 📊 Analisi più semplici (tutti i valori in un posto)
- 🚀 Migrazione più veloce (meno transazioni)
## Trasformazione Dati
### RAWDATACOR
**Da MySQL:**
```
Val0, Val1, ..., ValF (16 colonne)
Val0_unitmisure, Val1_unitmisure, ..., ValF_unitmisure (16 colonne)
```
**A PostgreSQL (JSONB measurements):**
```json
{
"0": {"value": "123.45", "unit": "<22>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):**
```json
{
"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
```sql
-- 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 usando la colonna `event_year`:
```sql
-- Partizioni create automaticamente per:
-- rawdatacor_2014, rawdatacor_2015, ..., rawdatacor_2031
-- elabdatadisp_2014, elabdatadisp_2015, ..., elabdatadisp_2031
-- Partizionamento basato su event_year (calcolato da event_timestamp durante insert)
CREATE TABLE rawdatacor_2024 PARTITION OF rawdatacor
FOR VALUES FROM (2024) TO (2025);
-- Query partizionata (constraint exclusion automatico)
SELECT * FROM rawdatacor
WHERE event_year = 2024;
-- PostgreSQL usa solo rawdatacor_2024
-- Oppure usando event_timestamp
SELECT * FROM rawdatacor
WHERE event_timestamp >= '2024-01-01' AND event_timestamp < '2025-01-01';
-- PostgreSQL usa solo rawdatacor_2024
```
## Indici
### RAWDATACOR
```sql
-- Primary key (necessario per tabelle partizionate)
rawdatacor_pkey -- UNIQUE (id, event_year)
-- Consolidation key (previene duplicati)
rawdatacor_consolidation_key_unique -- UNIQUE (unit_name, tool_name_id, event_timestamp, event_year)
-- Query optimization
idx_rawdatacor_unit_tool -- (unit_name, tool_name_id)
idx_rawdatacor_measurements_gin -- GIN (measurements) per query JSONB
```
### ELABDATADISP
```sql
-- Primary key (necessario per tabelle partizionate)
elabdatadisp_pkey -- UNIQUE (id, event_year)
-- Consolidation key (previene duplicati)
elabdatadisp_consolidation_key_unique -- UNIQUE (unit_name, tool_name_id, event_timestamp, event_year)
-- Query optimization
idx_elabdatadisp_unit_tool -- (unit_name, tool_name_id)
idx_elabdatadisp_measurements_gin -- GIN (measurements) per query JSONB
```
## 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:
```json
{
"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**
```bash
# Configurare .env
cp .env.example .env
nano .env
# Creare schema PostgreSQL
python main.py setup --create-schema
```
2. **Prima migrazione (completa)**
```bash
# Test con dry-run
python main.py migrate full --dry-run
# Migrazione effettiva
python main.py migrate full
```
3. **Migrazioni periodiche (incrementali)**
```bash
# Manuale
python main.py migrate incremental
# Oppure schedule con cron (daily at 2 AM)
0 2 * * * cd /path/to/mysql2postgres && python main.py migrate incremental >> /var/log/migration.log 2>&1
```
4. **Sincronizzare stato migrazione (se necessario)**
```bash
# Se migration_state non è sincronizzato con i dati
python scripts/sync_migration_state.py
```
5. **Benchmark di performance**
```bash
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 `CONSOLIDATION_GROUP_LIMIT` in `.env` (default: 40000)
- Verificare performance di rete tra MySQL e PostgreSQL
### "No previous migration found" (incremental)
- Causa: Non è stata eseguita una migrazione completa prima
- Soluzione: Eseguire `python main.py migrate full` prima
### Migrazione incrementale lenta
- Causa: Query `SELECT MAX(mysql_max_id)` su PostgreSQL impiega ~60 secondi
- Soluzione: Questo è normale ed è eseguito solo una volta all'inizio. Le query MySQL successive sono istantanee.
- Alternativa: Creare indice su `mysql_max_id` (usa spazio disco):
```sql
CREATE INDEX idx_rawdatacor_mysql_max_id ON rawdatacor (mysql_max_id DESC);
CREATE INDEX idx_elabdatadisp_mysql_max_id ON elabdatadisp (mysql_max_id DESC);
```
### migration_state non sincronizzato
- Causa: Dati inseriti manualmente o stato corrotto
- Soluzione: Eseguire `python scripts/sync_migration_state.py`
### JSONB con valori NULL
- Il tool esclude automaticamente valori NULL da JSONB (solo valori non-NULL vengono aggiunti)
## Performance Tips
1. **Migration**
- Aumentare `CONSOLIDATION_GROUP_LIMIT` per processare più chiavi per batch (default: 40000)
- Aumentare `PROGRESS_LOG_INTERVAL` per ridurre logging (default: 10000)
- 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