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-metastoreporque 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:
# --- 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
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:
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.
- Levantar la infraestructura:
- 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.
-
Conectarse a HiveServer2:
-
Prueba DDL (Data Definition Language): Creamos una tabla. Esto valida la conexión con el Metastore y Postgres.
-
Prueba DML (Data Manipulation Language): Insertamos datos. Esto valida la escritura en HDFS y el lanzamiento de tareas en YARN.
-
Consulta:
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:
- No hacemos
cd /opt/hiveni descargamos datos dentro de los contenedores (son efímeros y no tienenwgetounzipinstalados por defecto). - La herramienta
beelinese ejecuta condocker exec. - 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.
- Crea una carpeta
datos_origenen la raíz de tu proyecto (junto aldocker-compose.yaml):
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.
2. Conexión a Hive¶
Conéctate al CLI de Beeline:
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.
5. Consultas¶
Verifica que los datos existen y cuenta los registros:
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:
- 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. - Los Metadatos (La Definición): Viven en PostgreSQL. Hive ha guardado que existe una tabla llamada
u_data, que tiene columnasintystring, 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:
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";
\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":
- 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.
- 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)¶
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¶
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¶
- 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:
- 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:
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
- 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:
- 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:
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:
- 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:
12. Ejemplo 4: Join¶
1. Preparación del Entorno¶
Primero, conéctate a la consola de Hive desde tu terminal:
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.
Aquí "JAIME" aparecerá con dname como NULL.
12.3 Right Outer Join¶
Muestra todos los departamentos, tengan o no empleados.
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.
5. Limpieza del Ejemplo¶
Al finalizar, puedes borrar la base de datos para mantener el entorno limpio.
(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.
Y verifica físicamente en HDFS:
Verás carpetas tipostate=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.
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.