Skip to content
GitHub

ETL Troubleshooting

Guía de diagnóstico y resolución de problemas comunes en el pipeline ETL Python que sincroniza datos desde el SII y otras fuentes externas.

Referencia: ADR-003: ETL Architecture


┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Sources │────▶│ ETL Python │────▶│ PostgreSQL │
│ (SII, CSV) │ │ Pipeline │ │ (nostromo) │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────┐
│ Logs │
│ /var/log/etl│
└─────────────┘

  • Cron job no ejecuta
  • Script falla al iniciar
  • No hay logs recientes
Terminal window
# Verificar cron configurado
crontab -l | grep etl
# Ejecutar manualmente
cd /opt/nostromo/etl
python3 main.py --verbose
# Verificar permisos
ls -la /opt/nostromo/etl/
CausaSolución
Cron no configuradocrontab -e y agregar job
Python no encontradoUsar path absoluto /usr/bin/python3
Permisos insuficienteschmod +x main.py
Virtual env no activadosource venv/bin/activate en cron

Ejemplo cron correcto:

0 3 * * * cd /opt/nostromo/etl && source venv/bin/activate && python main.py >> /var/log/etl/cron.log 2>&1

  • psycopg2.OperationalError: could not connect to server
  • Connection refused
  • Authentication failed
Terminal window
# Desde el servidor ETL
pg_isready -h localhost -p 5432
# Verificar credenciales
cat /opt/nostromo/etl/.env | grep DB_
# Test de conexión
psql -h localhost -U etl_user -d nostromo -c "SELECT 1;"
CausaSolución
PostgreSQL caídosudo systemctl start postgresql
Credenciales incorrectasVerificar .env vs pg_hba.conf
Firewall bloqueasudo ufw allow from <ETL_IP> to any port 5432
SSL requeridoAgregar sslmode=require a connection string

  • Job tarda más de 2 horas
  • Alto uso de CPU/memoria
  • Timeouts en queries
Terminal window
# Ver queries activas
sudo -u postgres psql -c "SELECT pid, now() - query_start as duration, query
FROM pg_stat_activity
WHERE state = 'active' AND query NOT LIKE '%pg_stat%'
ORDER BY duration DESC;"
# Monitorear proceso ETL
top -p $(pgrep -f etl)
CausaSolución
Sin índicesCrear índices en tablas destino
Batch size pequeñoAumentar ETL_BATCH_SIZE
Sin paralelismoHabilitar ETL_PARALLEL_WORKERS
Lock contentionEjecutar en horario de bajo tráfico

Optimizaciones recomendadas:

config.py
ETL_BATCH_SIZE = 10000 # Default: 1000
ETL_PARALLEL_WORKERS = 4 # Default: 1
ETL_COMMIT_INTERVAL = 5000 # Commit cada N registros

  • Registros duplicados en tablas destino
  • Conteos inconsistentes
  • Errores de constraint unique
-- Buscar duplicados por RUT
SELECT rut, COUNT(*)
FROM employees
GROUP BY rut
HAVING COUNT(*) > 1;
-- Ver últimos inserts
SELECT id, rut, created_at
FROM employees
ORDER BY created_at DESC
LIMIT 20;
CausaSolución
Falta upsertUsar ON CONFLICT DO UPDATE
ID no únicoAgregar constraint UNIQUE
Re-ejecución accidentalTracking de estado de jobs

Patrón upsert correcto:

etl/loaders/employees.py
INSERT_QUERY = """
INSERT INTO employees (rut, name, email)
VALUES (%s, %s, %s)
ON CONFLICT (rut) DO UPDATE SET
name = EXCLUDED.name,
email = EXCLUDED.email,
updated_at = NOW()
"""

  • Registros no importados
  • Campos nulos inesperados
  • Conteo menor al esperado
Terminal window
# Verificar archivo fuente
wc -l /opt/data/import_*.csv
# Comparar con registros importados
psql -c "SELECT COUNT(*) FROM staging.import_log WHERE status = 'error';"
# Ver errores específicos
tail -100 /var/log/etl/errors.log
CausaSolución
Encoding incorrectoForzar encoding='utf-8' o latin-1
Formato fecha inválidoNormalizar fechas antes de insert
Validación fallidaRevisar reglas en validators.py
Archivo truncadoVerificar integridad de fuente

Logging de errores:

etl/transforms/base.py
def transform_row(row):
try:
return validate_and_transform(row)
except ValidationError as e:
logger.error(f"Row {row['id']} failed: {e}")
error_log.append(row)
return None # Skip row

  • ConnectionError: Max retries exceeded
  • HTTPError: 503 Service Unavailable
  • SSLError: certificate verify failed
Terminal window
# Test de conectividad
curl -v https://api.sii.cl/health
# Verificar certificados
openssl s_client -connect api.sii.cl:443 -servername api.sii.cl
# Ver rate limits
grep "rate limit" /var/log/etl/sii.log
CausaSolución
SII caídoReintentar con backoff exponencial
Rate limitReducir SII_REQUESTS_PER_MINUTE
Certificado expiradoActualizar ca-certificates
IP bloqueadaContactar SII soporte

Implementar retry:

etl/clients/sii.py
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(5),
wait=wait_exponential(multiplier=1, min=4, max=60)
)
def fetch_from_sii(endpoint):
response = requests.get(f"{SII_BASE_URL}/{endpoint}")
response.raise_for_status()
return response.json()

  • Column "X" does not exist
  • Value too long for type character varying(50)
  • Invalid input syntax for type integer
-- Ver schema actual
\d employees
-- Comparar con fuente
SELECT column_name, data_type, character_maximum_length
FROM information_schema.columns
WHERE table_name = 'employees';
CausaSolución
Columna nueva en fuenteAgregar migración ALTER TABLE
Tipo incorrectoCast explícito en transformación
Longitud insuficienteALTER TABLE ... ALTER COLUMN ... TYPE VARCHAR(100)

LogPathContenido
Main/var/log/etl/main.logEjecución general
Errors/var/log/etl/errors.logSolo errores
SII/var/log/etl/sii.logLlamadas a API SII
Cron/var/log/etl/cron.logOutput de cron jobs
Terminal window
# Ver últimos errores
tail -f /var/log/etl/errors.log
# Buscar errores específicos
grep -i "connection" /var/log/etl/main.log | tail -20
# Contar errores por tipo
grep -oP "ERROR: \K[^:]*" /var/log/etl/errors.log | sort | uniq -c
Terminal window
# Agregar a cron para alertar si ETL falla
0 5 * * * /opt/nostromo/etl/check_status.sh || mail -s "ETL FAILED" [email protected]

-- Si ETL corrompió datos, rollback desde staging
BEGIN;
-- Borrar datos del día
DELETE FROM employees
WHERE updated_at >= CURRENT_DATE;
-- Restaurar desde staging/backup
INSERT INTO employees
SELECT * FROM staging.employees_backup
WHERE backup_date = CURRENT_DATE - 1;
COMMIT;
Terminal window
# Re-procesar solo un día específico
python main.py --date 2026-01-15
# Re-procesar solo un módulo
python main.py --module indicadores
# Dry run (sin escribir a DB)
python main.py --dry-run

  • PostgreSQL accesible: pg_isready
  • Credenciales válidas: verificar .env
  • Espacio en disco: df -h
  • APIs externas accesibles: curl https://api.sii.cl
  • Backup del día anterior existe
  • Logs rotados: logrotate -f


FechaVersionCambios
2026-01-181.0Guía inicial creada