Un Data Lake, tres nubes: Analitica multi-cloud con DataSpoc
Tu empresa adquirio una startup que corre en GCP. Tu plataforma principal esta en AWS. El equipo de finanzas almacena exportaciones en Azure Blob Storage. Ahora necesitas unir datos de clientes de las tres nubes para un solo reporte.
DataSpoc Lens registra buckets de S3, GCS y Azure cómo un catálogo unificado. DuckDB consulta todos ellos con SQL estandar. Una consulta, tres nubes, sin movimiento de datos.
El escenario
Una empresa mediana con datos dispersos en proveedores de nube:
| Nube | Bucket | Datos | Equipo |
|---|---|---|---|
| AWS S3 | s3://acme-production | Datos core de la app (usuarios, ordenes) | Ingenieria |
| GCS | gs://acme-acquired-app | Datos del producto adquirido | Equipo de la ex-startup |
| Azure | az://acme-finance | Exportaciones financieras, facturas | Finanzas |
Cada equipo eligio su nube. Mover todo a un solo proveedor es un proyecto de 6 meses. Necesitas analitica ahora.
Paso 1: Registrar todos los buckets
# AWS S3 bucketdataspoc-lens add-bucket s3://acme-production --name production
# Google Cloud Storage bucketdataspoc-lens add-bucket gs://acme-acquired-app --name acquired
# Azure Blob Storage bucketdataspoc-lens add-bucket az://acme-finance/data --name financeVerifica que los tres estan conectados:
dataspoc-lens tablesproduction.raw.usersproduction.raw.ordersproduction.raw.productsproduction.curated.daily_revenueacquired.raw.app_usersacquired.raw.app_eventsacquired.raw.subscriptionsfinance.raw.invoicesfinance.raw.paymentsfinance.raw.budget_forecastTres nubes, un catálogo.
Paso 2: Configurar credenciales para cada proveedor
AWS S3
Usa un perfil de AWS o variables de entorno:
# Option 1: AWS profile (recommended)export AWS_PROFILE=production
# Option 2: Explicit credentialsexport AWS_ACCESS_KEY_ID="AKIA..."export AWS_SECRET_ACCESS_KEY="..."export AWS_REGION="us-east-1"O usa roles IAM si ejecutas en EC2/ECS — no se necesitan credenciales.
Google Cloud Storage
Usa una llave de service account o credenciales predeterminadas de aplicación:
# Option 1: Service account keyexport GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
# Option 2: Application default credentials (if on GCP or gcloud configured)gcloud auth application-default loginAzure Blob Storage
Usa una llave de storage account o identidad administrada:
# Option 1: Storage account keyexport AZURE_STORAGE_ACCOUNT="acmefinance"export AZURE_STORAGE_KEY="..."
# Option 2: Connection stringexport AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;AccountName=..."
# Option 3: Managed identity (if running on Azure)# No env vars needed -- uses Azure Instance MetadataDataSpoc Lens detecta el proveedor desde el prefijo URI del bucket (s3://, gs://, az://) y usa las credenciales correspondientes.
Paso 3: Consultas cross-cloud
Ahora la parte interesante. Consulta datos de las tres nubes con una sola sentencia SQL:
-- Match acquired app users with production users by emailSELECT p.name AS production_name, p.email, p.plan AS production_plan, a.subscription_tier AS acquired_plan, a.last_active AS acquired_last_active, f.total_invoicedFROM production.raw.users pJOIN acquired.raw.app_users a ON LOWER(p.email) = LOWER(a.email)LEFT JOIN ( SELECT customer_email, SUM(amount) AS total_invoiced FROM finance.raw.invoices GROUP BY customer_email) f ON LOWER(p.email) = LOWER(f.customer_email)ORDER BY f.total_invoiced DESC NULLS LASTLIMIT 20;Esta consulta une datos de AWS con datos de GCS y datos de Azure en una sola sentencia. DuckDB maneja las lecturas cross-cloud de forma transparente.
Ejecutala:
dataspoc-lens query " SELECT p.name, p.email, p.plan, a.subscription_tier, f.total_invoiced FROM production.raw.users p JOIN acquired.raw.app_users a ON LOWER(p.email) = LOWER(a.email) LEFT JOIN ( SELECT customer_email, SUM(amount) AS total_invoiced FROM finance.raw.invoices GROUP BY customer_email ) f ON LOWER(p.email) = LOWER(f.customer_email) ORDER BY f.total_invoiced DESC NULLS LAST LIMIT 10"┌─────────────────┬──────────────────────┬────────┬───────────────────┬────────────────┐│ name │ email │ plan │ subscription_tier │ total_invoiced │├─────────────────┼──────────────────────┼────────┼───────────────────┼────────────────┤│ Acme Corp │ admin@acme.com │ enterprise │ premium │ 125,400.00 ││ TechFlow Inc │ ops@techflow.io │ business │ pro │ 89,200.00 ││ Global Systems │ data@globalsys.com │ business │ premium │ 67,800.00 │└─────────────────┴──────────────────────┴────────┴───────────────────┴────────────────┘Paso 4: Cache para rendimiento
Las consultas cross-cloud pueden ser lentas porque los datos se leen por red desde tres proveedores diferentes. El cache resuelve esto:
# Cache the most-queried tables locallydataspoc-lens cache refresh production.raw.usersdataspoc-lens cache refresh production.raw.ordersdataspoc-lens cache refresh acquired.raw.app_usersdataspoc-lens cache refresh finance.raw.invoicesVerifica el estado del cache:
dataspoc-lens cache statusTable Status Size Last Refreshedproduction.raw.users cached 1.2 MB 2 min agoproduction.raw.orders cached 8.4 MB 2 min agoacquired.raw.app_users cached 3.1 MB 1 min agofinance.raw.invoices cached 5.6 MB 1 min agoproduction.raw.products stale 42 KB 3 days agoacquired.raw.app_events not cached -- --Las consultas cacheadas se ejecutan contra archivos Parquet locales — sin llamadas de red, sin latencia cross-cloud. El mismo JOIN cross-cloud que tomaba 12 segundos sin cache corre en 200ms con cache.
Refresca caches desactualizados en un solo comando:
dataspoc-lens cache refresh-stalePaso 5: Control de acceso por bucket via IAM
DataSpoc nunca implementa autenticacion. El IAM de cada proveedor de nube controla quien puede acceder a que:
AWS S3 (production): └── IAM Policy: DataEngineers group → full access └── IAM Policy: Analysts group → read-only
GCS (acquired): └── IAM Policy: IntegrationTeam → read-only └── IAM Policy: AcquiredTeam → full access
Azure (finance): └── IAM Policy: FinanceTeam → full access └── IAM Policy: Executives → read-onlyCuando un analista ejecuta una consulta que toca el bucket de finanzas, necesita credenciales de Azure con acceso de lectura. Si no las tiene, la consulta falla con un error de permisos. DataSpoc no intenta sortear el IAM de la nube — lo respeta.
Ejemplo practico: Customer 360 unificado
Construye una vista completa de cliente a traves de las tres nubes:
-- transforms/01_customer_360.sqlCREATE OR REPLACE TABLE gold.customer_360 ASWITH production_data AS ( SELECT LOWER(email) AS email, name, plan AS production_plan, created_at AS production_signup, DATE_DIFF('day', created_at, CURRENT_DATE) AS days_as_customer FROM production.raw.users),acquired_data AS ( SELECT LOWER(email) AS email, subscription_tier AS acquired_plan, last_active AS acquired_last_active, feature_usage_score FROM acquired.raw.app_users),finance_data AS ( SELECT LOWER(customer_email) AS email, SUM(amount) AS total_invoiced, COUNT(*) AS invoice_count, MAX(invoice_date) AS last_invoice_date FROM finance.raw.invoices WHERE status = 'paid' GROUP BY 1),order_data AS ( SELECT LOWER(u.email) AS email, COUNT(*) AS total_orders, SUM(o.amount) AS total_revenue, MAX(o.order_date) AS last_order_date FROM production.raw.orders o JOIN production.raw.users u ON o.user_id = u.user_id WHERE o.status = 'completed' GROUP BY 1)SELECT p.email, p.name, p.production_plan, p.production_signup, p.days_as_customer, a.acquired_plan, a.acquired_last_active, a.feature_usage_score, f.total_invoiced, f.invoice_count, o.total_orders, o.total_revenue, o.last_order_date, CASE WHEN a.email IS NOT NULL THEN 'both_products' ELSE 'production_only' END AS product_usage, COALESCE(o.total_revenue, 0) + COALESCE(f.total_invoiced, 0) AS total_ltvFROM production_data pLEFT JOIN acquired_data a ON p.email = a.emailLEFT JOIN finance_data f ON p.email = f.emailLEFT JOIN order_data o ON p.email = o.email;Ejecuta esta transformacion:
dataspoc-lens transform run --file 01_customer_360.sqlAhora consulta la vista unificada:
dataspoc-lens query " SELECT product_usage, COUNT(*) as customers, ROUND(AVG(total_ltv), 2) as avg_ltv FROM gold.customer_360 GROUP BY product_usage"┌────────────────┬───────────┬──────────┐│ product_usage │ customers │ avg_ltv │├────────────────┼───────────┼──────────┤│ both_products │ 2,340 │ 1,245.80 ││ production_only│ 10,110 │ 342.50 │└────────────────┴───────────┴──────────┘Los clientes que usan ambos productos tienen 3.6x mayor LTV. Esa es una oportunidad de venta cruzada que solo podrias encontrar consultando a traves de las nubes.
Uso del SDK
La misma configuración multi-cloud funcióna desde Python:
from dataspoc_lens import LensClient
lens = LensClient()
# Tables from all clouds appear in one catalogtables = lens.tables()print(tables)# ['production.raw.users', 'acquired.raw.app_users', 'finance.raw.invoices', ...]
# Cross-cloud queryresult = lens.query(""" SELECT COUNT(DISTINCT p.email) AS overlap FROM production.raw.users p JOIN acquired.raw.app_users a ON LOWER(p.email) = LOWER(a.email)""")print(f"Users on both platforms: {result}")Consideraciones de costo
La transferencia de datos cross-cloud cuesta dinero. Esto es lo que puedes esperar:
| Transferencia | Costo por GB |
|---|---|
| S3 a internet | $0.09 |
| GCS a internet | $0.12 |
| Azure a internet | $0.087 |
| Cache local (despues de la primera lectura) | $0.00 |
Estrategia para minimizar costos:
- Cachea agresivamente — la primera consulta lee de la nube, las consultas subsiguientes leen del cache local
- Refresca cache por programacion —
dataspoc-lens cache refresh-staleuna vez por hora - Pre-agrega — construye tablas gold que resuman datos cross-cloud, cachea solo las tablas gold
- Filtra temprano — clausulas
WHEREcon pushdown de Parquet reducen los bytes leidos
Para una empresa con 10 GB a traves de tres nubes, refrescar cache diariamente cuesta apróximadamente $3/mes en egress. Eso es mas barato que un solo credito de Snowflake.
Un catálogo, tres nubes, cero movimiento de datos. Registra tus buckets, escribe SQL y deja que DuckDB se encargue del resto.