En un plan de ejecución de consulta simple (por ejemplo, al contar registros sujetos a una condición de filtrado), ¿cuál es típicamente la primera etapa de procesamiento en los nodos de trabajo distribuido?
A. Consolidación y suma final
B. Distribución de resultados al maestro
C. Filtrado y conteo parcial de entradas
D. Creación del plan de shuffle
C
¿Por qué la “C” es la correcta?
En un sistema distribuido, la regla de oro es “Lleva el cómputo a los datos, no los datos al cómputo”.
Imagina que tienes una tabla con 1 billón de filas repartidas en 1,000 computadoras (Workers). Quieres saber cuántas filas cumplen una condición (ej. WHERE pais = ‘España’).
Stage 1 (Fase de Mapa/Lectura): Antes de mover nada por la red, cada nodo trabajador lee su propio pedazo de disco (shard).
Filtrado: Aplica el filtro inmediatamente. Si la fila no es de ‘España’, la descarta.
Conteo Parcial: Cuenta cuántas filas de ‘España’ encontró en su pedazo.
Resultado del Worker 1: “Yo encontré 50”.
Resultado del Worker 2: “Yo encontré 200”.
Esto es lo más eficiente porque reduce drásticamente la cantidad de datos que deben viajar por la red hacia la siguiente etapa.
¿Por qué las otras opciones ocurren después (o no son la primera etapa)?
A. Consolidación y suma final (Stage 2):
❌ Incorrecta (Es el segundo paso).
Explicación: Esta es la fase de “Reduce”. Ocurre después de que los trabajadores han enviado sus conteos parciales. Aquí es donde se suman los “50” del Worker 1 + los “200” del Worker 2 para obtener el total global. No puedes sumar totales si primero no has contado las partes.
B. Distribución de resultados al maestro:
❌ Incorrecta (Es el paso final).
Explicación: Esto ocurre al final absoluto del proceso, cuando el cálculo ya terminó y el sistema le entrega la respuesta (“El total es 250”) al usuario o al nodo maestro.
D. Creación del plan de shuffle:
❌ Incorrecta.
Explicación: El “Shuffle” es el movimiento de datos entre la Etapa 1 y la Etapa 2. El plan de cómo se hará ese movimiento generalmente lo decide el “Query Master” antes de que los trabajadores empiecen a procesar nada, o es una fase intermedia de transferencia, no de procesamiento de datos en sí.
Analogía del CensoImagina que quieres contar cuántas personas viven en todo el país (El País es la Base de Datos Distribuida).Opción C (Lo correcto): Envías a un censista a cada ciudad. El censista cuenta a la gente de su ciudad y anota el número en un papel (Conteo Parcial).Opción A (Lo incorrecto): No le pides a todos los habitantes del país que viajen a la capital para contarlos todos juntos en una plaza. Eso colapsaría las carreteras (la red). Primero cuentas localmente, luego sumas los totales.Resumen del FlujoWorker (Stage 1 - Opción C): Lee disco $\rightarrow$ Filtra $\rightarrow$ Cuenta localmente.Red (Shuffle): Envía el numerito del conteo a un agregador.Worker/Master (Stage 2 - Opción A): Recibe los numeritos $\rightarrow$ Suma el total $\rightarrow$ Entrega resultado (Opción B).
Si al analizar las métricas de un plan de consulta se observa una diferencia significativa entre el tiempo máximo de procesamiento y el tiempo promedio, ¿cuál es la causa subyacente más probable?
A. El uso excesivo de funciones definidas por el usuario (UDF).
B. Un error de sintaxis no capturado previamente.
C. Una inclinación o asimetría de datos (data skew).
D. Un fallo en la lectura de la caché.
C
¿Cuál es la meta fundamental al optimizar una consulta en un entorno de procesamiento distribuido?
A. Minimizar el número de etapas.
B. Reducir el trabajo total realizado por la consulta.
C. Aumentar el número de workers utilizados.
D. Maximizar el uso de la caché.
B
La Respuesta Correcta
B. Reducir el trabajo total realizado por la consulta.
¿Por qué? En computación distribuida, el “trabajo” se mide en tres factores costosos:
I/O (Entrada/Salida): Leer datos del disco.
Shuffle (Red): Mover datos entre diferentes ordenadores (nodos).
CPU: Procesar esos datos.
El objetivo fundamental de optimizar no es ser “más sofisticado”, sino hacer menos.
Si filtras los datos antes de hacer un Join, reduces el trabajo.
Si seleccionas solo las columnas que necesitas, reduces el trabajo.
Si usas particiones para no leer toda la tabla, reduces el trabajo.
Si logras reducir la cantidad de bytes que se leen y se mueven, la consulta será más rápida y barata, independientemente de cuántos servidores tengas.
Por qué las otras opciones son incorrectas
Estas opciones suelen confundirse con optimización, pero en realidad son tácticas secundarias o errores conceptuales.
A. Minimizar el número de etapas (Stages)
Por qué es mala: En sistemas como Spark, las “etapas” se crean cuando hay que mover datos (Shuffle). A veces, necesitas más etapas para que la consulta funcione bien.
Ejemplo: Si intentas forzar todo en una sola etapa, podrías sobrecargar la memoria de un solo worker y hacer que falle. A veces, dividir el trabajo en dos etapas (repartir y luego procesar) es más lento en teoría, pero más estable y necesario. El número de etapas es una consecuencia, no la meta.
C. Aumentar el número de workers utilizados
Por qué es mala: Esto no es optimizar, esto es escalar.
La analogía: Si tienes una mudanza y empacas las cajas de forma desordenada y lenta (una consulta ineficiente), contratar a 50 camiones más (workers) te ayudará a terminar, pero te saldrá carísimo y seguirás siendo ineficiente.
Regla de oro: Una mala consulta distribuida en 1000 nodos sigue siendo una mala consulta (y a veces va peor por la sobrecarga de gestión).
D. Maximizar el uso de la caché
Por qué es mala: La caché es una herramienta, no un objetivo.
El riesgo: Si intentas “maximizar” la caché, podrías llenarla de datos basura que no necesitas, provocando que el sistema se quede sin memoria (OOM - Out Of Memory) y falle. El objetivo es usar la caché inteligentemente para reducir la lectura de disco (que nos lleva de vuelta a la opción B: reducir el trabajo), no usarla por usarla.
Resumen Mental para el Examen
Piensa en la eficiencia como en cocinar:
Opción B (La Meta): Comprar solo los ingredientes necesarios y ensuciar pocos platos (Reducir el trabajo).
Opción C (Workers): Traer a 10 cocineros a una cocina pequeña. (A veces estorba más que ayuda).
Opción D (Caché): Llenar toda la mesa de ingredientes “por si acaso”. (Te quedas sin espacio para trabajar).
Por tanto, hacer menos trabajo (filtrar pronto, leer poco) siempre es la optimización reina.
¿Qué componentes se consideran explícitamente como “trabajo” (work) que contribuye al coste y tiempo de ejecución de una consulta? (selecciona todas las que apliquen)
A. I/O (lectura de bytes).
B. Shuffle (bytes pasados a la siguiente etapa).
C. Agrupación (Grouping).
D. CPU (ejecución de UDFs o funciones).
A, B, C y D
El “trabajo” (work) de una consulta es la suma de todos los recursos que el sistema debe gastar. Cada una de las cuatro opciones es un tipo de recurso consumido:
A. I/O (lectura de bytes): Es el trabajo de leer los datos desde el almacenamiento. Es el costo inicial de mover los datos del disco a la memoria para poder procesarlos.
B. Shuffle (bytes pasados a la siguiente etapa): Es el trabajo de mover datos a través de la red. Ocurre cuando los datos deben ser redistribuidos entre los nodos de cómputo, algo esencial para operaciones como JOIN y GROUP BY. A menudo es uno de los cuellos de botella más significativos.
C. Agrupación (Grouping): Es el trabajo de computación (CPU) específico para realizar la agregación. Implica crear y mantener estructuras de datos (como tablas hash) para agrupar las claves idénticas y luego ejecutar las funciones de agregación (SUM, AVG, MAX) sobre cada grupo. Es un tipo de trabajo de CPU distinto y muy intensivo.
D. CPU (ejecución de UDFs o funciones): Es el trabajo de computación (CPU) “general”. Esto incluye cualquier cálculo que no sea I/O o Shuffle, como aplicar filtros (WHERE), procesar expresiones (CASE WHEN…), o ejecutar funciones complejas como UDFs, JSON o expresiones regulares.
Si la mayor parte del tiempo de ejecución se invierte en tareas de CPU o en leer datos de etapas intermedias, ¿qué estrategia de optimización se recomienda aplicar?
A. Inspeccionar el uso de funciones definidas por el usuario (UDFs).
B. Filtrar los datos en una etapa más temprana de la consulta.
C. Utilizar funciones aproximadas en lugar de exactas.
D. Todas las anteriores.
D
Esta es una pregunta clásica de optimización de consultas en motores de bases de datos distribuidas (como BigQuery, Spark SQL, etc.). El problema describe dos cuellos de botella:
Exceso de tiempo en tareas de CPU: El motor está “pensando” demasiado (haciendo cálculos complejos).
Exceso de tiempo leyendo datos intermedios: Una etapa de la consulta produce un conjunto de resultados masivo, y la siguiente etapa tiene que leerlo todo para continuar.
Veamos por qué todas las opciones son estrategias válidas para solucionar esto.
Por qué todas las opciones son correctas
A. Inspeccionar el uso de funciones definidas por el usuario (UDFs).
Impacto: Las UDFs (especialmente las que están en JavaScript o Python) son “cajas negras” para el optimizador de consultas. Tienen un costo de CPU altísimo, ya que el motor tiene que cambiar de su ejecución SQL optimizada a un intérprete de otro lenguaje por cada fila.
Solución: Reemplazar una UDF lenta con funciones SQL nativas es una de las optimizaciones de CPU más efectivas.
B. Filtrar los datos en una etapa más temprana de la consulta.
Impacto: Esta es la regla de oro de la optimización SQL. Si aplicas tu filtro (WHERE) al final, el motor tiene que procesar, mover y leer millones o billones de filas innecesarias a través de todas las “etapas intermedias”.
Solución: Al filtrar lo antes posible, reduces la cantidad de filas en cada paso. Esto ahorra CPU (menos filas para procesar) y reduce drásticamente el tamaño de los datos intermedios.
C. Utilizar funciones aproximadas en lugar de exactas.
Impacto: Ciertas funciones, como COUNT(DISTINCT …), son extremadamente costosas en términos de CPU y memoria en conjuntos de datos masivos.
Solución: Si la precisión exacta no es vital, usar una función de aproximación (como APPROX_COUNT_DISTINCT()) es órdenes de magnitud más rápido. Utiliza algoritmos (como HyperLogLog++) que usan mucha menos CPU y memoria, aliviando ese cuello de botella.
Dado que las tres son estrategias de optimización potentes y recomendadas para los problemas específicos de CPU y datos intermedios, la respuesta correcta es D. Todas las anteriores.
¿Por qué se considera una mala práctica de optimización y coste el uso de SELECT * en consultas, especialmente en subconsultas?
A. El motor de consulta no puede paralelizar la lectura de todas las columnas.
B. Aumenta la complejidad del plan de ejecución innecesariamente.
C. Es ineficiente en términos de coste y rendimiento, ya que selecciona columnas innecesarias.
D. Causa errores de desbordamiento de recursos en la etapa final.
C
Si es necesario seleccionar casi todas las columnas de una tabla grande, pero se desea excluir específicamente un número pequeño de ellas, ¿qué sintaxis se debe considerar para la optimización?
A. Uso de una vista materializada que solo contenga las columnas requeridas.
B. SELECT * EXCEPT (col1, col2)
C. Filtrar las columnas después del JOIN.
D. Denormalización manual de la tabla.
B
¿Qué mecanismos de organización de tablas son mencionados como habilitadores del auto-poda o auto-recorte (auto-pruning) de datos? (selecciona DOS)
A. Particionamiento de la tabla.
B. Uso de la cláusula LIMIT.
C. Agrupación o Clustering de la tabla.
D. Denormalización plana.
A y C
¿Qué es el “Auto-Pruning” (Auto-Recorte)?
Imagina que tus datos son una biblioteca gigante. El “auto-pruning” es la habilidad del bibliotecario (el motor de consulta) de saber exactamente a qué estantería y a qué libro ir, basándose en tu solicitud (WHERE), en lugar de tener que revisar todos los libros de la biblioteca.
Esto reduce drásticamente el I/O (trabajo de lectura), lo cual ahorra tiempo y, en sistemas como BigQuery, ahorra costes.
✅ Las respuestas correctas
A. Particionamiento de la tabla (Partitioning)
Por qué es correcta: Este es el mecanismo de poda (pruning) a gran escala. Particionar una tabla la divide físicamente en “contenedores” separados basados en una columna (casi siempre una fecha).
Ejemplo: Si particionas una tabla de ventas por día (sales_date), todos los datos del 1 de enero están en un contenedor, los del 2 de enero en otro, etc.
Auto-Pruning: Cuando haces una consulta con WHERE sales_date = ‘2025-01-01’, el motor de consulta automáticamente poda (ignora) todos los demás contenedores y solo lee el del 1 de enero.
C. Agrupación o Clustering de la tabla (Clustering)
Por qué es correcta: Este es el mecanismo de poda a pequeña escala (más granular). Dentro de cada partición (o en toda la tabla si no está particionada), el clustering ordena físicamente los datos basándose en el contenido de una o más columnas.
Ejemplo: Si además de particionar por fecha, agrupas (cluster) por customer_id, todos los datos de un mismo cliente dentro de ese día se escribirán juntos en los mismos bloques de almacenamiento.
Auto-Pruning: Cuando haces una consulta con WHERE customer_id = ‘cliente_123’, el motor de consulta sabe exactamente en qué bloques dentro de la partición se encuentran los datos de ese cliente, y poda (ignora) el resto de bloques de esa misma partición.
❌ Las respuestas incorrectas
B. Uso de la cláusula LIMIT
Por qué es incorrecta: LIMIT no es un mecanismo de organización de la tabla. Es una instrucción de ejecución de la consulta. Le dice al motor: “cuando termines de procesar y encuentres X filas, detente”. No le ayuda al motor a decidir qué datos no leer al principio; simplemente detiene el trabajo una vez que se cumple la condición.
D. Denormalización plana (Flat denormalization)
Por qué es incorrecta: La denormalización es una técnica de modelado de datos (diseño del esquema), no un mecanismo de organización física del almacenamiento. Consiste en guardar datos relacionados juntos en una sola tabla (usando campos anidados o repetidos) para evitar costosos JOINs. Si bien hace que las consultas sean más eficientes al evitar JOINs, no habilita por sí misma el auto-pruning basado en filtros. De hecho, las tablas denormalizadas suelen ser muy grandes, por lo que es más importante aplicarles particionamiento (A) y clustering (C).
Al modelar datos que contienen ítems repetidos (como líneas de pedido), ¿cuál es la ventaja de anidar los ítems dentro de un campo en lugar de utilizar una denormalización plana con una fila por cada ítem?
A. Permite usar funciones de ventana de manera más eficiente.
B. Evita la necesidad de utilizar la cláusula GROUP BY para analizar la entidad principal (el pedido).
C. Reduce la latencia de I/O en un 50%.
D. Garantiza que la consulta utilice un shuffle join.
B
Este es un concepto clave en el modelado de datos para sistemas analíticos como BigQuery.
Imaginemos las dos estructuras con un pedido (order_id: 123) que tiene 3 ítems:
Denormalización Plana (una fila por ítem): | order_id | customer_id | item_sku | | :— | :— | :— | | 123 | 900 | A-001 | | 123 | 900 | B-002 | | 123 | 900 | C-003 |
Anidada (una fila por pedido): | order_id | customer_id | items (ARRAY) | | :— | :— | :— | | 123 | 900 | [ {sku: A-001}, {sku: B-002}, {sku: C-003} ] |
✅ La respuesta correcta
B. Evita la necesidad de utilizar la cláusula GROUP BY para analizar la entidad principal (el pedido).
Por qué es correcta: En el modelo anidado, la “entidad principal” (el pedido, order_id: 123) existe en una sola fila.
Si quieres hacer una pregunta sobre los pedidos, como “¿Cuántos pedidos tengo?”:
En el modelo anidado: SELECT COUNT(order_id) FROM tabla_anidada. (Respuesta: 1)
En el modelo plano: SELECT COUNT(order_id) FROM tabla_plana. (Respuesta: 3. ¡Incorrecto!)
Para obtener la respuesta correcta en el modelo plano, tienes que agrupar (GROUP BY) o contar distintos: SELECT COUNT(DISTINCT order_id) FROM tabla_plana.
El modelo anidado preserva la granularidad de la entidad principal, por lo que puedes analizarla directamente sin tener que “agrupar” primero los ítems que se repiten.
❌ Las respuestas incorrectas
A. Permite usar funciones de ventana de manera más eficiente.
Por qué es incorrecta: Es lo contrario. Las funciones de ventana operan sobre un conjunto de filas. En el modelo plano, los ítems ya están en filas separadas, por lo que es fácil usar PARTITION BY order_id para analizarlos. En el modelo anidado, primero tendrías que “desanidar” (usar UNNEST) el array para convertirlo en filas, lo cual es un paso extra.
C. Reduce la latencia de I/O en un 50%.
Por qué es incorrecta: Si bien el modelo anidado sí reduce el I/O (porque no repite los datos del pedido en cada fila, como order_id y customer_id), el número “50%” es completamente arbitrario y falso. El ahorro real depende de cuántos ítems haya por pedido y cuán grandes sean los datos del pedido que se repiten.
D. Garantiza que la consulta utilice un shuffle join.
Por qué es incorrecta: El objetivo de ambos métodos (plano y anidado) es la denormalización. La denormalización se hace precisamente para EVITAR tener que hacer JOINs (como un shuffle join) entre una tabla de pedidos y una tabla de lineas_de_pedido. Esta opción describe un problema, no una ventaja.
¿Qué factor se utiliza para calcular el hash que determina si se puede utilizar el resultado de una consulta desde la caché?
A. Las tablas referenciadas o vistas utilizadas.
B. La cadena de consulta exacta (Query string).
C. Los tiempos de modificación de los datos.
D. La zona horaria del usuario.
B
Por qué la Opción B es la Correcta
B. La cadena de consulta exacta (Query string).
El mecanismo de caché de BigQuery funciona de manera muy literal. Para determinar si un resultado se puede reutilizar, BigQuery toma la consulta SQL que envías y calcula un “hash” (un identificador único) basado en ese texto.
Es el factor principal: El hash se calcula directamente a partir de la cadena de texto de la consulta.
Debe ser idéntica: Si la nueva consulta es diferente por un solo carácter (un espacio extra, una mayúscula en lugar de minúscula, un comentario diferente), generará un hash diferente y la caché no se utilizará.
Esta es la base fundamental para encontrar una posible coincidencia en la caché.
Por qué las Otras Opciones son Incorrectas
A. Las tablas referenciadas o vistas utilizadas. y C. Los tiempos de modificación de los datos.
Incorrecto: Estas dos opciones están relacionadas, pero describen la invalidación de la caché, no el cálculo del hash.
El Proceso Correcto:
BigQuery calcula el hash de tu consulta (usando la opción B).
Busca ese hash en la caché.
Si lo encuentra, BigQuery realiza un segundo chequeo: ¿Han cambiado los datos en las tablas referenciadas (A) desde que se guardó este resultado? (Opción C).
Si los datos han cambiado (tiempo de modificación), la caché se considera “sucia” (stale) y no se usa, aunque el hash coincidiera. Por lo tanto, A y C son factores para decidir si usar la caché, pero no para calcular el hash inicial.
D. La zona horaria del usuario.
Incorrecto: La configuración de la zona horaria del usuario no se incluye en el cálculo del hash. De hecho, el uso de funciones que dependen del momento exacto (como NOW() o CURRENT_TIMESTAMP()) generalmente hace que una consulta no sea apta para la caché en primer lugar, ya que su resultado debe ser diferente cada vez que se ejecuta.
¿Bajo qué condiciones se omite el uso de la caché de consultas (Query cache)? (selecciona todas las que apliquen)
A. Si se solicita una tabla de resultados permanente.
B. Si se utiliza una función no determinista (ej. NOW()).
C. Si las tablas de origen tienen búferes de transmisión (streaming buffers).
D. Si las tablas o vistas referenciadas han sido modificadas.
A, B, C y D
Cada una de ellas describe una situación específica en la que BigQuery decide, por motivos de precisión o por la naturaleza de la solicitud, que no puede reutilizar un resultado anterior y debe ejecutar la consulta de nuevo.
A. Si se solicita una tabla de resultados permanente
Por qué es correcto: La caché de consultas existe para acelerar las consultas SELECT (lecturas) que se repiten. Si estás pidiendo una “tabla de resultados permanente” (es decir, usando la “Tabla de Destino” o Destination Table en la configuración), tu intención no es solo ver los datos, sino escribirlos en una nueva tabla.
La lógica: BigQuery debe ejecutar la consulta para generar los datos que va a escribir. No puede simplemente tomar un resultado de la caché y “moverlo” a una nueva tabla; debe procesar la solicitud de escritura. Por lo tanto, omite la caché y ejecuta el trabajo.
B. Si se utiliza una función no determinista (ej. NOW())
Por qué es correcto: Las funciones no deterministas son aquellas cuyo resultado cambia cada vez que se ejecutan, aunque la entrada sea la misma. NOW(), CURRENT_TIMESTAMP() o RAND() son los ejemplos perfectos.
La lógica: La caché solo funciona si el resultado de la consulta es estático y reutilizable. Si tu consulta es SELECT * FROM mi_tabla WHERE fecha > NOW() - INTERVAL 1 DAY, el valor de NOW() es diferente cada milisegundo. Si BigQuery usara un resultado de la caché, te estaría dando una respuesta basada en la hora de ayer o de hace 5 minutos, lo cual sería incorrecto. Para garantizar la precisión, BigQuery omite la caché.
C. Si las tablas de origen tienen búferes de transmisión (streaming buffers)
Por qué es correcto: Los “búferes de transmisión” (streaming buffers) contienen datos que acaban de ser insertados en BigQuery mediante la API de streaming y que aún no se han consolidado en el almacenamiento principal.
La lógica: Estos datos son los más “frescos” de la tabla. Un resultado guardado en la caché se generó antes de que estos nuevos datos llegaran. Si BigQuery usara la caché, tu consulta ignoraría por completo los datos recién llegados. Para garantizar que la consulta incluya los datos más recientes (incluidos los del búferred de streaming), BigQuery debe omitir la caché.
D. Si las tablas o vistas referenciadas han sido modificadas
Por qué es correcto: Esta es la regla de invalidación de caché más fundamental.
La lógica: La caché guarda una “foto” del resultado en un momento dado. Si los datos de la tabla original cambian (por un UPDATE, INSERT, DELETE, una carga por lotes, o incluso la expiración de una partición), esa “foto” guardada en la caché queda obsoleta (se vuelve stale).
BigQuery detecta que la tabla ha sido modificada y, aunque la pregunta (la cadena de consulta) sea la misma, sabe que la respuesta debe ser diferente. Por lo tanto, omite la caché y vuelve a ejecutar la consulta.
En el contexto de la optimización de agregaciones, ¿cuál es la recomendación general sobre cuándo se debe realizar la agregación?
A. Siempre se debe agregar la tabla más grande primero.
B. Agregar tan tarde y tan raramente como sea posible, ya que es costoso.
C. Agregar siempre en la etapa de subconsulta inicial.
D. La agregación temprana es siempre la mejor práctica, independientemente del tamaño de la tabla.
B
Existe una excepción a la regla de la agregación tardía: ¿cuándo se recomienda agregar un conjunto de datos antes de una operación de JOIN?
A. Cuando la agregación utiliza una función aproximada.
B. Si el JOIN es con una clave no única.
C. Si la tabla puede reducirse drásticamente en tamaño como preparación para la unión.
D. Cuando la consulta tiene una cláusula ORDER BY.
C
Durante la ejecución de una agregación distribuida con shuffle (reorganización), ¿cuál es la función principal de esta operación de shuffle?
A. Realizar la ordenación final antes de aplicar LIMIT.
B. Distribuir el resultado de la agregación final al nodo maestro.
C. Garantizar que todos los valores iguales de la clave de agrupación vayan al mismo nodo de trabajo.
D. Reorganizar la tabla de entrada para la etapa de Partial GROUP BY.
C
Al crear una consulta que utiliza JOIN, ¿cuál es la mejor práctica recomendada para ordenar manualmente las tablas a unir?
A. Colocar la tabla más pequeña primero.
B. Colocar la tabla más grande primero, seguida de la más pequeña, y luego por tamaño decreciente.
C. La tabla debe ser ordenada alfabéticamente por nombre.
D. El optimizador estándar de SQL siempre anula cualquier ordenación manual de la tabla.
B
Por qué la respuesta B es la correcta
B) Colocar la tabla más grande primero, seguida de la más pequeña, y luego por tamaño decreciente.
Esta es la respuesta correcta porque describe una heurística de optimización clave en los sistemas de data warehouse distribuidos y de procesamiento paralelo masivo (MPP), como BigQuery, Redshift o Spark SQL.
“Colocar la tabla más grande primero…”: Este es el principio más importante. En un hash join distribuido (el método más común en estos sistemas), el motor de consulta debe decidir qué tabla usar para “construir” (build) una tabla hash en memoria y qué tabla “sondear” (probe) o transmitir (stream).
Lo ideal es “construir” la tabla hash con la tabla más pequeña, ya que debe caber en la memoria de cada nodo worker.
La tabla más grande se “transmite” (stream), y cada una de sus filas se comprueba contra la tabla hash en memoria.
¿Por qué el orden manual importa?: Aunque los optimizadores de consulta son muy inteligentes, no son perfectos. Sus decisiones se basan en estadísticas de las tablas, que pueden estar desactualizadas o ser imprecisas. Al poner la tabla más grande primero en la cláusula FROM o JOIN, el desarrollador le está dando una pista fuerte (hint) al optimizador para que la trate como la tabla de stream y no intente cargarla en memoria (lo cual fallaría y causaría un error).
Aunque la segunda parte de la regla (“…seguida de la más pequeña, y luego por tamaño decreciente”) es muy específica y puede no ser universal, la parte principal (“Colocar la tabla más grande primero”) es la “mejor práctica” reconocida en este entorno.
Por qué las otras respuestas son incorrectas
A) Colocar la tabla más pequeña primero.
Incorrecto. Esta era una mejor práctica en sistemas de bases de datos antiguos que dependían de nested-loop joins (bucles anidados). En ese método, poner la tabla pequeña en el “bucle exterior” significaba menos iteraciones. En los data warehouses modernos que usan hash joins, esta lógica se invierte.
C) La tabla debe ser ordenada alfabéticamente por nombre.
Incorrecto. El rendimiento de la consulta no tiene absolutamente nada que ver con el nombre de las tablas.
D) El optimizador estándar de SQL siempre anula cualquier ordenación manual de la tabla.
Incorrecto (y es la “trampa”). Si bien es cierto que los optimizadores de consulta modernos (Cost-Based Optimizers) son los que toman la decisión final y pueden ignorar el orden del desarrollador, la palabra “siempre” es la que invalida esta opción.
En primer lugar, el optimizador a veces usa el orden como una pista.
En segundo lugar, casi todas las bases de datos tienen “pistas” de consulta (query hints, ej. /*+ ORDERED */ en Oracle) que fuerzan al optimizador a seguir el orden manual del desarrollador.
Dado que el optimizador no “siempre” anula el orden y que la “mejor práctica” en sistemas distribuidos es (B), esta opción es incorrecta.
Para asegurar que los conjuntos de datos a unir sean lo más pequeños posible, ¿dónde se recomienda ejecutar las cláusulas WHERE, especialmente en consultas que contienen JOINs?
A. Después de la agregación.
B. En una subconsulta o lo más pronto posible dentro de las condiciones de unión.
C. Exclusivamente en la tabla de la derecha del JOIN.
D. Solo cuando el plan de explicación muestre un uso excesivo de I/O.
B
¿Qué tipo de JOIN se ilustra cuando una tabla pequeña (que califica para ser manejada por un solo nodo o ser replicada) se combina eficientemente con una tabla grande, sin necesidad de un shuffle independiente en ambos lados?
A. Hash Join
B. Shuffle Join
C. Broadcast Join (o Small JOIN)
D. Cartesian Join
C
Por qué la respuesta C es la correcta
C) Broadcast Join (o Small JOIN)
Un Broadcast Join (o “unión por difusión”) es una estrategia de optimización diseñada exactamente para el escenario descrito: unir una tabla muy pequeña con una tabla muy grande.
Así es como funciona:
El optimizador de consultas identifica que una de las tablas (la “tabla pequeña”) es lo suficientemente pequeña como para caber en la memoria de un solo nodo.
En lugar de “barajar” (shuffle) la tabla grande (lo cual es extremadamente costoso en términos de red y tiempo), el sistema hace lo contrario.
Transmite (Broadcasts) o replica la tabla pequeña completa a todos los nodos (workers) que tienen una parte de la tabla grande.
Una vez que cada worker tiene su parte de la tabla grande y una copia completa de la tabla pequeña, puede realizar el JOIN localmente (generalmente usando un Hash Join) sin más comunicación de red.
Esto se ajusta perfectamente a la descripción: la “tabla pequeña” es “replicada”, se une con una “tabla grande”, y se hace “sin necesidad de un shuffle” en la tabla grande.
Por qué las otras respuestas son incorrectas
A) Hash Join
Incorrecto. Un “Hash Join” es el algoritmo que se usa para realizar la unión dentro de cada nodo, no la estrategia para mover los datos entre los nodos. Tanto el Broadcast Join como el Shuffle Join usan un “Hash Join” como mecanismo. Es una respuesta demasiado genérica.
B) Shuffle Join
Incorrecto. Un “Shuffle Join” (o repartition join) es lo opuesto a lo descrito. Se usa cuando ambas tablas son grandes. En este caso, el sistema sí necesita hacer un “shuffle” en ambas tablas, reparticionándolas por la clave del JOIN para que las filas que coinciden terminen en el mismo nodo. Esto es lo que la opción C busca evitar.
D) Cartesian Join
Incorrecto. Un “Cartesian Join” (o CROSS JOIN) es un tipo de unión que multiplica cada fila de la tabla A por cada fila de la tabla B (M x N). No tiene una cláusula ON. Es la forma más ineficiente de unir tablas y no describe la estrategia de optimización mencionada.
¿Qué condiciones o acciones son causantes de una “explosión de JOIN” (Join explosion)? (selecciona todas las que apliquen)
A. El producto cartesiano de filas que comparten la misma clave de unión.
B. La utilización de una clave no única en ambos lados del JOIN.
C. La pre-agregación de filas mediante GROUP BY.
D. Un caso extremo donde el número de filas de salida es el producto del número de filas de la tabla izquierda por el número de filas de la tabla derecha.
A, B y D
¿Por qué A, B y D definen la “Explosión”?El término “Join Explosion” se refiere a cuando el resultado de unir dos tablas genera muchas más filas de las que esperabas, a menudo saturando la memoria del sistema (OOM - Out of Memory).1. La Causa Raíz (Opción B)El problema: Ocurre cuando intentas hacer un JOIN y la columna que usas como llave (key) no es única en ninguna de las dos tablas.El concepto: Es una relación Muchos-a-Muchos (N-to-N) no controlada.Ejemplo: Tienes la tabla “Ventas” y la tabla “Clientes”. Si por error tienes al cliente “Juan” duplicado 10 veces en la tabla de Clientes y 10 veces en la de Ventas, y haces un JOIN… ¡Boom!2. El mecanismo matemático (Opción A)Lo que sucede: El motor de base de datos realiza un producto cartesiano para esas filas coincidentes.Las matemáticas: Si tienes 1,000 filas con la clave “A” en la izquierda y 1,000 filas con la clave “A” en la derecha, el resultado no son 1,000 filas, ni 2,000. Son $1,000 \times 1,000 = \mathbf{1,000,000}$ de filas.3. El caso extremo (Opción D)Cross Join: Si haces un JOIN sin ninguna condición (o si todas las claves son idénticas en ambas tablas), obtienes el producto total de ambas tablas. Si cada tabla tiene 1 millón de filas, el resultado es 1 billón ($10^{12}$) de filas. Esto es la definición máxima de explosión.¿Por qué la C es “la mala” (en este contexto)?C. La pre-agregación de filas mediante GROUP BY.❌ No causa la explosión, ¡la cura!Explicación: Esta es la técnica estándar para evitar la explosión de JOINs.Cómo funciona: Antes de unir las tablas, usas GROUP BY para eliminar duplicados en la clave de unión, asegurándote de que al menos un lado del JOIN sea único.Estrategia: Transformas una relación “Muchos-a-Muchos” (peligrosa) en una “Muchos-a-Uno” (segura).Ejemplo VisualImagina que unes dos tablas por la clave ID:Tabla A| ID | Valor || :— | :— || 1 | X || 1 | Y |Tabla B| ID | Valor || :— | :— || 1 | A || 1 | B |Resultado del JOIN (Explosión - Opciones A/B):El resultado tendrá 4 filas ($2 \times 2$), duplicando datos innecesariamente.
ID,A.Valor,B.Valor
1,X,A
1,X,B
1,Y,A
1,Y,B
Solución (Opción C): Si haces un GROUP BY ID en la Tabla B antes de unir, solo tendrías 1 fila con ID 1, y el resultado final serían solo 2 filas, evitando la explosión.
¿Cuál es la causa subyacente de un Skewed JOIN (JOIN asimétrico) en un sistema distribuido?
A. Una clave de unión que tiene muy pocos valores únicos.
B. El sistema reparte aleatoriamente los datos sin considerar la clave de unión.
C. El shuffle de datos envía todos los datos con la misma clave de unión a un único nodo de trabajo, sobrecargándolo.
D. El uso de la cláusula LIMIT sin ORDER BY.
C
¿Por qué la “C” es la respuesta correcta?
Para entender esto, hay que entender cómo funciona un JOIN en un sistema distribuido (cluster).
La regla de oro del JOIN: Para unir la Tabla A con la Tabla B usando una clave (ej. ID_Cliente), el sistema necesita que todas las filas de A con ID_Cliente = 50 y todas las filas de B con ID_Cliente = 50 estén en el mismo ordenador físico al mismo tiempo para poder compararlas.
El Shuffle (Barajado): El sistema mueve los datos a través de la red basándose en la clave. Envía el “Cliente 50” al Nodo 1, el “Cliente 51” al Nodo 2, etc.
El problema (Skew/Asimetría): Si tienes una clave muy popular (por ejemplo, ID_Cliente = “NULL” o una categoría como “Usuarios Anónimos” que tiene 10 millones de filas), el proceso de Shuffle enviará esos 10 millones de filas a un solo nodo pobre e infeliz.
El resultado: Mientras los otros 99 nodos terminan su trabajo en 5 segundos y se quedan esperando, el nodo sobrecargado tarda 2 horas o se queda sin memoria (OOM) y falla. Esto es el Skewed JOIN.
¿Por qué las otras opciones no son la “causa subyacente”?
A. Una clave de unión que tiene muy pocos valores únicos.
⚠️ Parcialmente cierta, pero incompleta.
Explicación: Tener pocos valores únicos (Baja Cardinalidad) facilita el skew, pero no es la única causa.
El matiz: Podrías tener 1 millón de claves únicas (alta cardinalidad), pero si una sola de esas claves (ej. la clave “X”) representa el 90% de tus datos, tendrás un Skewed JOIN brutal, aunque haya muchas otras claves únicas. La opción C describe el mecanismo mecánico del fallo (el nodo sobrecargado por el shuffle) que aplica a todos los casos.
B. El sistema reparte aleatoriamente los datos sin considerar la clave de unión.
❌ Incorrecta.
Explicación: Esto es lo contrario a un JOIN estándar. Si el sistema repartiera los datos aleatoriamente, nunca podría hacer el JOIN correctamente (porque el Cliente A estaría en el nodo 1 y sus ventas en el nodo 5). El sistema está obligado a considerar la clave de unión, y esa obligación es lo que causa el problema.
D. El uso de la cláusula LIMIT sin ORDER BY.
❌ Incorrecta.
Explicación: Esto genera resultados no deterministas (cada vez que corres la consulta podrías ver filas distintas), pero es un problema de lógica de negocio, no de rendimiento o distribución de carga en el clúster.
Analogía del Supermercado
Imagina un supermercado con 10 cajas (Nodos).
Funcionamiento normal: Los clientes se reparten equitativamente.
Skewed JOIN (Opción C): Hay una regla que dice “Todos los que compren Pan deben ir a la Caja 1”.
Si el 90% de la gente compra pan, la Caja 1 tendrá una fila kilométrica (Sobrecarga/Bottleneck), mientras las cajas 2-10 están vacías. El problema es la regla de agrupación (Shuffle por clave) combinada con la popularidad del Pan (Dato asimétrico).
Resumen Mental
Síntoma: Un nodo trabaja mucho más que el resto (Straggler).
Causa Técnica (Opción C): El Shuffle concentra datos de una clave popular (Hot Key) en un solo lugar.
Solución típica: “Salting” (añadir un número aleatorio a la clave para dividirla artificialmente).
¿Qué estrategias de mitigación o solución se recomiendan para trabajar en torno a un Skewed JOIN? (selecciona todas las que apliquen)
A. Pre-filtrar las filas de la consulta que tienen la clave desequilibrada (unbalanced key).
B. Utilizar funciones de agregación exactas en lugar de aproximadas.
C. Potencialmente dividir la operación en dos consultas separadas.
D. Forzar el uso de un Broadcast JOIN.
A, C y D
¿Por qué A, C y D son las soluciones?Analicemos cada estrategia desde la más simple a la más técnica:1. Pre-filtrar (Opción A) - “La solución higiénica”La lógica: A menudo, el Data Skew es causado por “basura”. Por ejemplo, millones de filas tienen la clave NULL o una cadena vacía “” porque el sistema de origen falló al registrar el ID.La acción: Si esos datos no son útiles para tu análisis, simplemente elimínalos antes del JOIN (WHERE id IS NOT NULL).Resultado: Eliminas la clave masiva que iba a saturar un nodo. Problema resuelto sin ingeniería compleja.2. Broadcast JOIN (Opción D) - “La solución arquitectónica”La lógica: El Skew ocurre durante el Shuffle (mover la tabla grande a través de la red para agrupar claves).La acción: Si una de las dos tablas es pequeña (ej. una tabla de dimensiones de “Códigos de País”), puedes forzar un Broadcast JOIN.Cómo funciona: En lugar de mover la tabla gigante, el sistema copia la tabla pequeña completa y la envía a todos los nodos.Resultado: Cada nodo puede hacer el JOIN localmente con su pedazo de la tabla gigante sin necesidad de hacer Shuffle. Si no hay Shuffle, no hay Skew.3. Dividir la operación (Opción C) - “Divide y vencerás”La lógica: Si tienes una clave que es necesaria pero muy masiva (ej. “Cliente Genérico”), y no puedes usar Broadcast.La acción: Separas el proceso en dos:Una consulta solo para la clave problemática (quizás usando técnicas especiales).Otra consulta para el resto de los datos “normales”.Al final, haces un UNION ALL de los resultados.Resultado: Aíslas el problema para que no ralentice todo el pipeline.¿Por qué la B es incorrecta?B. Utilizar funciones de agregación exactas en lugar de aproximadas.❌ Incorrecta.El motivo: Esto mezcla peras con manzanas.Las funciones “aproximadas” (como APPROX_COUNT_DISTINCT) se usan para hacer conteos más rápidos sacrificando un poco de precisión (usando algoritmos como HyperLogLog).Usar una función “exacta” (COUNT(DISTINCT)) es más pesado computacionalmente y requiere más Shuffle, por lo que empeoraría el rendimiento, no solucionaría el bloqueo del JOIN.Resumen de Estrategias para el ExamenSi te encuentras con un Skewed JOIN, tu lista de chequeo mental debe ser:¿Es basura? $\rightarrow$ Filtra (Opción A).¿Cabe la otra tabla en memoria? $\rightarrow$ Broadcast Join (Opción D).¿Es un dato válido y gigante? $\rightarrow$ Divide la consulta (Opción C) o usa “Salting” (añadir aleatoriedad a la clave).
Asumiendo que el optimizador no reordena las expresiones, ¿cómo deben ordenarse las expresiones dentro de una cláusula WHERE para lograr la máxima selectividad temprana?
A. Las expresiones más costosas de ejecutar primero.
B. Las expresiones menos selectivas primero.
C. Las expresiones más selectivas primero.
D. El orden de las expresiones no tiene impacto en el rendimiento.
C
Cuando se intenta ordenar un conjunto de resultados muy grande mediante una cláusula ORDER BY sin restricciones, ¿por qué es común que ocurran errores de “Recursos Excedidos”?
A. El proceso de shuffle es incapaz de manejar el volumen de datos.
B. La ordenación final debe ser realizada por un único slot/nodo, lo que puede sobrecargarlo.
C. El I/O de lectura inicial se vuelve demasiado lento.
D. La clave de ordenación es demasiado larga.
B
Para mitigar el riesgo de errores por sobrecarga de recursos al ordenar un número muy grande de valores, ¿qué cláusula de optimización debe combinarse con ORDER BY?
A. GROUP BY
B. HAVING
C. LIMIT
D. EXCEPT
C
¿Cómo mejora el rendimiento distribuido la combinación de ORDER BY con LIMIT?
A. Permite que el nodo Maestro realice toda la ordenación.
B. Permite a los nodos de trabajo descartar valores que superan el límite en etapas intermedias, reduciendo el volumen de datos a mover.
C. Transforma la operación en un Broadcast Join.
D. Obliga a utilizar una clave de clustering.
B