spark-migration 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ """Public interface for the Migration framework."""
2
+
3
+ from Migration.service.main import Migration
4
+
5
+ __all__ = ["Migration"]
6
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """Infraestrutura de conexao, tarefas e Spark."""
@@ -0,0 +1,72 @@
1
+ from sqlalchemy import create_engine, inspect, Engine
2
+ from Migration.logs.logs import logger
3
+
4
+ #Cria Conexão do banco de dados e retorna o nome de todas as tabelas
5
+
6
+ class Engine:
7
+ def __init__(self, url:str, name:str)-> None:
8
+
9
+ #Url que conecta com o banco de dados
10
+ self.__url = url
11
+
12
+ self.name = name
13
+
14
+ self.eng = self.con()
15
+
16
+ #Cria a engine do banco de dados
17
+ def con(self) -> Engine:
18
+
19
+ try:
20
+ logger.info(f"Connecting to database {self.name}...")
21
+
22
+ eng = create_engine(
23
+ url=self.__url
24
+ )
25
+
26
+ logger.info(f"Connected to database {self.name}.")
27
+
28
+ return eng
29
+
30
+ except Exception as error:
31
+
32
+ raise Exception(error)
33
+
34
+ #Pega todas as tabelas do banco e retorna em uma lista
35
+ def tables(self) -> list:
36
+
37
+ try:
38
+ logger.info(f"Discovering tables in database {self.name}...")
39
+
40
+ inspector = inspect(self.eng)
41
+
42
+ tables = inspector.get_table_names()
43
+
44
+ logger.info(
45
+ f"Discovered {len(tables)} table(s): "
46
+ f"{', '.join(tables) if tables else '[none]'}"
47
+ )
48
+
49
+ return tables
50
+
51
+ except Exception as error:
52
+
53
+ raise Exception(error)
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
@@ -0,0 +1,30 @@
1
+ from pathlib import Path
2
+
3
+ from Migration.logs.logs import logger
4
+ from pyspark.sql import SparkSession
5
+
6
+ logger.info("Starting Spark session...")
7
+
8
+ # Usa o driver que acompanha o projeto. No Windows, spark.jars.packages tenta
9
+ # baixar/copiar o arquivo via Hadoop e exige winutils.exe/HADOOP_HOME.
10
+ postgres_driver = (
11
+ Path(__file__).resolve().parents[1]
12
+ / "drivers"
13
+ / "postgresql-42.7.12.jar"
14
+ )
15
+
16
+ if not postgres_driver.is_file():
17
+ raise FileNotFoundError(f"Driver PostgreSQL nao encontrado: {postgres_driver}")
18
+
19
+ driver_path = str(postgres_driver)
20
+
21
+ app_spark = (
22
+ SparkSession.builder
23
+ .appName("Migration")
24
+ .master("local[*]")
25
+ .config("spark.driver.extraClassPath", driver_path)
26
+ .config("spark.executor.extraClassPath", driver_path)
27
+ .getOrCreate()
28
+ )
29
+ logger.info("Spark session started.")
30
+
@@ -0,0 +1,61 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ from functools import wraps
3
+ from queue import Queue
4
+
5
+ from Migration.logs.logs import logger
6
+
7
+ class Channel:
8
+ def __init__(self, name: str) -> None:
9
+ self.name = name
10
+ self.queue = Queue()
11
+ logger.info(f"Channel {self.name} created.")
12
+
13
+ def send(self, result) -> None:
14
+ self.queue.put(result)
15
+
16
+ def get(self):
17
+ return self.queue.get()
18
+
19
+
20
+ class Task:
21
+ """Executa tarefas em threads para compartilhar uma unica sessao Spark."""
22
+
23
+ def __init__(self, max_workers: int, name: str, channel: Channel) -> None:
24
+ self.name = name
25
+ self.channel = channel
26
+ self.executor = ThreadPoolExecutor(
27
+ max_workers=max_workers,
28
+ thread_name_prefix=name,
29
+ )
30
+
31
+ def run(self):
32
+ def decorator(func):
33
+ @wraps(func)
34
+ def wrapper(*args, **kwargs):
35
+ future = self.executor.submit(func, *args, **kwargs)
36
+
37
+ def finished(completed):
38
+ try:
39
+ self.channel.send(completed.result())
40
+ except BaseException as error:
41
+ self.channel.send(error)
42
+
43
+ future.add_done_callback(finished)
44
+ return future
45
+
46
+ return wrapper
47
+
48
+ return decorator
49
+
50
+
51
+ # Canal onde vai receber o resultado das tasks.
52
+ ch = Channel(name="Migrations")
53
+
54
+ # Threads evitam criar uma JVM/SparkSession por tabela e excecoes nao precisam
55
+ # ser serializadas entre processos.
56
+ task = Task(
57
+ max_workers=4,
58
+ name="Migration_task",
59
+ channel=ch
60
+ )
61
+
@@ -0,0 +1 @@
1
+ """Configuracao de logs do framework."""
@@ -0,0 +1,9 @@
1
+ #Cria a configuração global de logs
2
+ import logging
3
+ logging.basicConfig(
4
+ level=logging.INFO,
5
+ format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
6
+ handlers=[
7
+ logging.StreamHandler()
8
+ ]
9
+ )
@@ -0,0 +1,4 @@
1
+ #Pega a configuração global de logs
2
+ from Migration.logs import global_logs
3
+ import logging
4
+ logger = logging.getLogger(__name__)
@@ -0,0 +1 @@
1
+ """Modelos e controle da migracao de dados."""
@@ -0,0 +1,101 @@
1
+ from Migration.logs.logs import logger
2
+ from Migration.engine.init_spark import app_spark
3
+ from pyspark.sql import DataFrame
4
+ import time
5
+
6
+ #Essa classe serve para ler e transferir a tabela
7
+ class SparkDb:
8
+ def __init__(
9
+ self,
10
+ url: str,
11
+ table_name: str,
12
+ new_url: str,
13
+ user: str,
14
+ password: str,
15
+ new_user: str,
16
+ new_password: str,
17
+ ) -> None:
18
+
19
+ self.url = url
20
+ self.table = table_name
21
+ self.spark = app_spark
22
+ self.new_url = new_url
23
+ self.user = user
24
+ self.password = password
25
+ self.new_user = new_user
26
+ self.new_password = new_password
27
+
28
+ #Le a tabela
29
+ def read(self, max_attempts: int = 3)-> DataFrame:
30
+ for attempt in range(1, max_attempts + 1):
31
+ try:
32
+ logger.info(
33
+ f"Reading table {self.table} "
34
+ f"(attempt {attempt}/{max_attempts})"
35
+ )
36
+
37
+ df = (self.spark.
38
+ read.format("jdbc").
39
+ option("url", self.url).
40
+ option("dbtable", self.table).
41
+ option("driver", "org.postgresql.Driver").
42
+ option("user", self.user).
43
+ option("password", self.password).
44
+ option("connectTimeout", "30").
45
+ option("socketTimeout", "300").
46
+ option("tcpKeepAlive", "true").
47
+ load())
48
+
49
+ logger.info(f"Table {self.table} loaded.")
50
+ return df
51
+
52
+ except Exception as error:
53
+ if attempt == max_attempts:
54
+ raise RuntimeError(
55
+ f"Nao foi possivel ler a tabela {self.table} "
56
+ f"apos {max_attempts} tentativas: {error}"
57
+ ) from error
58
+
59
+ logger.warning(
60
+ f"Temporary failure while reading {self.table}: {error}. "
61
+ "Retrying..."
62
+ )
63
+ time.sleep(3 * attempt)
64
+
65
+ #Salva no novo banco de dados
66
+ def save(self) -> None:
67
+ try:
68
+ logger.info(f"Writing table {self.table}...")
69
+
70
+ (self.df.write.
71
+ format("jdbc").
72
+ option("url", self.new_url).
73
+ option("dbtable", self.table).
74
+ option("driver", "org.postgresql.Driver").
75
+ option("user", self.new_user).
76
+ option("password", self.new_password).
77
+ mode("append").save()
78
+ )
79
+
80
+ logger.info(f"Table {self.table} written successfully.")
81
+
82
+ except Exception as error:
83
+ raise Exception(error)
84
+
85
+ def run(self) -> str:
86
+ self.df = self.read()
87
+ self.save()
88
+ return self.table
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
@@ -0,0 +1,22 @@
1
+ from urllib.parse import quote_plus
2
+ #serve pra formatar od dados
3
+ class Formate:
4
+
5
+ def __init__(self, host:str, port:int, user:str, password:str, dbname:str)->None:
6
+ self.host = host
7
+ self.port = port
8
+ self.user = user
9
+ self.Pass = password
10
+ self.db = dbname
11
+
12
+ #formata os dados de conexão pra url do spark
13
+ def url_spark(self) -> str:
14
+ return f"jdbc:postgresql://{self.host}:{self.port}/{self.db}"
15
+
16
+ #forma para url do sqlalchemy
17
+ def url_alchemy(self) ->str:
18
+ password = quote_plus(self.Pass)
19
+
20
+ return f"postgresql://{self.user}:{password}@{self.host}:{self.port}/{self.db}"
21
+
22
+
@@ -0,0 +1,2 @@
1
+ """Servico publico de migracao."""
2
+ from Migration.service.main import Migration
@@ -0,0 +1,81 @@
1
+ from Migration.logs.logs import logger
2
+ from Migration.engine.connect import Engine
3
+ from Migration.tasks.task_db import SaveDb
4
+ from Migration.engine.init_task import ch
5
+ from Migration.schema.model import Formate
6
+
7
+ #Faz toda migração do banco de dados
8
+ class Migration:
9
+ def __init__(self, data:dict, new_data:dict)->None:
10
+
11
+ self.__url = Formate(
12
+ host=data["host"],
13
+ port=data["port"],
14
+ dbname=data["dbname"],
15
+ password=data["password"],
16
+ user=data["user"]
17
+ )
18
+
19
+ self.__newurl = Formate(
20
+ host=new_data["host"],
21
+ port=new_data["port"],
22
+ dbname=new_data["dbname"],
23
+ password=new_data["password"],
24
+ user=new_data["user"]
25
+ )
26
+
27
+ self.__source_user = data["user"]
28
+ self.__source_password = data["password"]
29
+ self.__destination_user = new_data["user"]
30
+ self.__destination_password = new_data["password"]
31
+
32
+ self.tables = Engine(url=self.__url.url_alchemy(), name=data["dbname"]).tables()
33
+
34
+ self.channel = ch
35
+
36
+ #Incia uma task para cada tabela do banco de dados
37
+ def init_task(self)->None:
38
+ for table in self.tables:
39
+ SaveDb(
40
+ table_name=table,
41
+ url=self.__url.url_spark(),
42
+ new_url=self.__newurl.url_spark(),
43
+ user=self.__source_user,
44
+ password=self.__source_password,
45
+ new_user=self.__destination_user,
46
+ new_password=self.__destination_password,
47
+ )
48
+
49
+ #Chama o metodo init_task para iniciar as tasks, e depois entra num loop e espera todas as tasks finalizarem
50
+ def run(self) -> None:
51
+ logger.info("Starting database migration...")
52
+
53
+ self.init_task()
54
+
55
+ tables_finish = []
56
+
57
+ while len(tables_finish) != len(self.tables):
58
+
59
+ data = self.channel.get()
60
+
61
+ if isinstance(data, BaseException):
62
+ raise RuntimeError(f"Falha durante a migracao: {data}") from data
63
+
64
+ if data is not None:
65
+ logger.info(f"Table {data} migrated successfully.")
66
+ tables_finish.append(data)
67
+
68
+
69
+ logger.info("Database migration completed successfully.")
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
@@ -0,0 +1 @@
1
+ """Tarefas de migracao por tabela."""
@@ -0,0 +1,28 @@
1
+
2
+ from Migration.engine.init_task import task
3
+ from Migration.schema.control import SparkDb
4
+
5
+ #Transforma todo sparkdb em uma task
6
+ @task.run()
7
+ def SaveDb(
8
+ url: str,
9
+ new_url: str,
10
+ table_name: str,
11
+ user: str,
12
+ password: str,
13
+ new_user: str,
14
+ new_password: str,
15
+ ):
16
+ tb = SparkDb(
17
+ url=url,
18
+ new_url=new_url,
19
+ table_name=table_name,
20
+ user=user,
21
+ password=password,
22
+ new_user=new_user,
23
+ new_password=new_password,
24
+ ).run()
25
+ return tb
26
+
27
+
28
+
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: spark_migration
3
+ Version: 0.1.0
4
+ Summary: Spark Migration: framework concorrente para migracao de dados PostgreSQL com Apache Spark.
5
+ Keywords: database,migration,postgresql,pyspark,spark,etl
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Database
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: pyspark<5,>=4.0
20
+ Requires-Dist: SQLAlchemy<3,>=2.0
21
+ Requires-Dist: psycopg2-binary<3,>=2.9
22
+
23
+ <div align="center">
24
+
25
+ # Spark Migration
26
+
27
+ ### Um framework concorrente para migração de dados PostgreSQL com Apache Spark
28
+
29
+ [![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/)
30
+ [![Apache Spark](https://img.shields.io/badge/Apache_Spark-4.x-E25A1C?style=for-the-badge&logo=apachespark&logoColor=white)](https://spark.apache.org/)
31
+ [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-Suportado-4169E1?style=for-the-badge&logo=postgresql&logoColor=white)](https://www.postgresql.org/)
32
+ [![Status](https://img.shields.io/badge/Status-Beta-F59E0B?style=for-the-badge)](#status-do-projeto)
33
+
34
+ Descubra tabelas, execute tarefas concorrentes e transfira dados via JDBC usando uma única sessão Spark compartilhada.
35
+
36
+ </div>
37
+
38
+ ---
39
+
40
+ ## Visão geral
41
+
42
+ Spark Migration é um framework Python para copiar dados entre bancos PostgreSQL. Ele combina SQLAlchemy para inspecionar o banco de origem, Apache Spark para transferir os dados via JDBC e um conjunto controlado de threads para processar as tabelas concorrentemente.
43
+
44
+ A API pública foi projetada para ser simples:
45
+
46
+ ```python
47
+ Migration(data=origem, new_data=destino).run()
48
+ ```
49
+
50
+ ## Recursos
51
+
52
+ - Descoberta automática das tabelas do banco de origem.
53
+ - Leitura e escrita via JDBC com Apache Spark.
54
+ - Execução concorrente de até quatro tabelas.
55
+ - Uma única sessão Spark compartilhada entre todas as tarefas.
56
+ - Até três tentativas para falhas temporárias de leitura.
57
+ - Propagação de erros das tarefas para a thread principal.
58
+ - Driver JDBC do PostgreSQL incluído no pacote.
59
+ - Logs estruturados em inglês.
60
+
61
+ ## Como funciona
62
+
63
+ ```mermaid
64
+ flowchart LR
65
+ A["PostgreSQL de origem"] -->|"Descobre tabelas"| B["SQLAlchemy"]
66
+ B --> C["Serviço Spark Migration"]
67
+ C -->|"Uma tarefa por tabela"| D["Pool de threads"]
68
+ D --> E["Sessão Spark compartilhada"]
69
+ E -->|"Leitura JDBC"| A
70
+ E -->|"Escrita JDBC em append"| F["PostgreSQL de destino"]
71
+ D -->|"Resultado ou erro"| C
72
+ ```
73
+
74
+ 1. `Migration` formata as configurações de conexão da origem e do destino.
75
+ 2. O SQLAlchemy conecta-se à origem e descobre suas tabelas.
76
+ 3. O serviço cria uma tarefa para cada tabela encontrada.
77
+ 4. Cada tarefa lê sua tabela em um DataFrame Spark via JDBC.
78
+ 5. O Spark adiciona o DataFrame à tabela correspondente no destino.
79
+ 6. A thread principal aguarda os resultados e interrompe a execução quando uma tarefa falha.
80
+
81
+ ## Requisitos
82
+
83
+ - Python 3.10 ou superior.
84
+ - Java 17 disponível no `PATH`.
85
+ - Bancos PostgreSQL de origem e destino.
86
+ - Acesso de rede aos dois bancos.
87
+ - Permissão de leitura nas tabelas de origem.
88
+ - Permissões de criação e inserção no destino.
89
+
90
+ ## Instalação
91
+
92
+ Instale o Spark Migration e suas dependências diretamente pelo PyPI:
93
+
94
+ ```bash
95
+ pip install spark_migration
96
+ ```
97
+
98
+ Após a instalação, o framework é importado como `Migration`.
99
+
100
+ ## Uso rápido
101
+
102
+ Mantenha as credenciais fora do código-fonte. Este exemplo utiliza variáveis de ambiente:
103
+
104
+ ```python
105
+ import os
106
+
107
+ from Migration import Migration
108
+
109
+ origem = {
110
+ "host": os.environ["SOURCE_DB_HOST"],
111
+ "port": int(os.getenv("SOURCE_DB_PORT", "5432")),
112
+ "dbname": os.environ["SOURCE_DB_NAME"],
113
+ "user": os.environ["SOURCE_DB_USER"],
114
+ "password": os.environ["SOURCE_DB_PASSWORD"],
115
+ }
116
+
117
+ destino = {
118
+ "host": os.environ["DESTINATION_DB_HOST"],
119
+ "port": int(os.getenv("DESTINATION_DB_PORT", "5432")),
120
+ "dbname": os.environ["DESTINATION_DB_NAME"],
121
+ "user": os.environ["DESTINATION_DB_USER"],
122
+ "password": os.environ["DESTINATION_DB_PASSWORD"],
123
+ }
124
+
125
+ Migration(data=origem, new_data=destino).run()
126
+ ```
127
+
128
+ Execute o arquivo normalmente:
129
+
130
+ ```bash
131
+ python spark_migration.py
132
+ ```
133
+
134
+ Exemplo de saída:
135
+
136
+ ```text
137
+ INFO | Starting Spark session...
138
+ INFO | Spark session started.
139
+ INFO | Connecting to database postgres...
140
+ INFO | Discovered 1 table(s): orders
141
+ INFO | Starting database migration...
142
+ INFO | Reading table orders (attempt 1/3)
143
+ INFO | Table orders written successfully.
144
+ INFO | Database migration completed successfully.
145
+ ```
146
+
147
+ ## Configuração da conexão
148
+
149
+ Os parâmetros `data` e `new_data` recebem os mesmos campos:
150
+
151
+ | Campo | Tipo | Descrição |
152
+ | --- | --- | --- |
153
+ | `host` | `str` | Endereço ou IP do servidor PostgreSQL |
154
+ | `port` | `int` | Porta do PostgreSQL, normalmente `5432` |
155
+ | `dbname` | `str` | Nome do banco de dados |
156
+ | `user` | `str` | Usuário do banco |
157
+ | `password` | `str` | Senha do banco |
158
+
159
+ ## Comportamento da escrita
160
+
161
+ A versão atual utiliza o modo `append` do Spark.
162
+
163
+ - Se a tabela não existir no destino, o Spark a criará com base no DataFrame.
164
+ - Se a tabela já existir, os novos registros serão adicionados.
165
+ - Executar a mesma migração mais de uma vez pode duplicar os dados.
166
+ - Uma falha durante a escrita pode deixar dados parciais no destino.
167
+
168
+ Utilize um destino limpo para migrações completas e compare a quantidade de registros na origem e no destino antes de repetir uma execução.
169
+
170
+ ## Escopo e limitações atuais
171
+
172
+ O framework atualmente migra os registros das tabelas. Ele ainda não reproduz todos os objetos e configurações do PostgreSQL, incluindo:
173
+
174
+ - chaves primárias e estrangeiras;
175
+ - índices e restrições únicas;
176
+ - sequências e seus valores atuais;
177
+ - views e materialized views;
178
+ - triggers e funções armazenadas;
179
+ - usuários, permissões e políticas de segurança em nível de linha;
180
+ - extensões e configurações do banco.
181
+
182
+ Em migrações de produção, recrie e valide esses objetos separadamente.
183
+
184
+ ## Observações para Windows
185
+
186
+ O Spark pode informar que `winutils.exe`, `HADOOP_HOME` ou a biblioteca nativa do Hadoop não estão disponíveis. O framework carrega diretamente o driver JDBC incluído no pacote, portanto esses avisos não impedem o fluxo suportado de migração PostgreSQL.
187
+
188
+ Se o Spark não iniciar, confirme se o Java 17 está instalado:
189
+
190
+ ```powershell
191
+ java -version
192
+ ```
193
+
194
+ ## Solução de problemas
195
+
196
+ ### `Connection reset`
197
+
198
+ O banco ou o gerenciador de conexões encerrou a conexão JDBC. O framework repete automaticamente a leitura até três vezes. Se o problema continuar, verifique o estado do banco, a estabilidade da rede, os requisitos de SSL e os limites do pool de conexões.
199
+
200
+ ### `Migration failed`
201
+
202
+ A thread principal recebeu uma exceção de uma tarefa. Procure o primeiro erro de banco ou Spark exibido antes dessa mensagem; normalmente ele contém a causa real.
203
+
204
+ ### Porta da interface Spark ocupada
205
+
206
+ O Spark pode selecionar outra porta quando a porta local `4040` já estiver em uso. Isso é apenas informativo e normalmente não impede a migração.
207
+
208
+ ## Segurança
209
+
210
+ - Nunca inclua senhas de banco ou tokens do PyPI no repositório.
211
+ - Prefira variáveis de ambiente ou um gerenciador de segredos.
212
+ - Utilize usuários de banco dedicados e com as menores permissões necessárias.
213
+ - Troque credenciais que já tenham sido publicadas ou compartilhadas.
214
+
215
+ ## Status do projeto
216
+
217
+ Spark Migration está em fase **Beta**. A migração de registros entre bancos PostgreSQL está funcional. Modos de escrita idempotentes, migração completa do esquema, validação automática e suporte a outros bancos ainda estão em desenvolvimento.
218
+
219
+ ## Roadmap
220
+
221
+ - Modos configuráveis `append`, `overwrite` e falha se a tabela existir.
222
+ - Validação por quantidade de registros e checksum.
223
+ - Migração de esquema, restrições, índices e sequências.
224
+ - Filtros para incluir ou excluir tabelas.
225
+ - Configuração de concorrência e tentativas.
226
+ - Testes automatizados e integração contínua.
227
+ - Suporte a outros bancos compatíveis com JDBC.
@@ -0,0 +1,205 @@
1
+ <div align="center">
2
+
3
+ # Spark Migration
4
+
5
+ ### Um framework concorrente para migração de dados PostgreSQL com Apache Spark
6
+
7
+ [![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/)
8
+ [![Apache Spark](https://img.shields.io/badge/Apache_Spark-4.x-E25A1C?style=for-the-badge&logo=apachespark&logoColor=white)](https://spark.apache.org/)
9
+ [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-Suportado-4169E1?style=for-the-badge&logo=postgresql&logoColor=white)](https://www.postgresql.org/)
10
+ [![Status](https://img.shields.io/badge/Status-Beta-F59E0B?style=for-the-badge)](#status-do-projeto)
11
+
12
+ Descubra tabelas, execute tarefas concorrentes e transfira dados via JDBC usando uma única sessão Spark compartilhada.
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## Visão geral
19
+
20
+ Spark Migration é um framework Python para copiar dados entre bancos PostgreSQL. Ele combina SQLAlchemy para inspecionar o banco de origem, Apache Spark para transferir os dados via JDBC e um conjunto controlado de threads para processar as tabelas concorrentemente.
21
+
22
+ A API pública foi projetada para ser simples:
23
+
24
+ ```python
25
+ Migration(data=origem, new_data=destino).run()
26
+ ```
27
+
28
+ ## Recursos
29
+
30
+ - Descoberta automática das tabelas do banco de origem.
31
+ - Leitura e escrita via JDBC com Apache Spark.
32
+ - Execução concorrente de até quatro tabelas.
33
+ - Uma única sessão Spark compartilhada entre todas as tarefas.
34
+ - Até três tentativas para falhas temporárias de leitura.
35
+ - Propagação de erros das tarefas para a thread principal.
36
+ - Driver JDBC do PostgreSQL incluído no pacote.
37
+ - Logs estruturados em inglês.
38
+
39
+ ## Como funciona
40
+
41
+ ```mermaid
42
+ flowchart LR
43
+ A["PostgreSQL de origem"] -->|"Descobre tabelas"| B["SQLAlchemy"]
44
+ B --> C["Serviço Spark Migration"]
45
+ C -->|"Uma tarefa por tabela"| D["Pool de threads"]
46
+ D --> E["Sessão Spark compartilhada"]
47
+ E -->|"Leitura JDBC"| A
48
+ E -->|"Escrita JDBC em append"| F["PostgreSQL de destino"]
49
+ D -->|"Resultado ou erro"| C
50
+ ```
51
+
52
+ 1. `Migration` formata as configurações de conexão da origem e do destino.
53
+ 2. O SQLAlchemy conecta-se à origem e descobre suas tabelas.
54
+ 3. O serviço cria uma tarefa para cada tabela encontrada.
55
+ 4. Cada tarefa lê sua tabela em um DataFrame Spark via JDBC.
56
+ 5. O Spark adiciona o DataFrame à tabela correspondente no destino.
57
+ 6. A thread principal aguarda os resultados e interrompe a execução quando uma tarefa falha.
58
+
59
+ ## Requisitos
60
+
61
+ - Python 3.10 ou superior.
62
+ - Java 17 disponível no `PATH`.
63
+ - Bancos PostgreSQL de origem e destino.
64
+ - Acesso de rede aos dois bancos.
65
+ - Permissão de leitura nas tabelas de origem.
66
+ - Permissões de criação e inserção no destino.
67
+
68
+ ## Instalação
69
+
70
+ Instale o Spark Migration e suas dependências diretamente pelo PyPI:
71
+
72
+ ```bash
73
+ pip install spark_migration
74
+ ```
75
+
76
+ Após a instalação, o framework é importado como `Migration`.
77
+
78
+ ## Uso rápido
79
+
80
+ Mantenha as credenciais fora do código-fonte. Este exemplo utiliza variáveis de ambiente:
81
+
82
+ ```python
83
+ import os
84
+
85
+ from Migration import Migration
86
+
87
+ origem = {
88
+ "host": os.environ["SOURCE_DB_HOST"],
89
+ "port": int(os.getenv("SOURCE_DB_PORT", "5432")),
90
+ "dbname": os.environ["SOURCE_DB_NAME"],
91
+ "user": os.environ["SOURCE_DB_USER"],
92
+ "password": os.environ["SOURCE_DB_PASSWORD"],
93
+ }
94
+
95
+ destino = {
96
+ "host": os.environ["DESTINATION_DB_HOST"],
97
+ "port": int(os.getenv("DESTINATION_DB_PORT", "5432")),
98
+ "dbname": os.environ["DESTINATION_DB_NAME"],
99
+ "user": os.environ["DESTINATION_DB_USER"],
100
+ "password": os.environ["DESTINATION_DB_PASSWORD"],
101
+ }
102
+
103
+ Migration(data=origem, new_data=destino).run()
104
+ ```
105
+
106
+ Execute o arquivo normalmente:
107
+
108
+ ```bash
109
+ python spark_migration.py
110
+ ```
111
+
112
+ Exemplo de saída:
113
+
114
+ ```text
115
+ INFO | Starting Spark session...
116
+ INFO | Spark session started.
117
+ INFO | Connecting to database postgres...
118
+ INFO | Discovered 1 table(s): orders
119
+ INFO | Starting database migration...
120
+ INFO | Reading table orders (attempt 1/3)
121
+ INFO | Table orders written successfully.
122
+ INFO | Database migration completed successfully.
123
+ ```
124
+
125
+ ## Configuração da conexão
126
+
127
+ Os parâmetros `data` e `new_data` recebem os mesmos campos:
128
+
129
+ | Campo | Tipo | Descrição |
130
+ | --- | --- | --- |
131
+ | `host` | `str` | Endereço ou IP do servidor PostgreSQL |
132
+ | `port` | `int` | Porta do PostgreSQL, normalmente `5432` |
133
+ | `dbname` | `str` | Nome do banco de dados |
134
+ | `user` | `str` | Usuário do banco |
135
+ | `password` | `str` | Senha do banco |
136
+
137
+ ## Comportamento da escrita
138
+
139
+ A versão atual utiliza o modo `append` do Spark.
140
+
141
+ - Se a tabela não existir no destino, o Spark a criará com base no DataFrame.
142
+ - Se a tabela já existir, os novos registros serão adicionados.
143
+ - Executar a mesma migração mais de uma vez pode duplicar os dados.
144
+ - Uma falha durante a escrita pode deixar dados parciais no destino.
145
+
146
+ Utilize um destino limpo para migrações completas e compare a quantidade de registros na origem e no destino antes de repetir uma execução.
147
+
148
+ ## Escopo e limitações atuais
149
+
150
+ O framework atualmente migra os registros das tabelas. Ele ainda não reproduz todos os objetos e configurações do PostgreSQL, incluindo:
151
+
152
+ - chaves primárias e estrangeiras;
153
+ - índices e restrições únicas;
154
+ - sequências e seus valores atuais;
155
+ - views e materialized views;
156
+ - triggers e funções armazenadas;
157
+ - usuários, permissões e políticas de segurança em nível de linha;
158
+ - extensões e configurações do banco.
159
+
160
+ Em migrações de produção, recrie e valide esses objetos separadamente.
161
+
162
+ ## Observações para Windows
163
+
164
+ O Spark pode informar que `winutils.exe`, `HADOOP_HOME` ou a biblioteca nativa do Hadoop não estão disponíveis. O framework carrega diretamente o driver JDBC incluído no pacote, portanto esses avisos não impedem o fluxo suportado de migração PostgreSQL.
165
+
166
+ Se o Spark não iniciar, confirme se o Java 17 está instalado:
167
+
168
+ ```powershell
169
+ java -version
170
+ ```
171
+
172
+ ## Solução de problemas
173
+
174
+ ### `Connection reset`
175
+
176
+ O banco ou o gerenciador de conexões encerrou a conexão JDBC. O framework repete automaticamente a leitura até três vezes. Se o problema continuar, verifique o estado do banco, a estabilidade da rede, os requisitos de SSL e os limites do pool de conexões.
177
+
178
+ ### `Migration failed`
179
+
180
+ A thread principal recebeu uma exceção de uma tarefa. Procure o primeiro erro de banco ou Spark exibido antes dessa mensagem; normalmente ele contém a causa real.
181
+
182
+ ### Porta da interface Spark ocupada
183
+
184
+ O Spark pode selecionar outra porta quando a porta local `4040` já estiver em uso. Isso é apenas informativo e normalmente não impede a migração.
185
+
186
+ ## Segurança
187
+
188
+ - Nunca inclua senhas de banco ou tokens do PyPI no repositório.
189
+ - Prefira variáveis de ambiente ou um gerenciador de segredos.
190
+ - Utilize usuários de banco dedicados e com as menores permissões necessárias.
191
+ - Troque credenciais que já tenham sido publicadas ou compartilhadas.
192
+
193
+ ## Status do projeto
194
+
195
+ Spark Migration está em fase **Beta**. A migração de registros entre bancos PostgreSQL está funcional. Modos de escrita idempotentes, migração completa do esquema, validação automática e suporte a outros bancos ainda estão em desenvolvimento.
196
+
197
+ ## Roadmap
198
+
199
+ - Modos configuráveis `append`, `overwrite` e falha se a tabela existir.
200
+ - Validação por quantidade de registros e checksum.
201
+ - Migração de esquema, restrições, índices e sequências.
202
+ - Filtros para incluir ou excluir tabelas.
203
+ - Configuração de concorrência e tentativas.
204
+ - Testes automatizados e integração contínua.
205
+ - Suporte a outros bancos compatíveis com JDBC.
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "spark_migration"
7
+ version = "0.1.0"
8
+ description = "Spark Migration: framework concorrente para migracao de dados PostgreSQL com Apache Spark."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "pyspark>=4.0,<5",
13
+ "SQLAlchemy>=2.0,<3",
14
+ "psycopg2-binary>=2.9,<3",
15
+ ]
16
+ keywords = [
17
+ "database",
18
+ "migration",
19
+ "postgresql",
20
+ "pyspark",
21
+ "spark",
22
+ "etl",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 4 - Beta",
26
+ "Intended Audience :: Developers",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Programming Language :: Python :: 3.14",
34
+ "Topic :: Database",
35
+ "Topic :: Software Development :: Libraries :: Python Modules",
36
+ ]
37
+
38
+ [tool.setuptools]
39
+ include-package-data = true
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["."]
43
+ include = ["Migration*"]
44
+ namespaces = true
45
+
46
+ [tool.setuptools.package-data]
47
+ Migration = ["drivers/*.jar"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: spark_migration
3
+ Version: 0.1.0
4
+ Summary: Spark Migration: framework concorrente para migracao de dados PostgreSQL com Apache Spark.
5
+ Keywords: database,migration,postgresql,pyspark,spark,etl
6
+ Classifier: Development Status :: 4 - Beta
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Database
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: pyspark<5,>=4.0
20
+ Requires-Dist: SQLAlchemy<3,>=2.0
21
+ Requires-Dist: psycopg2-binary<3,>=2.9
22
+
23
+ <div align="center">
24
+
25
+ # Spark Migration
26
+
27
+ ### Um framework concorrente para migração de dados PostgreSQL com Apache Spark
28
+
29
+ [![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/)
30
+ [![Apache Spark](https://img.shields.io/badge/Apache_Spark-4.x-E25A1C?style=for-the-badge&logo=apachespark&logoColor=white)](https://spark.apache.org/)
31
+ [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-Suportado-4169E1?style=for-the-badge&logo=postgresql&logoColor=white)](https://www.postgresql.org/)
32
+ [![Status](https://img.shields.io/badge/Status-Beta-F59E0B?style=for-the-badge)](#status-do-projeto)
33
+
34
+ Descubra tabelas, execute tarefas concorrentes e transfira dados via JDBC usando uma única sessão Spark compartilhada.
35
+
36
+ </div>
37
+
38
+ ---
39
+
40
+ ## Visão geral
41
+
42
+ Spark Migration é um framework Python para copiar dados entre bancos PostgreSQL. Ele combina SQLAlchemy para inspecionar o banco de origem, Apache Spark para transferir os dados via JDBC e um conjunto controlado de threads para processar as tabelas concorrentemente.
43
+
44
+ A API pública foi projetada para ser simples:
45
+
46
+ ```python
47
+ Migration(data=origem, new_data=destino).run()
48
+ ```
49
+
50
+ ## Recursos
51
+
52
+ - Descoberta automática das tabelas do banco de origem.
53
+ - Leitura e escrita via JDBC com Apache Spark.
54
+ - Execução concorrente de até quatro tabelas.
55
+ - Uma única sessão Spark compartilhada entre todas as tarefas.
56
+ - Até três tentativas para falhas temporárias de leitura.
57
+ - Propagação de erros das tarefas para a thread principal.
58
+ - Driver JDBC do PostgreSQL incluído no pacote.
59
+ - Logs estruturados em inglês.
60
+
61
+ ## Como funciona
62
+
63
+ ```mermaid
64
+ flowchart LR
65
+ A["PostgreSQL de origem"] -->|"Descobre tabelas"| B["SQLAlchemy"]
66
+ B --> C["Serviço Spark Migration"]
67
+ C -->|"Uma tarefa por tabela"| D["Pool de threads"]
68
+ D --> E["Sessão Spark compartilhada"]
69
+ E -->|"Leitura JDBC"| A
70
+ E -->|"Escrita JDBC em append"| F["PostgreSQL de destino"]
71
+ D -->|"Resultado ou erro"| C
72
+ ```
73
+
74
+ 1. `Migration` formata as configurações de conexão da origem e do destino.
75
+ 2. O SQLAlchemy conecta-se à origem e descobre suas tabelas.
76
+ 3. O serviço cria uma tarefa para cada tabela encontrada.
77
+ 4. Cada tarefa lê sua tabela em um DataFrame Spark via JDBC.
78
+ 5. O Spark adiciona o DataFrame à tabela correspondente no destino.
79
+ 6. A thread principal aguarda os resultados e interrompe a execução quando uma tarefa falha.
80
+
81
+ ## Requisitos
82
+
83
+ - Python 3.10 ou superior.
84
+ - Java 17 disponível no `PATH`.
85
+ - Bancos PostgreSQL de origem e destino.
86
+ - Acesso de rede aos dois bancos.
87
+ - Permissão de leitura nas tabelas de origem.
88
+ - Permissões de criação e inserção no destino.
89
+
90
+ ## Instalação
91
+
92
+ Instale o Spark Migration e suas dependências diretamente pelo PyPI:
93
+
94
+ ```bash
95
+ pip install spark_migration
96
+ ```
97
+
98
+ Após a instalação, o framework é importado como `Migration`.
99
+
100
+ ## Uso rápido
101
+
102
+ Mantenha as credenciais fora do código-fonte. Este exemplo utiliza variáveis de ambiente:
103
+
104
+ ```python
105
+ import os
106
+
107
+ from Migration import Migration
108
+
109
+ origem = {
110
+ "host": os.environ["SOURCE_DB_HOST"],
111
+ "port": int(os.getenv("SOURCE_DB_PORT", "5432")),
112
+ "dbname": os.environ["SOURCE_DB_NAME"],
113
+ "user": os.environ["SOURCE_DB_USER"],
114
+ "password": os.environ["SOURCE_DB_PASSWORD"],
115
+ }
116
+
117
+ destino = {
118
+ "host": os.environ["DESTINATION_DB_HOST"],
119
+ "port": int(os.getenv("DESTINATION_DB_PORT", "5432")),
120
+ "dbname": os.environ["DESTINATION_DB_NAME"],
121
+ "user": os.environ["DESTINATION_DB_USER"],
122
+ "password": os.environ["DESTINATION_DB_PASSWORD"],
123
+ }
124
+
125
+ Migration(data=origem, new_data=destino).run()
126
+ ```
127
+
128
+ Execute o arquivo normalmente:
129
+
130
+ ```bash
131
+ python spark_migration.py
132
+ ```
133
+
134
+ Exemplo de saída:
135
+
136
+ ```text
137
+ INFO | Starting Spark session...
138
+ INFO | Spark session started.
139
+ INFO | Connecting to database postgres...
140
+ INFO | Discovered 1 table(s): orders
141
+ INFO | Starting database migration...
142
+ INFO | Reading table orders (attempt 1/3)
143
+ INFO | Table orders written successfully.
144
+ INFO | Database migration completed successfully.
145
+ ```
146
+
147
+ ## Configuração da conexão
148
+
149
+ Os parâmetros `data` e `new_data` recebem os mesmos campos:
150
+
151
+ | Campo | Tipo | Descrição |
152
+ | --- | --- | --- |
153
+ | `host` | `str` | Endereço ou IP do servidor PostgreSQL |
154
+ | `port` | `int` | Porta do PostgreSQL, normalmente `5432` |
155
+ | `dbname` | `str` | Nome do banco de dados |
156
+ | `user` | `str` | Usuário do banco |
157
+ | `password` | `str` | Senha do banco |
158
+
159
+ ## Comportamento da escrita
160
+
161
+ A versão atual utiliza o modo `append` do Spark.
162
+
163
+ - Se a tabela não existir no destino, o Spark a criará com base no DataFrame.
164
+ - Se a tabela já existir, os novos registros serão adicionados.
165
+ - Executar a mesma migração mais de uma vez pode duplicar os dados.
166
+ - Uma falha durante a escrita pode deixar dados parciais no destino.
167
+
168
+ Utilize um destino limpo para migrações completas e compare a quantidade de registros na origem e no destino antes de repetir uma execução.
169
+
170
+ ## Escopo e limitações atuais
171
+
172
+ O framework atualmente migra os registros das tabelas. Ele ainda não reproduz todos os objetos e configurações do PostgreSQL, incluindo:
173
+
174
+ - chaves primárias e estrangeiras;
175
+ - índices e restrições únicas;
176
+ - sequências e seus valores atuais;
177
+ - views e materialized views;
178
+ - triggers e funções armazenadas;
179
+ - usuários, permissões e políticas de segurança em nível de linha;
180
+ - extensões e configurações do banco.
181
+
182
+ Em migrações de produção, recrie e valide esses objetos separadamente.
183
+
184
+ ## Observações para Windows
185
+
186
+ O Spark pode informar que `winutils.exe`, `HADOOP_HOME` ou a biblioteca nativa do Hadoop não estão disponíveis. O framework carrega diretamente o driver JDBC incluído no pacote, portanto esses avisos não impedem o fluxo suportado de migração PostgreSQL.
187
+
188
+ Se o Spark não iniciar, confirme se o Java 17 está instalado:
189
+
190
+ ```powershell
191
+ java -version
192
+ ```
193
+
194
+ ## Solução de problemas
195
+
196
+ ### `Connection reset`
197
+
198
+ O banco ou o gerenciador de conexões encerrou a conexão JDBC. O framework repete automaticamente a leitura até três vezes. Se o problema continuar, verifique o estado do banco, a estabilidade da rede, os requisitos de SSL e os limites do pool de conexões.
199
+
200
+ ### `Migration failed`
201
+
202
+ A thread principal recebeu uma exceção de uma tarefa. Procure o primeiro erro de banco ou Spark exibido antes dessa mensagem; normalmente ele contém a causa real.
203
+
204
+ ### Porta da interface Spark ocupada
205
+
206
+ O Spark pode selecionar outra porta quando a porta local `4040` já estiver em uso. Isso é apenas informativo e normalmente não impede a migração.
207
+
208
+ ## Segurança
209
+
210
+ - Nunca inclua senhas de banco ou tokens do PyPI no repositório.
211
+ - Prefira variáveis de ambiente ou um gerenciador de segredos.
212
+ - Utilize usuários de banco dedicados e com as menores permissões necessárias.
213
+ - Troque credenciais que já tenham sido publicadas ou compartilhadas.
214
+
215
+ ## Status do projeto
216
+
217
+ Spark Migration está em fase **Beta**. A migração de registros entre bancos PostgreSQL está funcional. Modos de escrita idempotentes, migração completa do esquema, validação automática e suporte a outros bancos ainda estão em desenvolvimento.
218
+
219
+ ## Roadmap
220
+
221
+ - Modos configuráveis `append`, `overwrite` e falha se a tabela existir.
222
+ - Validação por quantidade de registros e checksum.
223
+ - Migração de esquema, restrições, índices e sequências.
224
+ - Filtros para incluir ou excluir tabelas.
225
+ - Configuração de concorrência e tentativas.
226
+ - Testes automatizados e integração contínua.
227
+ - Suporte a outros bancos compatíveis com JDBC.
@@ -0,0 +1,23 @@
1
+ README.md
2
+ pyproject.toml
3
+ Migration/__init__.py
4
+ Migration/drivers/postgresql-42.7.12.jar
5
+ Migration/engine/__init__.py
6
+ Migration/engine/connect.py
7
+ Migration/engine/init_spark.py
8
+ Migration/engine/init_task.py
9
+ Migration/logs/__init__.py
10
+ Migration/logs/global_logs.py
11
+ Migration/logs/logs.py
12
+ Migration/schema/__init__.py
13
+ Migration/schema/control.py
14
+ Migration/schema/model.py
15
+ Migration/service/__init__.py
16
+ Migration/service/main.py
17
+ Migration/tasks/__init__.py
18
+ Migration/tasks/task_db.py
19
+ spark_migration.egg-info/PKG-INFO
20
+ spark_migration.egg-info/SOURCES.txt
21
+ spark_migration.egg-info/dependency_links.txt
22
+ spark_migration.egg-info/requires.txt
23
+ spark_migration.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ pyspark<5,>=4.0
2
+ SQLAlchemy<3,>=2.0
3
+ psycopg2-binary<3,>=2.9