Saltar a contenido

UD 5 - Apache Hadoop - Cluster Docker + Hive

En este apartado se explica cómo desplegar Apache Hive sobre el clúster Hadoop que hemos creado previamente utilizando Docker Compose.

1. Arquitectura de Servicios: Desacoplamiento de Componentes

Para un despliegue robusto y escalable, adoptaremos una arquitectura de servicios desacoplados. Separaremos la Gestión de Metadatos del Motor de Ejecución.

📚 Recursos Oficiales

Para profundizar en las herramientas que vamos a desplegar, consulta las fuentes oficiales:

1.1. Componentes del Despliegue

Utilizaremos dos imágenes de contenedor distintas, cada una con una responsabilidad única (Principio de Responsabilidad Única):

A. Hive Metastore (apache/hive:standalone-metastore-4.2.0)

El Hive Metastore Service (HMS) es un servicio fundamental en el ecosistema Big Data.

  • Función: Actúa como el repositorio central de esquemas. Almacena la definición de las tablas, tipos de datos, particiones y la ubicación física de los archivos en HDFS.
  • Abstracción: No procesa datos. Su función es servir metadatos a cualquier motor de cómputo que lo solicite (Hive, Spark, Presto/Trino, Flink).
  • Justificación de Uso: Usamos la imagen standalone-metastore porque es una versión ligera y optimizada que contiene únicamente los binarios necesarios para servir metadatos a través del protocolo Thrift (thrift://). Esto reduce la superficie de ataque y el consumo de recursos en comparación con la imagen completa.

B. HiveServer2 (apache/hive:4.2.0)

Este componente representa el motor de ejecución SQL.

  • Función: Permite a los clientes (Beeline, DBeaver, Tableau) ejecutar consultas SQL estandarizadas.
  • Flujo: Recibe la consulta SQL, consulta al Metastore para validar la semántica, compila un plan de ejecución (DAG) y lo envía al clúster YARN para su procesamiento distribuido.
  • Justificación de Uso: Esta imagen contiene el stack completo de Hive, incluyendo el compilador de consultas y los drivers de ejecución necesarios para interactuar con el Resource Manager.

C. PostgreSQL (postgres:18)

El almacén de persistencia para el Metastore.

  • Función: Base de datos relacional (RDBMS) que guarda el estado del catálogo de manera transaccional.
  • Justificación: En entornos productivos, la base de datos embebida por defecto (Derby) no soporta concurrencia (múltiples usuarios simultáneos). PostgreSQL 18 ofrece robustez, conformidad ACID y capacidad para gestionar catálogos con millones de particiones.

2. Ajuste Crítico en Hadoop (hadoop.env)

Al añadir servicios pesados como Hive y Postgres, la red interna de Docker experimenta mayor latencia. Esto puede provocar un error conocido donde los DataNodes no logran registrarse en el NameNode (DisallowedDatanodeException) debido a fallos en la resolución DNS inversa, ya que la búsqueda DNS Inversa (Reverse DNS Lookup) puede que todavía no haya propagado los nombres de host e intenta acceder a una ip no resuelta todavía por Docker.

🛠️ Solución: Relajamos la seguridad DNS (Lab Mode)

En un entorno Docker interno (bridge), confiamos en la red. No necesitamos que el NameNode esté comprobando identidades DNS. Vamos a desactivar esa comprobación.

Por tanto, si te este error, para solucionarlo, edita tu archivo hadoop.env existente y añade la siguiente línea al final de la sección HDFS-SITE para relajar esta comprobación en el entorno de laboratorio:

hadoop.env
# --- SOLUCIÓN Posibles errores DNS DATANODES ---
# Desactivamos la comprobación de resolución DNS inversa para evitar que 
# los DataNodes sean rechazados en redes docker saturadas.
HDFS-SITE.XML_dfs.namenode.datanode.registration.ip-hostname-check=false

3. Preparación de Librerías y Drivers

Las imágenes oficiales de Hive son "limpias" y no incluyen drivers de bases de datos externas ni ciertas librerías de Hadoop YARN necesarias para tareas de mantenimiento en la versión 4.2.0. Debemos descargarlas manualmente e inyectarlas como volúmenes.

1. Crear estructura de directorios

En la raíz de tu proyecto (al mismo nivel que docker-compose.yaml), crea una carpeta llamada drivers.

2. Descargar dependencias

Ejecuta los siguientes comandos para descargar el driver JDBC de PostgreSQL y los parches de YARN necesarios para evitar el error java.lang.NoClassDefFoundError: org/apache/hadoop/yarn/util/SystemClock.

Note

Al parecer, Hive 4.2.0 tiene una tarea de limpieza (HiveProtoEventsCleanerTask) que depende de clases de YARN, pero los desarrolladores de la imagen de Docker no incluyeron los JARs de YARN para ahorrar espacio. 🛠️ La Solución: Necesitamos dos archivos .jar de Hadoop 3.4.1 (Common y API) para evitar el error.

mkdir -p drivers
cd drivers

# 1. Driver JDBC para PostgreSQL (Versión 42.7.8)
curl -O https://jdbc.postgresql.org/download/postgresql-42.7.8.jar
# Si estas en Windows Powershell y no funciona el comando anterior, usa este:
curl https://jdbc.postgresql.org/download/postgresql-42.7.8.jar -OutFile postgresql-42.7.8.jar

# 2. Librería Hadoop YARN Common (Necesaria para Hive 4.2.0 Housekeeping)
curl -O https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-yarn-common/3.4.1/hadoop-yarn-common-3.4.1.jar
# Si estas en Windows Powershell y no funciona el comando anterior, usa este:
curl https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-yarn-common/3.4.1/hadoop-yarn-common-3.4.1.jar -OutFile hadoop-yarn-common-3.4.1.jar

# 3. Librería Hadoop YARN API (Dependencia)
curl -O https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-yarn-api/3.4.1/hadoop-yarn-api-3.4.1.jar
# Si estas en Windows Powershell y no funciona el comando anterior, usa este:
curl https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-yarn-api/3.4.1/hadoop-yarn-api-3.4.1.jar -OutFile hadoop-yarn-api-3.4.1.jar

cd ..

Warning

Si estas usando Windows Powershell, y salta algún tipo de error a la hora de descargar los ficheros, usa el el parámetro -OutFile y el mismo nombre del fichero que queráis descargar, tal y como aparece en el punto anterior. Por ejemplo:

curl https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-yarn-api/3.4.1/hadoop-yarn-api-3.4.1.jar -OutFile hadoop-yarn-api-3.4.1.jar
Si este es tu caso, hazlo cada vez que veas un comando curl -O <URL> o wget <URL> en este documento.

4. Implementación en Docker Compose

Procedemos a extender nuestro manifiesto docker-compose.yaml. Añadiremos los tres servicios nuevos, asegurándonos de que compartan la red bda-network para comunicarse con Hadoop.

Observa el uso de SERVICE_OPTS: inyectamos la configuración de Hive directamente en el comando de arranque de Java, evitando problemas con archivos XML. Consulta la info oficial de Hive QuickStarted with docker para más detalles.

Añade el siguiente bloque a tu sección services:

    # --- CAPA DE PERSISTENCIA RELACIONAL (Catalog Database) ---
    postgres:
      image: postgres:18
      container_name: postgres
      networks:
        - bda-network
      environment:
        POSTGRES_DB: metastore_db
        POSTGRES_USER: hive
        POSTGRES_PASSWORD: hive_password
        # Le decimos a Postgres dónde poner los datos exactamente (Necesario para Postgres 18+)
        PGDATA: /var/lib/postgresql/data/pgdata
      volumes:
        - postgres_data:/var/lib/postgresql/data
      # healthcheck declara una verificación que se ejecuta para determinar si los contenedores de servicio están "healthy" o no.
      healthcheck:
        test: ["CMD-SHELL", "pg_isready -U hive -d metastore_db"]
        interval: 10s
        timeout: 5s
        retries: 5

    # --- SERVICIO DE METADATOS (Hive Metastore) ---
    metastore:
      image: apache/hive:standalone-metastore-4.2.0
      container_name: metastore
      networks:
        - bda-network
      environment:
        SERVICE_NAME: metastore
        # Credenciales explícitas para el script de inicio de la imagen
        DB_DRIVER: postgres
        # Configuración JAVA directa (Sin XMLs intermedios para la DB)
        SERVICE_OPTS: >-
            -Xmx1G 
            -Djavax.jdo.option.ConnectionDriverName=org.postgresql.Driver
            -Djavax.jdo.option.ConnectionURL=jdbc:postgresql://postgres:5432/metastore_db
            -Djavax.jdo.option.ConnectionUserName=hive
            -Djavax.jdo.option.ConnectionPassword=hive_password
            -Dhive.metastore.warehouse.dir=hdfs://namenode:9000/user/hive/warehouse
            -Dfs.defaultFS=hdfs://namenode:9000
      depends_on:
        postgres:
          # Establece la condición bajo la cual se considera satisfecha la dependencia (https://docs.docker.com/reference/compose-file/services/#depends_on)
          condition: service_healthy
        namenode:
          condition: service_started
      ports:
        - "9083:9083" # Puerto Thrift expuesto para clientes externos
      volumes:
        # Inyección del Driver postgres y librerias YARN (Fix SystemClock Error)
        - ./drivers/postgresql-42.7.8.jar:/opt/hive/lib/postgresql-jdbc.jar
        # --- NUEVOS PARCHES PARA YARN (Fix SystemClock Error) ---
        - ./drivers/hadoop-yarn-common-3.4.1.jar:/opt/hive/lib/hadoop-yarn-common-3.4.1.jar
        - ./drivers/hadoop-yarn-api-3.4.1.jar:/opt/hive/lib/hadoop-yarn-api-3.4.1.jar

    # --- MOTOR DE EJECUCIÓN SQL (HiveServer2) ---
    hiveserver2:
      image: apache/hive:4.2.0
      container_name: hiveserver2
      networks:
        - bda-network
      environment:
        SERVICE_NAME: hiveserver2
        TEZ_CONTAINER_SIZE: 512 # Ajuste de memoria para contenedores Tez
        # Evita re-inicializar el esquema (ya lo hace el metastore)
        IS_RESUME: "true"
        # Configuración: Conéctate al metastore remoto y usa HDFS
        SERVICE_OPTS: >-
            -Dhive.metastore.uris=thrift://metastore:9083
            -Dhive.metastore.warehouse.dir=hdfs://namenode:9000/user/hive/warehouse
            -Dfs.defaultFS=hdfs://namenode:9000
      depends_on:
        metastore:
          condition: service_started
        resourcemanager:
          condition: service_started
      ports:
        - "10000:10000" # Puerto JDBC (Beeline/DBeaver)
        - "10002:10002" # Web UI de HiveServer2
      labels:
        - "traefik.enable=true"
        # Acceso HTTP a la UI de Hive
        - "traefik.http.routers.hive.rule=Host(`hive.localhost`)"
        - "traefik.http.services.hive.loadbalancer.server.port=10002"
        - "traefik.http.routers.hive.entrypoints=web"
        # Desactivando el paso de la cabecera Host (passHostHeader = false).
        # Le decimos a Traefik que NO pase el nombre de dominio original al contenedor.
        # Así, Traefik reescribirá la cabecera Host para que coincida con la IP interna del contenedor y el puerto 10002.
        # Esto evita que Jetty de error en hiveserver2 por recibir tráfico del puerto 80.
        - "traefik.http.services.hive.loadbalancer.passhostheader=false"

No olvides añadir el nuevo volumen para PostgreSQL en la sección volumes:

volumes:
  # ... volúmenes de hadoop ...
  postgres_data: # Persistencia del catálogo

Por tanto, el docker compose completo junto con el cluster de Hadoop sería:

networks:
    bda-network:
        driver: bridge
        name: bda-network

volumes:
    namenode_data:
    secondary_namenode_data:
    datanode1_data:
    datanode2_data:
    datanode3_data:
    portainer_data:
    postgres_data: # Persistencia del catálogo
services:
    # --- INFRAESTRUCTURA DEVOPS ---

    # --- TRAEFIK: Reverse Proxy y Gestor de Rutas ---
    # Traefik actúa como el único punto de entrada (puerta de enlace) para nuestro cluster.
    # Su función es interceptar todas las peticiones HTTP (en el puerto 80) y, basándose
    # en el dominio solicitado (ej. 'portainer.localhost'), redirigir el tráfico al 
    # contenedor correcto de forma automática. Esto nos evita tener que gestionar y recordar
    # un puerto diferente para cada servicio. El dashboard en el puerto 8089 nos permite
    # ver las rutas que ha descubierto y si están activas.
    #Si quiere añadirlo a más servicios, usa la etiqueta 'labels' para definir las reglas de Traefik, como está en portainer y namenode
    traefik:
        image: traefik:v3.6.2
        container_name: traefik
        command:
        - "--api.insecure=true" # Habilita el dashboard inseguro para desarrollo
        - "--providers.docker=true" # Escucha eventos de Docker
        - "--providers.docker.exposedbydefault=false" # Seguridad: No exponer nada automáticamente
        - "--entrypoints.web.address=:80" # Punto de entrada HTTP estándar
        ports:
        - "80:80"       # Peticiones HTTP del host
        - "8089:8080"   # Dashboard de administración de Traefik
        volumes:
        - "/var/run/docker.sock:/var/run/docker.sock:ro" # Traefik necesita acceso al socket de Docker
        networks:
        - bda-network

    # --- PORTAINER: Interfaz Gráfica para Docker ---
    # Portainer nos da una UI web para gestionar de forma visual nuestros contenedores,
    # imágenes, redes y volúmenes, facilitando la administración del entorno Docker.
    #
    # Este servicio está configurado para ser accesible de dos maneras:
    # 1. Acceso Directo: A través del puerto 9000 (http://localhost:9000). Esto es gracias
    #    a la sección 'ports'. Es un acceso fiable y directo.
    # 2. Acceso vía Traefik: Las 'labels' definen la regla para acceder por el dominio
    #    http://portainer.localhost. Esto permite unificar el acceso a todos los servicios
    #    bajo el mismo proxy inverso, usando nombres amigables en lugar de puertos
    portainer:
        image: portainer/portainer-ce:latest
        container_name: portainer
        networks:
        - bda-network
        ports:
            # Mapeo de puertos directo: <PUERTO_EN_EL_HOST>:<PUERTO_EN_EL_CONTENEDOR>
            # Exponemos la UI de Portainer (puerto 9000) en el puerto 9010 de nuestra máquina para evitar conflictos con el 9000 del namenode
            - "9010:9000"
        volumes:
        - "/var/run/docker.sock:/var/run/docker.sock:ro" # Acceso al socket de Docker
        - portainer_data:/data # Volumen para persistir la data de Portainer
        command: -H unix:///var/run/docker.sock
        labels:
            # Activamos Traefik para este contenedor
            - "traefik.enable=true"
            # Regla: si el host es 'portainer.localhost', reenvía a este servicio
            - "traefik.http.routers.portainer.rule=Host(`portainer.localhost`)"
            # Definimos el punto de entrada (entrypoint) como 'web' (puerto 80)
            - "traefik.http.routers.portainer.entrypoints=web"
            # IMPORTANTE: Decirle a Traefik cuál es el puerto interno del servicio web (9000)
            - "traefik.http.services.portainer.loadbalancer.server.port=9000"

    # --- SERVICIOS BIG DATA (Con etiquetas Traefik) ---
    # --- CAPA DE ALMACENAMIENTO (HDFS)  ---
    namenode:
        image: apache/hadoop:3.4.1
        container_name: namenode
        hostname: namenode
        user: root
        networks:
            bda-network:
                aliases:
                    - cluster-bda # Alias for the namenode service. Para que así pueda resolver docker. Docker registra ese nombre de host en la red.
        ports:
            - "9870:9870" # UI Web
        env_file:
            - ./hadoop.env
        #environment:
            # Esta variable formatea el NameNode si la carpeta está vacía
            #- ENSURE_NAMENODE_DIR="/opt/hadoop/hadoop_data/hdfs/namenode"
        volumes:
            - namenode_data:/opt/hadoop/hadoop_data/hdfs/namenode
        #command: ["hdfs", "namenode"]
        command: 
        - "/bin/bash"
        - "-c"
        - "if [ ! -d /opt/hadoop/hadoop_data/hdfs/namenode/current ]; then echo '--- FORMATTING NAMENODE (FRESH START) ---'; hdfs namenode -format -nonInteractive; else echo '--- NAMENODE DATA FOUND (NO FORMAT) ---'; fi; hdfs namenode"
        labels:
            # Activamos Traefik para este contenedor
            - "traefik.enable=true"
            # Regla de enrutamiento: responder a namenode.localhost
            - "traefik.http.routers.namenode.rule=Host(`namenode.localhost`)"
            # IMPORTANTE: Decirle a Traefik cuál es el puerto interno del servicio web (9870)
            - "traefik.http.services.namenode.loadbalancer.server.port=9870"


    secondary_namenode:
        image: apache/hadoop:3.4.1
        container_name: secondary_namenode
        hostname: secondary_namenode
        user: root
        networks:
            - bda-network
        ports:
            - "9868:9868"
        env_file:
            - ./hadoop.env
        depends_on:
            - namenode
        volumes:
            - secondary_namenode_data:/opt/hadoop/hadoop_data/hdfs/secondary_namenode
        command: ["hdfs", "secondarynamenode"]

    datanode1:
        image: apache/hadoop:3.4.1
        container_name: datanode1
        hostname: datanode1
        user: root
        networks:
            - bda-network
        env_file:
            - ./hadoop.env
        depends_on:
            - namenode
        volumes:
            - datanode1_data:/opt/hadoop/hadoop_data/hdfs/datanode
        command: ["hdfs", "datanode"]

    datanode2:
        image: apache/hadoop:3.4.1
        container_name: datanode2
        hostname: datanode2
        user: root
        networks:
            - bda-network
        env_file:
            - ./hadoop.env
        depends_on:
            - namenode
        volumes:
            - datanode2_data:/opt/hadoop/hadoop_data/hdfs/datanode
        command: ["hdfs", "datanode"]

    datanode3:
        image: apache/hadoop:3.4.1
        container_name: datanode3
        hostname: datanode3
        user: root
        networks:
            - bda-network
        env_file:
            - ./hadoop.env
        depends_on:
            - namenode
        volumes:
            - datanode3_data:/opt/hadoop/hadoop_data/hdfs/datanode
        command: ["hdfs", "datanode"]


    # --- CAPA DE PROCESAMIENTO (YARN) ---

    resourcemanager:
        image: apache/hadoop:3.4.1
        container_name: resourcemanager
        hostname: resourcemanager
        user: root
        networks:
            - bda-network
        ports:
            - "8088:8088" # YARN ResourceManager Web UI
            - "8032:8032" # YARN ResourceManager RPC
        env_file:
            - ./hadoop.env
        depends_on:
            - namenode
        command: ["yarn", "resourcemanager"]
        labels:
            - "traefik.enable=true"
            - "traefik.http.routers.resourcemanager.rule=Host(`yarn.localhost`)"
            - "traefik.http.services.resourcemanager.loadbalancer.server.port=8088"

    # NodeManager asociado a DataNode1
    nodemanager1:
        image: apache/hadoop:3.4.1
        container_name: nodemanager1
        hostname: nodemanager1
        user: root
        networks:
            - bda-network
        ports:
            - "8042:8042" # Puerto único para la Web UI del NM1
        env_file:
            - ./hadoop.env
        depends_on:
            - resourcemanager
            - namenode
        command: ["yarn", "nodemanager"]

    # NodeManager asociado a DataNode2
    nodemanager2:
        image: apache/hadoop:3.4.1
        container_name: nodemanager2
        hostname: nodemanager2
        user: root
        networks:
            - bda-network
        ports:
            - "8043:8042" # Puerto único para la Web UI del NM2
        env_file:
            - ./hadoop.env
        depends_on:
            - resourcemanager
            - namenode
        command: ["yarn", "nodemanager"]

    # NodeManager asociado a DataNode3
    nodemanager3:
        image: apache/hadoop:3.4.1
        container_name: nodemanager3
        hostname: nodemanager3
        user: root
        networks:
            - bda-network
        ports:
            - "8044:8042" # Puerto único para la Web UI del NM3
        env_file:
            - ./hadoop.env
        depends_on:
            - resourcemanager
            - namenode
        command: ["yarn", "nodemanager"]

    # --- CAPA DE PERSISTENCIA RELACIONAL (Catalog Database) ---
    postgres:
      image: postgres:18
      container_name: postgres
      networks:
        - bda-network
      environment:
        POSTGRES_DB: metastore_db
        POSTGRES_USER: hive
        POSTGRES_PASSWORD: hive_password
        # Le decimos a Postgres dónde poner los datos exactamente (Necesario para Postgres 18+)
        PGDATA: /var/lib/postgresql/data/pgdata
      volumes:
        - postgres_data:/var/lib/postgresql/data
      # healthcheck declara una verificación que se ejecuta para determinar si los contenedores de servicio están "healthy" o no.
      healthcheck:
        test: ["CMD-SHELL", "pg_isready -U hive -d metastore_db"]
        interval: 10s
        timeout: 5s
        retries: 5

    # --- SERVICIO DE METADATOS (Hive Metastore) ---
    metastore:
      image: apache/hive:standalone-metastore-4.2.0
      container_name: metastore
      networks:
        - bda-network
      environment:
        SERVICE_NAME: metastore
        # Credenciales explícitas para el script de inicio de la imagen
        DB_DRIVER: postgres
        # Configuración JAVA directa (Sin XMLs intermedios para la DB)
        SERVICE_OPTS: >-
            -Xmx1G 
            -Djavax.jdo.option.ConnectionDriverName=org.postgresql.Driver
            -Djavax.jdo.option.ConnectionURL=jdbc:postgresql://postgres:5432/metastore_db
            -Djavax.jdo.option.ConnectionUserName=hive
            -Djavax.jdo.option.ConnectionPassword=hive_password
            -Dhive.metastore.warehouse.dir=hdfs://namenode:9000/user/hive/warehouse
            -Dfs.defaultFS=hdfs://namenode:9000
      depends_on:
        postgres:
          # Establece la condición bajo la cual se considera satisfecha la dependencia (https://docs.docker.com/reference/compose-file/services/#depends_on)
          condition: service_healthy
        namenode:
          condition: service_started
      ports:
        - "9083:9083" # Puerto Thrift expuesto para clientes externos
      volumes:
        # Inyección del Driver postgres y librerias YARN (Fix SystemClock Error)
        - ./drivers/postgresql-42.7.8.jar:/opt/hive/lib/postgresql-jdbc.jar
        # --- NUEVOS PARCHES PARA YARN (Fix SystemClock Error) ---
        - ./drivers/hadoop-yarn-common-3.4.1.jar:/opt/hive/lib/hadoop-yarn-common-3.4.1.jar
        - ./drivers/hadoop-yarn-api-3.4.1.jar:/opt/hive/lib/hadoop-yarn-api-3.4.1.jar

    # --- MOTOR DE EJECUCIÓN SQL (HiveServer2) ---
    hiveserver2:
      image: apache/hive:4.2.0
      container_name: hiveserver2
      networks:
        - bda-network
      environment:
        SERVICE_NAME: hiveserver2
        TEZ_CONTAINER_SIZE: 512 # Ajuste de memoria para contenedores Tez
        # Evita re-inicializar el esquema (ya lo hace el metastore)
        IS_RESUME: "true"
        # Configuración: Conéctate al metastore remoto y usa HDFS
        SERVICE_OPTS: >-
            -Dhive.metastore.uris=thrift://metastore:9083
            -Dhive.metastore.warehouse.dir=hdfs://namenode:9000/user/hive/warehouse
            -Dfs.defaultFS=hdfs://namenode:9000
      depends_on:
        metastore:
          condition: service_started
        resourcemanager:
          condition: service_started
      ports:
        - "10000:10000" # Puerto JDBC (Beeline/DBeaver)
        - "10002:10002" # Web UI de HiveServer2
      labels:
        - "traefik.enable=true"
        # Acceso HTTP a la UI de Hive
        - "traefik.http.routers.hive.rule=Host(`hive.localhost`)"
        - "traefik.http.services.hive.loadbalancer.server.port=10002"
        - "traefik.http.routers.hive.entrypoints=web"
        # Desactivando el paso de la cabecera Host (passHostHeader = false).
        # Le decimos a Traefik que NO pase el nombre de dominio original al contenedor.
        # Así, Traefik reescribirá la cabecera Host para que coincida con la IP interna del contenedor y el puerto 10002.
        # Esto evita que Jetty de error en hiveserver2 por recibir tráfico del puerto 80.
        - "traefik.http.services.hive.loadbalancer.passhostheader=false"

5. Inicialización del Esquema (SchemaTool)

A diferencia de Hadoop, que formatea su sistema de archivos automáticamente con nuestra configuración personalizada, el Metastore de Hive requiere que la base de datos PostgreSQL tenga una estructura de tablas específica (tablas TBLS, DBS, PARTITIONS, etc.).

La imagen apache/hive está diseñada para detectar si la base de datos está vacía e intenta inicializarla Por tanto, para garantizar un despliegue determinista, realizaremos la verificación la primera vez que arrancamos (o si borramos los volumenes) y comprobamos que ha creado correctamente el metastore.

  1. Levantar la infraestructura:
docker compose up -d
  1. Comprobando el Esquema (SchemaTool):

Ejecutamos el comando schematool dentro del contenedor del metastore.

docker exec -it metastore schematool -validate -dbType postgres \
-url 'jdbc:postgresql://postgres:5432/metastore_db' \
-driver 'org.postgresql.Driver' \
-userName hive \
-passWord hive_password

Resultado esperado: Verás un log que debería finalizar con un estado como el siguiente:

...
Starting metastore validation

Validating schema version
[SUCCESS]

Validating sequence number for SEQUENCE_TABLE
[SUCCESS]

Validating metastore schema tables
[SUCCESS]

Validating DFS locations
[SUCCESS]

Validating columns for incorrect NULL values.
[SUCCESS]

Done with metastore validation: [SUCCESS]
...

6. Permisos en HDFS para Hive

Por defecto, HDFS pertenece a root. Hive (usuario hive) necesita permisos para escribir en su almacén. Ejecuta esto una sola vez: code

# Crear estructura base y asignar propiedad al usuario 'hive'
docker exec namenode hdfs dfs -mkdir -p /user/hive/warehouse
docker exec namenode hdfs dfs -chown -R hive:supergroup /user/hive

Sin este paso, recibirás errores de "Permission denied" al crear bases de datos.

7. Verificación Funcional

Para validar que la arquitectura funciona como un todo integrado (Cliente -> HiveServer2 -> Metastore -> YARN -> HDFS), utilizaremos el cliente de línea de comandos Beeline.

  1. Conectarse a HiveServer2:

    docker exec -it hiveserver2 beeline -u 'jdbc:hive2://localhost:10000'
    

  2. Prueba DDL (Data Definition Language): Creamos una tabla. Esto valida la conexión con el Metastore y Postgres.

    CREATE TABLE empleados (id INT, nombre STRING) STORED AS ORC;
    

  3. Prueba DML (Data Manipulation Language): Insertamos datos. Esto valida la escritura en HDFS y el lanzamiento de tareas en YARN.

    INSERT INTO empleados VALUES (1, 'Marco');
    

  4. Consulta:

    SELECT * FROM empleados;
    

8. Ejemplos con Docker Compose

Vamos a adaptar los ejemplos prácticos del punto anterior de Hive al entorno Docker que hemos creado. Hay varias diferencias clave a tener en cuenta:

  1. No hacemos cd /opt/hive ni descargamos datos dentro de los contenedores (son efímeros y no tienen wget o unzip instalados por defecto).
  2. La herramienta beeline se ejecuta con docker exec.
  3. Los archivos locales del host (./datos_origen/) deben montarse o copiarse al contenedor para que Hive los vea.

Preparación del Entorno de Datos

En un entorno Docker, los contenedores son efímeros y minimalistas (a menudo no tienen wget o unzip).

Por tanto, descargaremos los datos en tu máquina local (Host) y los transferiremos al contenedor o a HDFS según sea necesario.

  1. Crea una carpeta datos_origen en la raíz de tu proyecto (junto al docker-compose.yaml):
    mkdir -p datos_origen
    cd datos_origen
    # Si no lo has hecho en el punto anterior: Crear estructura base y asignar propiedad al usuario 'hive'
    docker exec namenode hdfs dfs -mkdir -p /user/hive/warehouse
    docker exec namenode hdfs dfs -chown -R hive:supergroup /user/hive
    

9. Ejemplo 1: Primeros pasos (DDL y Carga Básica)

Vamos a crear una base de datos y cargar un dataset de películas (MovieLens).

1. Preparación de Datos

Descarga los datos en tu máquina local dentro de la carpeta datos_origen:

# En tu terminal local (Host), dentro de datos_origen
wget https://files.grouplens.org/datasets/movielens/ml-100k.zip
unzip ml-100k.zip

Ahora copiaremos el archivo u.data al contenedor de HiveServer2 para poder cargarlo con LOAD DATA LOCAL.

docker cp ml-100k/u.data hiveserver2:/tmp/u.data

2. Conexión a Hive

Conéctate al CLI de Beeline:

docker exec -it hiveserver2 beeline -u 'jdbc:hive2://localhost:10000'

3. Creación de Objetos (DDL)

-- Crear base de datos
CREATE DATABASE IF NOT EXISTS db_ejemplo1;
USE db_ejemplo1;

-- Crear tabla
CREATE TABLE u_data (
  userid INT,
  movieid INT,
  rating INT,
  unixtime STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;

4. Carga de Datos

Cargamos el fichero que copiamos a /tmp/u.data dentro del contenedor.

LOAD DATA LOCAL INPATH '/tmp/u.data' OVERWRITE INTO TABLE u_data;

5. Consultas

Verifica que los datos existen y cuenta los registros:

SELECT COUNT(*) FROM u_data;
-- Debería devolver 100000
SELECT * FROM u_data LIMIT 10;

6. Tablas Derivadas (CTAS)

Crearemos una tabla nueva filtrando solo las películas con 5 estrellas.

CREATE TABLE u_data_rating_5 AS 
SELECT userid, movieid, rating, unixtime 
FROM u_data 
WHERE rating = 5;

SELECT COUNT(*) FROM u_data_rating_5;

Arquitectura a fondo: Datos vs. Metadatos y el flujo de conexión

Es fundamental entender la separación de responsabilidades en este clúster. Al crear la tabla u_data, han ocurrido dos cosas en paralelo en lugares distintos:

  1. Los Datos (El Contenido): Viven en HDFS, distribuidos entre los DataNodes. Hive ha movido el fichero físico a /user/hive/warehouse/db_ejemplo1.db/u_data.
  2. Los Metadatos (La Definición): Viven en PostgreSQL. Hive ha guardado que existe una tabla llamada u_data, que tiene columnas int y string, y que sus ficheros están en esa ruta específica de HDFS.

Inspección del Catálogo (Metastore)

Puedes entrar directamente a la base de datos de metadatos para comprobarlo. Fíjate que aquí no verás los registros de las películas (los datos reales), solo punteros y definiciones:

# Entramos al contenedor de Postgres
docker exec -it postgres psql -U hive -d metastore_db

1. ¿Dónde están mis archivos? Ejecuta esta consulta para ver cómo Hive relaciona un nombre de tabla lógico con una ruta física en HDFS:

SELECT t."TBL_NAME" as Tabla, s."LOCATION" as Ruta_HDFS
FROM "TBLS" t
JOIN "SDS" s ON t."SD_ID" = s."SD_ID"
WHERE t."TBL_NAME" = 'u_data';

2. Radiografía Completa del Esquema Si quieres ver la estructura completa de tu Data Warehouse (Bases de datos, Tablas y sus Columnas) mirando directamente las tablas internas del sistema (DBS, TBLS, COLUMNS_V2), lanza esta consulta:

SELECT 
    d."NAME" as Base_Datos,
    t."TBL_NAME" as Tabla,
    c."COLUMN_NAME" as Columna,
    c."TYPE_NAME" as Tipo_Dato
FROM "TBLS" t
JOIN "DBS" d ON t."DB_ID" = d."DB_ID"
JOIN "SDS" s ON t."SD_ID" = s."SD_ID"
JOIN "COLUMNS_V2" c ON s."CD_ID" = c."CD_ID"
ORDER BY Base_Datos, Tabla, c."INTEGER_IDX";
(Escribe \q o quit para salir).

¿Por qué HiveServer2 no necesita la librería de Postgres?

Si revisas nuestro docker-compose.yaml, verás que no hemos montado el archivo postgresql-jdbc.jar en el contenedor hiveserver2, solo en metastore. ¿Por qué funciona?

Porque hemos desplegado una arquitectura "Remote Metastore":

  1. HiveServer2 actúa como cliente. Cuando necesita saber dónde está una tabla, le pregunta al servicio Metastore (puerto 9083) usando el protocolo binario Thrift. No sabe nada de bases de datos.
  2. El Metastore actúa como servidor. Recibe la petición, usa el driver JDBC para consultar a PostgreSQL (puerto 5432), y devuelve la respuesta a HiveServer2.

Esta separación es vital en producción: permite tener múltiples HiveServers procesando consultas masivas sin que cada uno de ellos tenga que abrir conexiones directas a la base de datos, lo que saturaría Postgres.

7. Limpieza (Opcional)

DROP TABLE u_data;
DROP TABLE u_data_rating_5;
DROP DATABASE db_ejemplo1;

10. Ejemplo 2: ETL con Regex (Logs de Apache)

Procesaremos logs de servidor web usando expresiones regulares (RegexSerDe).

1. Preparación de Datos

Al trabajar con archivos en HDFS, debemos tener cuidado con los permisos. La raíz de HDFS / pertenece al usuario root, pero Hive trabaja con el usuario hive. Si intentamos crear carpetas o subir archivos directamente a carpetas protegidas, obtendremos errores de permisos.

Seguiremos un proceso de dos pasos: Preparación del Admin y Trabajo del Usuario.

- Paso A: Descarga local (host)

Descarga los logs de la NASA en tu carpeta datos_origen:

# En tu terminal local (Host), dentro de datos_origen
wget --ftp-password="usuario@dominio.com" ftp://ita.ee.lbl.gov/traces/NASA_access_log_Jul95.gz
gunzip NASA_access_log_Jul95.gz

- Paso B: Preparación de directorios, permisos e Ingesta de datos

# Asignar dueño al usuario 'hive'
# 1. Transferir archivo del Host al contenedor
docker cp NASA_access_log_Jul95 namenode:/tmp/
# 2. Crear carpeta en HDFS para Hive
docker exec namenode hdfs dfs -mkdir -p /bda/hive/ejemplo2
# 3. Subir de Hive a HDFS (Creando subcarpetas)
docker exec namenode hdfs dfs -put /tmp/NASA_access_log_Jul95 /bda/hive/ejemplo2/
# 4. Cambiar propietario a 'hive' para evitar problemas de permisos
docker exec namenode hdfs dfs -chown -R hive:supergroup /bda/hive

2. DDL y Mapeo Regex

En Beeline:

CREATE DATABASE db_ejemplo2;
USE db_ejemplo2;

CREATE TABLE apache_logs(
  host STRING,
  identity STRING,
  usuario STRING,
  tiempo STRING,
  request STRING,
  status STRING,
  size STRING,
  referer STRING,
  agent STRING)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
  "input.regex" = "([^ ]*) ([^ ]*) ([^ ]*) (-|\\[[^\\]]*\\]) ([^ \"]*|\"[^\"]*\") (-|[0-9]*) (-|[0-9]*)(?: ([^ \"]*|\"[^\"]*\") ([^ \"]*|\"[^\"]*\"))?",
  "output.format.string" = "%1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s"
)
STORED AS TEXTFILE;

3. Carga desde HDFS

Observa que usamos LOAD DATA INPATH (sin LOCAL), porque el archivo ya está en HDFS.

LOAD DATA INPATH 'hdfs://namenode:9000/bda/hive/ejemplo2/NASA_access_log_Jul95' 
OVERWRITE INTO TABLE apache_logs;

4. Analítica

Lanzamos consultas sobre los logs parseados:

-- Ver 5 peticiones
SELECT host, request, size FROM apache_logs LIMIT 5;

-- Tamaño medio de respuesta
SELECT avg(size) FROM apache_logs;

11. Ejemplo 3: DML y Transacciones ACID

Hive soporta transacciones (INSERT, UPDATE, DELETE) si la tabla está configurada como transactional.

11.1 Configuración Dinámica en Sesión (ACID y Transacciones)

Por defecto, Hive está optimizado para lecturas rápidas y no soporta transacciones (UPDATE/DELETE). En lugar de complicar la configuración del clúster, habilitaremos estas características dinámicamente cuando las necesitemos.

Esta aproximación didáctica permite entender qué configuraciones cambian el comportamiento del motor.

1. Conexión a Beeline

docker exec -it hiveserver2 beeline -u 'jdbc:hive2://localhost:10000'

Siempre que necesites realizar operaciones transaccionales (tablas con transactional='true', updates, deletes), debemos tener las siguientes configuraciones activas en la sesión:

-- Configuración para habilitar Transacciones ACID
SET hive.support.concurrency=true;
SET hive.txn.manager=org.apache.hadoop.hive.ql.lockmgr.DbTxnManager;
SET hive.compactor.initiator.on=true;
SET hive.compactor.worker.threads=12;

11.2 Creación y Manipulación de Tabla ACID

1. Creación de Tabla ACID

Usamos el formato ORC y activamos la propiedad transaccional.

CREATE DATABASE db_ejemplo3;
USE db_ejemplo3;

CREATE TABLE u_data_acid (
  userid INT,
  movieid INT,
  rating INT,
  unixtime STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS ORC
TBLPROPERTIES ('transactional' = 'true');

2. Inserción y Modificación

-- Insertar datos manualmente
INSERT INTO TABLE u_data_acid VALUES (488, 706, 5, '891294474');

-- Verificar
SELECT * FROM u_data_acid;

-- Actualizar (UPDATE) - Cambiamos el rating de 5 a 6
UPDATE u_data_acid SET rating = 6 WHERE userid = 488 AND movieid = 706;

-- Verificar cambio
SELECT * FROM u_data_acid;

-- Borrar (DELETE)
DELETE FROM u_data_acid WHERE userid = 488;

11.3 Exportación de Datos

Cómo sacar datos de Hive hacia HDFS o tu sistema local.

Exportar a CSV en tu contenedor local de Hive

  1. Añadiendo una redirección de salida estándar a un archivo dentro del contenedor:
docker exec -it hiveserver2 bash -c 'beeline -u "jdbc:hive2://localhost:10000" -n hive -e "use db_ejemplo1; select * from u_data limit 10" >> /tmp/prueba1'

Comprobamos que se ha creado corrrectamente:

docker exec hiveserver2 cat /tmp/prueba1
  1. Sobreescribiendo un archivo existente:
docker exec -it hiveserver2 bash -c 'beeline -u "jdbc:hive2://localhost:10000" -n hive -e "use db_ejemplo1; select * from u_data limit 20" > /tmp/prueba2'

Comprobamos que se ha creado corrrectamente:

docker exec -it hiveserver2 cat /tmp/prueba2

Exportar a CSV en tu host local

Ejecución en Powershell de Windows

Si estas usando Powershell de windows y te da un error del estilo ...<EOF>..., usa la barra de escape \ antes de cada doble comilla ", o bien cambia de terminal, como por ejempo git bash o WSL

  1. Añadiendo una redirección de salida estándar a un archivo dentro del contenedor:
docker exec -it hiveserver2 beeline -u 'jdbc:hive2://localhost:10000' -n hive -e "use db_ejemplo1; select * from u_data limit 10" >> prueba1

Comprobamos que se ha creado corrrectamente:

cat prueba1
  1. Sobreescribiendo un archivo existente:
docker exec -it hiveserver2 beeline -u 'jdbc:hive2://localhost:10000' -n hive -e "use db_ejemplo1; select * from u_data limit 10" > prueba2

Comprobamos que se ha creado corrrectamente:

cat prueba2

Exportar a CSV en HDFS

Primero nos aseguramos de que la carpeta de destino en HDFS existe:

docker exec -it namenode hdfs dfs -mkdir -p /bda/hive/ejemplo3
docker exec -it namenode hdfs dfs -chown -R hive:supergroup /bda/hive

Añadimos una redirección de salida estándar a un archivo dentro de HDFS:

Este comando genera un directorio en HDFS con los resultados en formato CSV.

# Ejecutamos desde la terminal del Host (no dentro de Beeline)
docker exec -it hiveserver2 beeline -u 'jdbc:hive2://localhost:10000' -n hive \
-e "INSERT OVERWRITE DIRECTORY 'hdfs://namenode:9000/bda/hive/ejemplo3/salida_csv_hdfs' ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' SELECT * FROM db_ejemplo1.u_data LIMIT 100"

Verificamos el resultado:

docker exec namenode hdfs dfs -cat /bda/hive/ejemplo3/salida_csv_hdfs/000000_0 | head
  1. Sobreescribiendo un archivo existente en HDFS:
docker exec -it hiveserver2 bash -c 'beeline -u "jdbc:hive2://localhost:10000" -n hive --outputformat=csv2 -e "select * from db_ejemplo1.u_data" | hdfs dfs -put -f - hdfs://namenode:9000/bda/hive/ejemplo3/prueba4'

Verificamos el resultado:

docker exec -it hiveserver2 hdfs dfs -cat /bda/hive/ejemplo3/prueba4 | head

12. Ejemplo 4: Join

1. Preparación del Entorno

Primero, conéctate a la consola de Hive desde tu terminal:

docker exec -it hiveserver2 beeline -u 'jdbc:hive2://localhost:10000' -n hive

2. Creación de Objetos (DDL)

Creamos la base de datos y las tablas en formato ORC para mejor rendimiento.

CREATE DATABASE IF NOT EXISTS db_ejemplo4;
USE db_ejemplo4;

-- Tabla de Empleados
CREATE TABLE emp (
  empno INT,
  ename STRING,
  job STRING,
  mgr INT,
  hiredate DATE,
  sal INT,
  comm INT,
  deptno INT)
ROW FORMAT DELIMITED
STORED AS ORC;

-- Tabla de Departamentos
CREATE TABLE dep (
  deptno INT,
  dname STRING,
  loc STRING)
ROW FORMAT DELIMITED
STORED AS ORC;

3. Carga de Datos Optimizada

Optimización para YARN

En lugar de ejecutar una sentencia INSERT por cada fila (lo que lanzaría decenas de trabajos en YARN y tardaría minutos), agrupamos todos los valores en una sola sentencia. Esto genera un único trabajo de MapReduce/Tez.

Copia y pega estos bloques completos en Beeline:

Carga de Departamentos:

INSERT INTO TABLE dep VALUES 
  (10, 'ACCOUNTING', 'NEW YORK'),
  (20, 'RESEARCH', 'DALLAS'),
  (30, 'SALES', 'CHICAGO'),
  (40, 'OPERATIONS', 'BOSTON');

Carga de Empleados:

INSERT INTO TABLE emp VALUES
  (7369, 'SMITH', 'CLERK', 7902,'1980-12-17', 800, NULL, 20),
  (7499, 'ALLEN', 'SALESMAN', 7698,'1981-02-20',1600, 300, 30),
  (7521, 'WARD', 'SALESMAN', 7698,'1981-02-22',1250, 500, 30),
  (7566, 'JONES', 'MANAGER', 7839,'1981-04-02', 2975, NULL, 20),
  (7654, 'MARTIN', 'SALESMAN', 7698,'1981-09-28',1250, 1400, 30),
  (7698, 'BLAKE', 'MANAGER', 7839,'1981-05-01', 2850, NULL, 30),
  (7782, 'CLARK', 'MANAGER', 7839,'1981-06-09', 2450, NULL, 10),
  (7788, 'SCOTT', 'ANALYST', 7566,'1982-12-09', 3000, NULL, 20),
  (7839, 'KING', 'PRESIDENT', NULL,'1981-11-17', 5000, NULL, 10),
  (7844, 'TURNER', 'SALESMAN', 7698,'1981-09-08', 1500, 0, 30),
  (7876, 'ADAMS', 'CLERK', 7788,'1983-01-12', 1100, NULL, 20),
  (7900, 'JAMES', 'CLERK', 7698,'1981-12-03', 950, NULL, 30),
  (7902, 'FORD', 'ANALYST', 7566,'1981-12-03', 3000, NULL, 20),
  (7934, 'MILLER', 'CLERK', 7782,'1982-01-23', 1300, NULL, 10),
  (8000, 'JAIME', 'TEACHER', 7788,'2004-09-01', 1100, NULL, NULL);

12.1 Inner Join

Muestra solo los empleados que tienen un departamento válido.

SELECT e.empno, e.ename, e.job, d.deptno, d.dname, d.loc
FROM emp e JOIN dep d
ON (e.deptno = d.deptno);

Observarás que el empleado "JAIME" (deptno NULL) no aparece.

12.2 Left Outer Join

Muestra todos los empleados, tengan o no departamento.

SELECT *
FROM emp e LEFT OUTER JOIN dep d
ON (e.deptno = d.deptno);

Aquí "JAIME" aparecerá con dname como NULL.

12.3 Right Outer Join

Muestra todos los departamentos, tengan o no empleados.

SELECT *
FROM emp e RIGHT OUTER JOIN dep d
ON (e.deptno = d.deptno);

Aparecerá el departamento "OPERATIONS" (Boston), que no tiene nadie asignado, con ename NULL.

12.4 Full Outer Join

Muestra absolutamente todo: empleados sin departamento y departamentos sin empleados.

SELECT *
FROM emp e FULL OUTER JOIN dep d
ON (e.deptno = d.deptno);

5. Limpieza del Ejemplo

Al finalizar, puedes borrar la base de datos para mantener el entorno limpio.

DROP DATABASE db_ejemplo4 CASCADE;

(El modificador CASCADE borra la base de datos aunque contenga tablas).

13. Ejemplo 5: Particionamiento Dinámico

Optimizaremos las consultas dividiendo los datos en carpetas según el valor de una columna.

1. Preparación de Datos

Descarga los datos de pronóstico del tiempo (IHME).

# En tu terminal local (Host), dentro de datos_origen
wget https://gist.githubusercontent.com/jaimerabasco/cb528c32b4c4092e6a0763d8b6bc25c0/raw/dc29f1d79ed79cf5cd7ba5d69d41532e26929bdf/allstates.csv

# Copiar al contenedor
docker cp allstates.csv hiveserver2:/tmp/allstates.csv

2. Tabla Temporal (Staging)

Primero cargamos los datos "crudos" en una tabla normal.

CREATE DATABASE db_ejemplo5;
USE db_ejemplo5;

CREATE TABLE estados_raw (
  delta INT,
  fecha TIMESTAMP,
  fpsid INT,
  state STRING,
  forecastweek STRING,
  value INT,
  modelname STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

LOAD DATA LOCAL INPATH '/tmp/allstates.csv' OVERWRITE INTO TABLE estados_raw;

3. Tabla Particionada

Creamos la tabla destino particionada por state.

CREATE TABLE estados_part (
  delta INT,
  fecha TIMESTAMP,
  fpsid INT,
  forecastweek STRING,
  value INT,
  modelname STRING)
PARTITIONED BY (state STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

4. Carga Dinámica

Configuramos Hive para permitir particionamiento dinámico (crear carpetas al vuelo).

-- Configuración para Particionamiento Dinámico (necesario para cargas masivas)
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;

INSERT OVERWRITE TABLE estados_part PARTITION(state)
SELECT delta, fecha, fpsid, forecastweek, value, modelname, state 
FROM estados_raw;

5. Verificación de Particiones

Comprueba que Hive ha creado la estructura de directorios.

SHOW PARTITIONS estados_part;

Y verifica físicamente en HDFS:

docker exec namenode hdfs dfs -ls /user/hive/warehouse/db_ejemplo5.db/estados_part
Verás carpetas tipo state=Alabama, state=Alaska, etc.

14. Ejemplo 6: Bucketing (Cubos)

El Bucketing divide los datos dentro de una partición en un número fijo de ficheros (buckets) basándose en el Hash de una columna. Es útil para optimizar JOINs.

1. Tabla con Buckets

Vamos a particionar por estado y, dentro de cada estado, dividir en 4 buckets según el ID (fpsid).

CREATE TABLE estados_bucket (
  delta INT,
  fecha TIMESTAMP,
  fpsid INT,
  forecastweek STRING,
  value INT,
  modelname STRING)
PARTITIONED BY (state STRING)
CLUSTERED BY (fpsid) INTO 4 BUCKETS
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

2. Carga Forzando Bucketing

Para asegurar que Hive respete los buckets en la inserción (especialmente con pocos datos), forzamos la propiedad:

SET hive.enforce.bucketing = true;

INSERT OVERWRITE TABLE estados_bucket PARTITION(state)
SELECT delta, fecha, fpsid, forecastweek, value, modelname, state 
FROM estados_raw;

3. Verificación

Revisa una partición concreta en HDFS. Deberías ver 4 archivos (000000_0, 000001_0, etc.) dentro de la carpeta del estado.

docker exec namenode hdfs dfs -ls /user/hive/warehouse/db_ejemplo5.db/estados_bucket/state=Alabama

Nota sobre Spark y Hive

En este módulo hemos configurado Hive completo. Sin embargo, cuando integremos Apache Spark más adelante, no utilizaremos Hive para procesar datos.

Spark es un motor de procesamiento mucho más eficiente en memoria. Configuraremos Spark para que hable directamente con HDFS, omitiendo la capa de ejecución de Hive para optimizar recursos y rendimiento.