Capítulo 4. Fundamentos económicos de las tuberías
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
En los capítulos anteriores has aprendido a diseñar soluciones de computación y almacenamiento en la nube que ofrezcan las compensaciones adecuadas entre coste y rendimiento, teniendo en cuenta los objetivos generales del producto. Esto te proporciona una base sólida para un diseño rentable.
El siguiente paso consiste en diseñar e implantar canalizaciones de datos que se escalen eficazmente, limiten el despilfarro haciendo un uso inteligente de los recursos de ingeniería y computación, y minimicen el tiempo de inactividad de los datos. La primera parte de este proceso implica algunas estrategias de diseño fundamentales para los conductos de datos: idempotencia, puntos de control, reintentos automáticos y validación de datos.
En este capítulo, verás los problemas habituales de las canalizaciones de datos y cómo mitigarlos utilizando estas cuatro estrategias. En lugar de limitarme a definir la idempotencia, la comprobación, los reintentos y la validación de datos, ilustraré cómo aplicar estas estrategias en entornos por lotes y de flujo, y discutiré algunas de las ventajas y desventajas que encontrarás. También podrás ver cómo estas estrategias (o la falta de ellas) contribuyeron a los fracasos y éxitos de las canalizaciones del mundo real.
Idempotencia
El primer punto de partida es diseñar tus canalizaciones para que sean idempotentes. La idempotencia significa que puedes ejecutar repetidamente una canalización con los mismos datos de origen y los resultados serán exactamente los mismos. Esto tiene ventajas por sí mismo y es un requisito previo para implementar los reintentos, como verás más adelante en este capítulo.
Evitar la duplicación de datos
La definición de idempotencia puede variar en función de cómo se consuma la salida de la canalización. Una forma de entender la idempotencia es la ausencia de datos duplicados si el proceso se ejecuta varias veces con los mismos datos de origen.
Por ejemplo, considera una canalización que inserta datos en una base de datos recorriendo en bucle el conjunto de datos e insertando cada línea en la base de datos. Si se produce un error, como un fallo en la red que interrumpe la conexión con la base de datos, no podrías saber qué parte de los datos se escribió en la base de datos y cuál no. Si reintentas desde este estado, la tubería podría acabar creando datos duplicados.
Para que este proceso sea idempotente, puedes envolver las inserciones en la base de datos en una transacción, asegurándote de que si alguna de las inserciones fallara, se revertiría cualquier inserción anterior.1 Esto elimina la posibilidad de escrituras parciales.
También puedes crear sumideros de datos que rechacen los datos duplicados. Si puedes crear una clave única para los datos ingeridos, puedes detectar entradas duplicadas y decidir cómo tratarlas. En este caso, debes estar absolutamente seguro de que la clave que crees sea realmente única, como una clave natural. Ten en cuenta que las restricciones únicas pueden ralentizar las inserciones, ya que la base de datos necesita validar que la clave es única y actualizar el índice. En las bases de datos columnares y los lagos de datos, puedes imponer la unicidad mediante hash, impidiendo actualizaciones e inserciones si el hash coincide con los datos existentes. Algunos lagos de datos admiten claves de fusión, como la fusión de Delta Lake, donde puedes especificar una clave única e instrucciones sobre cómo manejar las coincidencias.
Consejo
Reducir la duplicación de datos reducirá los costes al limitar tu huella de almacenamiento. Dependiendo de cómo se utilicen los datos, esto también puede ahorrar en gastos de computación.
Cuando trabajes con almacenamiento en la nube, puedes utilizar un enfoque de sobrescritura, también conocido como borrado-escritura. Considera un proceso que se ejecuta una vez al día, escribiendo datos en una carpeta con el nombre de la fecha actual. Antes de escribir los datos, el proceso puede comprobar si ya existen datos en la carpeta de la fecha actual. Si es así, se borran los datos y se escriben los nuevos. Esto evita la ingestión parcial de datos, así como los datos duplicados.
Para los procesos de flujo, puedes lograr la idempotencia mediante la identificación única de los mensajes. Puedes conseguirlo configurando el productor de datos para que cree el mismo ID cuando se encuentren los mismos datos. En el lado del consumidor, puedes mantener un registro de los ID de los mensajes que ya se han procesado para evitar que se ingiera un mensaje duplicado.
Especialmente para los procesos de streaming o de larga duración, considera la posibilidad de mantener un registro duradero de la información que necesitas para garantizar la idempotencia. Por ejemplo, Kafka persiste los mensajes en el disco, asegurando que no se pierdan entre implementaciones o debido a interrupciones inesperadas.
La idempotencia puede ser difícil de garantizar, sobre todo en los procesos de flujo, donde tienes muchas opciones para manejar los mensajes. Ten en cuenta tu estrategia de reconocimiento de mensajes (ack); ¿reconoces los mensajes cuando se extraen de una cola o sólo cuando el consumidor ha terminado? Otra consideración es desde dónde leen tus consumidores; ¿es siempre el final del flujo o hay condiciones en las que puedes releer mensajes consumidos previamente?
Por ejemplo, si ack un mensaje cuando el consumidor lo lee de la cola, un fallo en el consumidor significa que el mensaje quedará sin procesar, dejando caer datos. Si, en cambio, los mensajes se ack sólo cuando el consumidor ha terminado de procesarlos, puedes tener la oportunidad de reprocesar el mensaje si se produce un fallo.
Esta oportunidad de reintentar también puede crear datos duplicados. Vi que esto ocurría en una canalización en la que, ocasionalmente, la fuente de datos producía un mensaje grande que aumentaba significativamente el tiempo de procesamiento. Estos procesos a veces fallaban en parte, creando datos parciales similares al ejemplo de la base de datos que has visto antes. Nuestro equipo solucionó el problema estableciendo un mensaje máximo para evitar los procesos de larga duración.
Tolerar la duplicación de datos
Dado que la idempotencia puede ser difícil de conseguir en el sentido de evitar la duplicación de datos, plantéate si necesitas la deduplicación de datos. Dependiendo del diseño de tu canalización y de los consumidores de datos, tal vez puedas permitir la duplicación de datos.
Por ejemplo, trabajé en una canalización que ingería y evaluaba los datos de los clientes para detectar la presencia de problemas específicos. En este caso, no importaba si los datos estaban duplicados; la pregunta era "¿Existe X?". Si en cambio la pregunta fuera cuántas veces se había producido X, habría sido necesaria la deduplicación de datos.
Otro lugar en el que puedes tolerar la duplicación son los datos de series temporales en los que sólo se utilizan los registros más recientes. Trabajé en una canalización que, idealmente, generaba registros una vez al día, pero si había errores, había que volver a ejecutar la canalización. Esto dificultaba especialmente la detección de duplicados, ya que los datos de origen cambiaban a lo largo del día, lo que significaba que los resultados de una ejecución anterior de la canalización podían generar registros diferentes a los de una repetición posterior. Para tratar este caso, añadí algunos metadatos que rastreaban la hora a la que se ejecutaba el trabajo y filtraba los registros sólo a la hora de ejecución más reciente. En este caso, había duplicación de datos, pero sus efectos quedaban mitigados por esta lógica de filtrado.
Si puedes tolerar una eventual deduplicación, podrías crear un proceso que limpiara periódicamente los duplicados. Algo así podría ejecutarse como un proceso en segundo plano para aprovechar los ciclos de computación sobrantes, como aprendiste en el Capítulo 1.
Punto de control
El punto de control es cuando el estado se guarda periódicamente a lo largo del funcionamiento del canal. Esto te proporciona una forma de reintentar el procesamiento de datos desde un último estado conocido si se produce un fallo en el canal.
Los puntos de control son especialmente importantes en el procesamiento de flujos. Si se produce un fallo mientras procesas el flujo, necesitas saber en qué parte del flujo te encontrabas cuando se produjo el fallo. Esto te indicará dónde reiniciar el procesamiento después de que se recupere la cadena.
También puedes beneficiarte de los puntos de control en los procesos por lotes. Por ejemplo, si tienes que adquirir datos de varias fuentes y luego realizar una transformación larga y de cálculo intensivo, puede ser una buena idea guardar los datos de origen. Si se interrumpe la transformación, puedes leer los datos de origen guardados y reintentar la transformación en lugar de volver a ejecutar el paso de adquisición de datos.
Esto no sólo reduce el gasto de tener que volver a ejecutar etapas de la tubería antes del fallo, sino que también tienes una caché de los datos de origen. Esto puede ser especialmente útil si los datos de origen cambian con frecuencia. En este caso, puedes perderte por completo la ingesta de algunos de los datos de origen si tienes que volver a ejecutar todo el canal.
Advertencia
Limpia los datos de los puntos de control en cuanto ya no los necesites. No hacerlo puede repercutir negativamente en el coste y el rendimiento. Por ejemplo, trabajé en una empresa en la que un gran DAG de Airflow realizaba un checkpoint de los datos después de cada tarea, pero no lo limpiaba una vez finalizado el trabajo. Esto creaba terabytes de datos adicionales que provocaban una alta latencia, hacían que la ejecución del DAG fuera insoportablemente lenta y aumentaban los costes de almacenamiento. Aunque no estoy familiarizado con los detalles, sospecho que hacer un punto de control después de cada tarea era una exageración, lo que es un buen recordatorio para ser juicioso sobre dónde utilizas el punto de control.
Elimina los datos comprobados después de que un trabajo se haya completado con éxito, o utiliza una política de ciclo de vida, como viste en el Capítulo 3, si quieres que persistan durante un breve periodo de tiempo para depuración.
Los datos intermedios también pueden ser útiles para depurar problemas del pipeline. Considera la posibilidad de incorporar una lógica para realizar un punto de control selectivo, utilizando criterios como el origen de los datos, la fase del canal o el cliente. A continuación, puedes utilizar una configuración para cambiar el punto de control según sea necesario, sin necesidad de implementar nuevo código. Esta granularidad más fina te ayudará a reducir el impacto de la comprobación en el rendimiento, sin dejar de cosechar los beneficios de capturar el estado para la depuración.
Para una granularidad aún mayor, considera la posibilidad de activar el punto de control por ejecución. Si un trabajo falla, puedes volver a ejecutarlo con el punto de control activado para capturar los datos intermedios para la depuración.
Por ejemplo, incorporé la comprobación de puntos en una etapa del proceso que recuperaba datos de una API. El punto de comprobación sólo se activaba si la canalización se ejecutaba en modo de depuración, lo que me permitía inspeccionar los datos recuperados si había problemas. El estado era pequeño, y se purgaba automáticamente al cabo de unos días como parte del ciclo de limpieza de metadatos de la canalización.
Reintentos automáticos
En el mejor de los casos, un rito de paso y, en el peor, un hecho cotidiano, volver a ejecutar un trabajo de canalización fallido (o muchos) es algo con lo que creo que la mayoría de los ingenieros de datos están familiarizados. Las repeticiones manuales de tareas no sólo son aburridas, sino también costosas.
En un proyecto en el que trabajé había un ingeniero de guardia dedicado, cuyo tiempo se dedicaba sobre todo a reejecutar trabajos fallidos. Considera esto por un momento: el coste de un ingeniero a tiempo completo y el coste de los recursos para volver a ejecutar trabajos fallidos. Muchas veces este coste no se tiene en cuenta; mientras se sigan cumpliendo los SLA, el coste de volver a ejecutar trabajos fallidos puede permanecer oculto. El coste de la reducción de la velocidad del equipo debido a un ingeniero menos es más difícil de cuantificar y puede llevar al agotamiento. Además, esta sobrecarga manual puede reducir tu capacidad de ampliación.
Según mi experiencia, un factor importante en el fracaso de los trabajos de canalización es la disponibilidad de recursos. Esto puede ser cualquier cosa, desde una interrupción del CSP que derribe parte de tu infraestructura, un parpadeo temporal en la disponibilidad de un servicio de credenciales o recursos insuficientemente aprovisionados, hasta la disponibilidad de la fuente de datos, como obtener un error 500 al realizar una solicitud de una API REST. Puede ser muy frustrante perder todo un trabajo de cálculo por un problema temporal como éste, por no hablar de los costes de nube desperdiciados.
La buena noticia es que muchos de estos asesinos de canalizaciones pueden gestionarse con procesos automatizados. Sólo tienes que saber dónde se producen estos fallos y aplicar estrategias de reintento. Recuerda que reintentar trabajos puede dar lugar a datos duplicados, por lo que la idempotencia es una condición previa para aplicar reintentos. Los puntos de control también pueden ser necesarios para los reintentos, como verás más adelante en esta sección.
Consideraciones sobre el reintento
A alto nivel, un reintento implica cuatro pasos:
Intenta un proceso.
Recibir un error reintentable.
Espera para reintentarlo.
Repítelo un número limitado de veces.
Generalmente, un error reintentable es el resultado de un proceso fallido que esperas que tenga éxito en un breve periodo de tiempo tras su intento inicial, de tal forma que tenga sentido retrasar la ejecución de la tubería.
Consejo
Los reintentos constantes y repetidos pueden ser un signo de falta de recursos, lo que puede conducir a un rendimiento deficiente de la canalización y limitar el escalado y la fiabilidad. Registrar los intentos de reintento te dará una idea de estos problemas y te ayudará a determinar si se necesitan recursos adicionales para mejorar el rendimiento del canal.
Como ejemplo, trabajé en una canalización que diferenciaba los tipos de errores de base de datos que recibía al realizar consultas. Un error podía deberse a la conectividad o a un problema con la consulta, como ejecutar una sentencia SELECT
contra una tabla que no existía. Un error de conectividad es algo que podría ser temporal, mientras que un error de consulta es algo que persistirá hasta que se realice un cambio en el código. Con errores separados, la canalización podría reintentar selectivamente consultas a la base de datos sólo si el error recibido estuviera relacionado con la conectividad.
Reintentar implica esperar un periodo de tiempo antes de volver a intentar el proceso. Siempre que sea posible, debes hacer que sea un evento no bloqueante, para que otros procesos puedan continuar mientras el proceso reintentado espera su siguiente intento. Si, por el contrario, permites que un proceso en espera ocupe una ranura de trabajador o un hilo, estarás perdiendo tiempo y recursos.
Puedes evitar el bloqueo en varios niveles de ejecución, por ejemplo utilizando métodos asíncronos o multihilo. Los ejecutores de tareas y los sistemas de programación como Airflow y Celery admiten reintentos no bloqueantes utilizando colas. Si una tarea reintentable falla, estos sistemas vuelven a colocar la tarea fallida en la cola, permitiendo que otras tareas avancen mientras la tarea reintentable espera el retardo del reintento.
Niveles de reintento en las cadenas de datos
Puedes pensar en los procesos de una canalización de datos en tres niveles: bajo, tarea y canalización. Los procesos de bajo nivel son aquellos en los que interactúas con un recurso, como hacer una solicitud a la API o escribir en una base de datos. Un error reintentable en estos casos podría ser un 429 de una API, indicando que se han producido demasiadas solicitudes en un periodo de tiempo determinado. Otro posible error reintentable es la contención de recursos, como esperar un slot de pool para escribir en una base de datos y superar el periodo de tiempo de espera.
Cuando se trabaja con servicios en la nube, los reintentos pueden ser un poco más complicados. Por ejemplo, trabajé en una canalización en la que se escribían datos en el almacenamiento en la nube. El tamaño de los datos era pequeño, y las peticiones estaban dentro de las limitaciones de ancho de banda y peticiones. Las subidas se realizaban con éxito la mayoría de las veces, pero de vez en cuando fallaba una subida, no por problemas con el servicio de almacenamiento, sino porque había un problema con el servicio de credenciales que concedía acceso al cubo de almacenamiento.
Nota
Ésta es una consideración importante cuando trabajas con servicios en la nube : aunque creas que estás interactuando con un único servicio, como el almacenamiento en la nube, pueden intervenir varios servicios en la gestión de tu solicitud.
Este problema fue especialmente difícil de resolver porque el mecanismo de reintento estaba encapsulado en la biblioteca del cliente CSP. Yo utilizaba el cliente Google Cloud Storage (GCS), que tenía su propia estrategia de reintento que sólo se aplicaba al servicio de almacenamiento. Como el servicio de credenciales era un servicio diferente, el reintento de GCS no gestionaba los casos en los que el servicio de credenciales no estaba disponible temporalmente. Al final, tuve que envolver el reintento del cliente GCS en un reintento personalizado utilizando la bibliotecatenacity para reintentar en los problemas del servicio de credenciales.
En el siguiente nivel por encima de los procesos de bajo nivel están los procesos de nivel de tarea. Puedes pensar en ellos como diferentes pasos de la canalización, incluidas tareas como ejecutar una transformación de datos o realizar una validación. Los procesos a nivel de tarea suelen incluir procesos de bajo nivel, como una tarea que escribe datos en el almacenamiento en la nube. En estos casos, tienes dos niveles de reintento: un reintento de bajo nivel que puede intentarse durante segundos o unos minutos, y la oportunidad de reintentar a nivel de tarea durante un periodo de tiempo mayor.
Como ejemplo, un pipeline en el que trabajé incluía una tarea que enviaba correos electrónicos a los clientes una vez finalizado el procesamiento de datos. La solicitud para enviar el correo electrónico iba a una API interna, que preparaba un correo personalizado basado en los datos del cliente. El canal tenía reintentos a nivel de tarea en la tarea "Enviar correo electrónico" y reintentos a bajo nivel en la solicitud a la API de correo electrónico interno, como se muestra en la Figura 4-1.
La temporización de la tarea y de los reintentos de bajo nivel se muestra en la Figura 4-2. La tarea "Enviar correo electrónico" comenzó al principio de la ventana de tiempo, haciendo una petición de bajo nivel a la API de correo electrónico interno. Si se recibía un error reintentable, se reintentaba la petición GET
utilizando un backoff exponencial. Puedes ver esto como el aumento de tiempo entre el primer, segundo y tercer reintento en la línea de tiempo "Reintentos de API de bajo nivel".
Si la petición GET
seguía fallando después de tres intentos, la tarea "Enviar correo electrónico" fallaba. Esto desencadenaba el reintento a nivel de tarea, que reintentaba durante un periodo de tiempo más largo. Mientras que el proceso de bajo nivel se reintentaba varias veces en el transcurso de unos minutos, el proceso a nivel de tarea lo hacía una vez a partir de los 10 minutos y aumentaba exponencialmente el periodo de espera con cada reintento. Mientras los errores devueltos por la API fueran reintentables, el reintento a nivel de tarea continuaría hasta una hora específica del día, después de la cual el correo electrónico dejaría de ser relevante.
Los reintentos a nivel de tarea son un lugar donde los puntos de control pueden ser especialmente útiles. Considera el proceso de HoD del Capítulo 1, en el que los datos del estudio de aves se unen a los datos de la base de datos de medios sociales de HoD. La Figura 4-3 muestra este proceso con el punto de control añadido, donde los datos se guardan en la nube en el cubo de Almacenamiento temporal después de extraer la información de las especies, antes del paso "Enriquecer con social". Si "Enriquecer con social" falla porque la base de datos de HoD está temporalmente ocupada, la canalización puede volver a intentar "Enriquecer con social" utilizando los datos del cubo de Almacenamiento temporal, en lugar de tener que volver a adquirir los datos de la encuesta y volver a ejecutar "Extraer especies".
Por último, también puedes experimentar fallos reintentables a nivel de canalización. Si piensas en la probabilidad de diferentes niveles de fallos recuperables, siendo el nivel bajo el más común, el nivel de canalización es el nivel menos probable, en parte porque ya has incorporado reintentos de bajo nivel y de nivel de tarea para manejar la mayoría de los fallos temporales.
En gran medida, los errores reintentables a nivel de canalización se deben a problemas temporales de infraestructura. Una posible fuente es la finalización de instancias interrumpibles. Como aprendiste en el Capítulo 1, éstas se encuentran entre las opciones de computación más baratas, pero pueden terminar antes de que se complete tu trabajo. En un entorno de contenedores como Kubernetes, pueden producirse problemas temporales de recursos si tus contenedores sobrepasan sus peticiones de recursos.
Construir estrategias de reintento para estas situaciones puede facilitar el uso de instancias interrumpibles baratas, al incorporar un mecanismo de autorreparación. Puede ser complicado determinar si una canalización ha fallado debido a la escasez de recursos y si esta escasez se debe a un problema temporal del servicio o a un problema no recuperable, como un trabajo que supera los recursos provisionados y seguirá fallando. En algunos casos, puedes obtener información de la infraestructura informática para ayudarte a averiguarlo.
Si utilizas instancias interrumpibles, puedes suscribirte a las notificaciones de terminación para ayudarte a identificar la inestabilidad resultante de la canalización. Cuando trabajaba en la canalización que experimentó problemas de falta de capacidad descritos en el Capítulo 1, pude capturar el motivo del fallo cuando el clúster de AWS EMR terminaba. Si el trabajo fallaba debido a una capacidad insuficiente, se ponía en marcha un mecanismo de reintento, que alertaba al equipo del fallo sólo si el trabajo seguía fallando tras unas horas de espera a que mejorara la capacidad.
Había algunos elementos clave en esta solución. En primer lugar, sabía por experiencia que estos problemas de recursos solían resolverse en pocas horas. En segundo lugar, tuve en cuenta la posibilidad de que el trabajo fuera simplemente demasiado grande para la capacidad asignada. Limité los reintentos a dos para no malgastar recursos en este caso. Otra forma de detectar un trabajo grande es comparar el tamaño de los datos con los valores históricos, como viste en "Ejemplo de autoescalado".
Este mecanismo de reintento redujo la intervención manual, los errores humanos y la fatiga por alerta. También redujo el coste de reejecutar los trabajos y mejoró el rendimiento de la tubería. Antes, cada trabajo fallido generaba una alerta, y un ingeniero reiniciaba manualmente el proceso. Si el problema era la capacidad insuficiente, el reintento también fallaba, lo que provocaba una cascada de fallos y recursos desperdiciados. Como los reintentos eran un proceso manual, inevitablemente nuestro equipo se perdía una de las repeticiones mientras esperaba a que se solucionaran los problemas de capacidad, con la consiguiente pérdida de datos.
Hasta ahora, este capítulo ha abarcado estrategias de diseño centradas en la mecánica de las canalizaciones de datos que te ayudarán a evitar la corrupción de datos y a recuperarte de los fallos intermitentes habituales. El último tema de este capítulo, la validación de datos, es una técnica que se puede incorporar a la ejecución de las canalizaciones para ayudarte a detectar los problemas con los datos antes de que se produzcan.
Validación de datos
La comedia de situación de los años 70 Laverne y Shirley comienza con el dúo dirigiéndose a sus puestos de trabajo en la fábrica de cerveza Shotz, donde inspeccionan las botellas de cerveza que salen de una cadena de montaje. A medida que pasan las botellas, Laverne y Shirley las observan, retirando las defectuosas para garantizar un producto de alta calidad.
La validación de datos es un poco como el control de calidad que realizaban Laverne y Shirley en sus trabajos: inspeccionar los datos a medida que pasan y deshacerse de los defectos antes de que lleguen a manos de los consumidores de datos.
La falta de validación de los datos es lo que condujo al error multimillonario que compartí en el Prefacio de este libro. La fuente de datos que nuestro equipo estaba ingiriendo añadía nuevas columnas de datos. Confiábamos en actualizar manualmente los esquemas para capturar los cambios en los datos de la fuente, una práctica que te mostraré cómo evitar en esta sección. El esquema no se había actualizado y no incluía las nuevas columnas, por lo que quedaban excluidas de la ingesta.
Antes de entrar en técnicas específicas de validación de datos, quiero que pienses en la validación de datos a alto nivel. Esto te ayudará a elaborar un plan de validación de datos en el que utilizarás las técnicas de este capítulo.
Según mi experiencia, la validación de datos tiene tres objetivos principales:
Evita el tiempo de inactividad de los datos.
Evita malgastar ciclos procesando datos erróneos.
Informa al equipo de los datos erróneos y de los fallos en las canalizaciones.
Para alcanzar estos objetivos, piensa en los datos de origen, en cómo se están procesando y en las expectativas de los datos resultantes. Puedes empezar con estas preguntas:
¿Qué constituye una fuente de datos válida? Por ejemplo, ¿hay atributos que deban estar presentes para que puedas confiar en que la fuente ha proporcionado buenos datos?
¿Debes ingerir todos los datos de una fuente, incluidos los nuevos atributos, o sólo un subconjunto?
¿Necesitan los datos determinados formatos, tipos de datos o atributos para una ingestión satisfactoria?
¿Existen relaciones deterministas entre las etapas del pipeline, como la forma de los datos, que puedas utilizar para identificar posibles problemas?
Reflexionar sobre estas cuestiones te ayudará a identificar las áreas en las que la validación de datos sería útil. Además, ten en cuenta la sobrecarga que supone añadir la validación en la canalización; se trata de otro proceso que actúa sobre los datos a medida que pasan por la ingesta y puede afectar al rendimiento dependiendo de cómo lo enfoques.
Por último, piensa qué quieres hacer si se produce un fallo en la validación de los datos. ¿Deben descartarse los datos erróneos? ¿Deben apartarse para su revisión? ¿Debe fallar el trabajo?
Validación de las características de los datos
En lugar de intentar captar cada problema individual de los datos, lo cual es una tarea imposible, piensa en la validación como la identificación de patrones en los datos. "Conocer tus datos" es el primer paso para procesar y analizar los datos con éxito. La validación implica codificar lo que sabes y esperas para evitar errores en los datos.
En esta sección, verás algunas comprobaciones básicas que son relativamente baratas de calcular y fáciles de poner en práctica, pero que dan grandes dividendos a la hora de erradicar problemas habituales con los datos. Estas comprobaciones incluyen:
Inspeccionar la forma y el tipo de los datos
Identificar datos corruptos
Comprobación de nulos
Tanto si estás limpiando datos como si los estás analizando, comprobar la forma de los datos a medida que pasan por las distintas etapas del proceso es una buena forma de detectar problemas. El problema de las columnas que faltan en el Prefacio del libro podría haberse evitado con una simple comprobación que comparara el número de columnas de los datos de entrada con el número de columnas del DataFrame que procesó los datos. De hecho, ésta fue una de las primeras comprobaciones de validación que nuestro equipo añadió para mitigar el problema.
Aunque comparar el número de columnas puede identificar los atributos que faltan, es necesario realizar comprobaciones adicionales de los nombres de las columnas y los tipos de datos cuando se añaden atributos. Por ejemplo, el paso "Extraer especies" de la Figura 4-3 añade una nueva columna, species
, a los datos brutos. Para comprobar que "Extraer especies" funcionó como se esperaba, podrías empezar por validar que hay N + 1 columnas en los datos de "Extraer especies". Suponiendo que esto se verifique, el siguiente paso es asegurarse de que los datos producidos por "Extraer especies" tienen los mismos nombres y tipos de columna que los datos de entrada del cubo Encuesta, además del nuevo campo species
. Esto verifica que has añadido la nueva columna y que no has eliminado ninguna columna de entrada.
Obtener la forma del DataFrame te proporciona un segundo dato: la longitud de los datos. Ésta es otra característica útil que debes comprobar a medida que los datos pasan por la canalización. Por ejemplo, esperarías que "Extraer especies" produjera el mismo número de filas que los datos de entrada.
En algunos casos, puedes estar trabajando con fuentes de datos que podrían proporcionar datos malformados. Esto es especialmente cierto si trabajas con fuentes de datos externas a tu organización, donde tienes una visibilidad limitada o nula de los posibles cambios. Una canalización de datos en la que trabajé ingería datos de docenas de API de terceros, que eran la columna vertebral del producto de la empresa. Si la canalización encontraba datos malformados, lanzaba una excepción, alertando al equipo de que algo iba mal.
Echemos un vistazo a algunos métodos para tratar los datos malformados, que puedes ver en el cuaderno de validación de este libro bajo el título "Identificar y actuar sobre los datos malformados".
Aquí tienes un ejemplo de JSON malformado en el que el último registro se escribió parcialmente:
bad_data
=
[
"{'user': 'pc@cats.xyz', 'location': [26.91756, 82.07842]}"
,
"{'user': 'lucy@cats.xyz', 'location': [26.91756, 82.07842]}"
,
"{'user': 'scout@cats.xyz', 'location': [26.91756,}"
]
Si intentas procesar esto con algo de Python básico, obtendrás una excepción en todo el conjunto de datos, aunque sólo un registro esté corrupto. Procesar cada fila te permitiría ingerir los registros buenos y, al mismo tiempo, aislar y responder a los corruptos.
Los DataFrames de PySpark te dan algunas opciones sobre cómo manejar los datos corruptos. El atributo mode
para leer datos JSON puede establecerse como PERMISSIVE
, DROPMALFORMED
, o FAILFAST
, proporcionando diferentes opciones de manejo para los datos malos.
El siguiente ejemplo muestra el atributo mode
establecido en PERMISSIVE
:
corrupt_df
=
spark
.
read
.
json
(
sc
.
parallelize
(
bad_data
),
mode
=
"PERMISSIVE"
,
columnNameOfCorruptRecord
=
"_corrupt_record"
)
corrupt_df
.
show
()
PERMISSIVE
leerá correctamente los datos, pero aislará los registros malformados en una columna separada para depuración, como puedes ver en la Figura 4-4.
PERMISSIVE
es una buena opción si quieres inspeccionar los registros corruptos. En un sistema de gestión de datos médicos en el que trabajé, la canalización aislaba registros como éste para que los inspeccionara un administrador de datos. El sistema de gestión de datos se utilizaba en el diagnóstico de pacientes, por lo que era fundamental que se ingirieran todos los datos. Depurar los datos malformados daba al personal médico la oportunidad de arreglar los datos de origen para volver a ingerirlos.
DROPMALFORMED
hace lo que parece: los registros corruptos se eliminarían por completo del Marco de datos. El resultado serían los registros 0-1 de la Figura 4-4. Por último, FAILFAST
lanzaría una excepción si algún registro estuviera mal formado, rechazando todo el lote.
Si se requieren determinados atributos para que ingieras correctamente los datos, la comprobación de nulos durante la ingesta puede ser otra actividad de validación. Puedes hacerlo comprobando el atributo requerido en los datos de origen o haciendo una comprobación de nulos en una columna del Marco de datos. También puedes hacer comprobaciones de nulos con esquemas.
Esquemas
Los esquemas pueden ayudarte a realizar una validación adicional, como la comprobación de cambios en el tipo de datos o en los nombres de los atributos. También puedes definir atributos obligatorios con un esquema, que puede utilizarse para comprobar si hay valores nulos. DoorDash utiliza la validación de esquemas para mejorar la calidad de los datos, como se describe en "Creación de un procesamiento de eventos en tiempo real escalable con Kafka y Flink".
Los esquemas también pueden limitar los datos a sólo los atributos que necesitas para la ingestión. Si sólo necesitas unos pocos atributos de un gran conjunto de datos, utilizar un esquema para cargar sólo esos atributos reducirá los costes de cálculo y almacenamiento al no procesar ni almacenar datos superfluos y no utilizados.
Otro uso de los esquemas es como contratos de servicio, estableciendo una expectativa tanto para los productores como para los consumidores de datos en cuanto a las características requeridas para la ingestión. Los esquemas también pueden utilizarse para generar datos sintéticos, tema que verás en el Capítulo 9.
En las siguientes subsecciones, cubro cómo crear, mantener y utilizar esquemas para la validación. Puedes encontrar el código correspondiente en el cuaderno de validación.
En un mundo ideal, las fuentes de datos publicarían esquemas precisos y actualizados de los datos que proporcionan. En realidad, tienes suerte si encuentras documentación sobre cómo acceder a los datos, por no hablar de un esquema.
Dicho esto, cuando trabajes con fuentes de datos desarrolladas por equipos de tu empresa, es posible que puedas obtener información sobre el esquema. Por ejemplo, una tubería en la que trabajé interactuaba con una API desarrollada por otro equipo. El equipo de la API utilizaba anotaciones Swagger y tenía un proceso automatizado que generaba esquemas JSON cuando la API cambiaba. La canalización de datos podía obtener estos esquemas y utilizarlos tanto para validar la respuesta de la API como para mantener actualizados los datos de prueba de la canalización, un tema sobre el que aprenderás en el Capítulo 9.
Crear esquemas
La mayoría de las veces tendrás que crear tus propios esquemas. Posiblemente más importante que crear los esquemas, es mantenerlos actualizados. Un esquema inexacto es peor que no tener esquema. Empecemos por ver formas de crear esquemas, y en la siguiente sección verás formas de mantenerlos actualizados con una intervención manual mínima.
Como ejemplo, veamos la creación de esquemas para el conducto de datos de la encuesta de la Figura 4-3. La Tabla 4-1 contiene una muestra de los datos brutos de la encuesta.
Usuario | Ubicación | Archivos de imagen | Descripción | Cuenta |
---|---|---|---|---|
pc@cats.xyz | ["26.91756", "82.07842"] | Varios jilgueros menores en el patio hoy. | 5 | |
sylvia@srlp.org | ["27.9659", "82.800"] | s3://bird-2345/34541.jpeg | Mañana ventosa, nublada. Vi una garza nocturna coronada negra en la vía navegable intercostera. | 1 |
birdlover124@email.com | ["26.91756", "82.07842"] | s3://bird-1243/09731.jpeg, s3://bird-1243/48195.jpeg | Esta tarde me he acercado a la colonia de garzas y he visto algunas garzas reales. | 3 |
Los datos del estudio contienen una fila por cada avistamiento registrado por un usuario de una aplicación de estudio de aves. Incluye el correo electrónico del usuario, su ubicación y una descripción libre. Los usuarios pueden adjuntar imágenes a cada avistamiento, que la aplicación de encuestas almacena en un cubo de almacenamiento en la nube, cuyos enlaces figuran en la columna "Archivos de imagen" de la tabla. Los usuarios también pueden proporcionar un recuento aproximado del número de aves avistadas.
En "Validación de las características de los datos", mencioné que el campo location
es utilizado por el paso "Extraer especies" de la Figura 4-3. Aquí está el código que extrae las especies, que puedes ver en transform.py:
def
apply_species_label
(
species_list
,
df
):
species_regex
=
f
".*(
{
'|'
.
join
(
species_list
)
}
).*"
return
(
df
.
withColumn
(
"description_lower"
,
f
.
lower
(
'description'
))
.
withColumn
(
"species"
,
f
.
regexp_extract
(
'description_lower'
,
species_regex
,
1
))
.
drop
(
"description_lower"
)
)
Lo que se espera de este código es que description
sea una cadena que se pueda poner en minúsculas y buscar una coincidencia de especie.
Otro paso del proceso extrae la latitud y la longitud del campo location
y se utiliza para agrupar a los usuarios en regiones geográficas similares. Esta es una característica importante de la plataforma HoD, ya que ayuda a los compañeros amantes de las aves a agruparse para realizar viajes de observación de aves. La ubicación se representa como una matriz de cadenas, donde el código subyacente espera un formato de [latitude, longitude]
:
df
.
withColumn
(
"latitude"
,
f
.
element_at
(
df
.
location
,
1
))
.
withColumn
(
"longitude"
,
f
.
element_at
(
df
.
location
,
2
))
Así que, como mínimo, un esquema para los datos de la encuesta debería incluir restricciones para que el campo location
sea una matriz de valores de cadena y el campo description
sea una cadena.
¿Qué ocurre si alguno de estos atributos es nulo? Es una pregunta interesante. Una pregunta mejor es cómo debe procesar los datos la canalización si estos atributos son nulos. Por ejemplo, si el campo location
es nulo, el código para extraer la latitud y la longitud lanzará una excepción. Esto podría ser un error del código, o podría ser que location
es un campo obligatorio dentro de los datos de la encuesta, lo que significa que un valor location
nulo es señal de un problema con los datos de origen y debería fallar la validación de datos.
Para este ejemplo, digamos que location
es un campo obligatorio, y un location
nulo indica un problema con los datos. Además, user
es un campo obligatorio, y count
, si se proporciona, debe ser coercible a un número entero. En cuanto a los atributos que deben ingerirse, digamos que sólo deben ingerirse las cinco columnas actuales de la Tabla 4-1, y que si se añaden nuevos atributos a los datos de la encuesta, deben ignorarse.
Con estos criterios en mente, vamos a echar un vistazo a algunas formas distintas de generar un esquema utilizando la muestra de datos de initial_source_data.json. Puedes encontrar este código en el apartado "Validación de esquemas" del cuaderno de validación.
Si trabajas con DataFrames, puedes leer la muestra de datos y guardar el esquema. Puede que tengas que cambiar la información anulable para que se ajuste a tus expectativas. Basándose en la muestra de datos de initial_source_data.json, el esquema inferido asume que el valor anulable del campo descripción debe ser True
:
df
=
(
spark
.
read
.
option
(
"inferSchema"
,
True
)
.
json
(
"initial_source_data.json"
))
source_schema
=
df
.
schema
source_schema
>
StructType
(
[
StructField
(
"count"
,
LongType
(),
True
),
StructField
(
"description"
,
StringType
(),
True
),
StructField
(
"user"
,
StringType
(),
False
),
StructField
(
"img_files"
,
ArrayType
(
StringType
(),
True
),
True
),
StructField
(
"location"
,
ArrayType
(
StringType
(),
True
),
False
)]
)
Si tu lógica de transformación de datos asume que el campo description
siempre se rellenará, querrás modificar este valor a False
para su validación.
Otra forma de validar los datos de la encuesta sobre aves es con un esquema JSON, que permite más definición que el esquema Spark. Puedes generar un esquema JSON a partir de datos de muestra utilizando algunas herramientas online, o a mano si los atributos de los datos son pocos.
Validar con esquemas
Estos métodos de generación de esquemas te dan un punto de partida. A partir de aquí, tendrás que refinar el esquema para asegurarte de que genere errores de validación, como cambiar los valores anulables en el esquema Spark generado.
Por ejemplo, el uso de una herramienta generadora de esquemas JSON para datos_de_origen_iniciales.json proporcionó el esquema que se muestra en la sección "Trabajar con esquemas JSON" del cuaderno de validación. Observa cómo el generador de esquemas definió location
:
#
"location"
:[
"26.91756"
,
"82.07842"
]
"location"
:
{
"type"
:
"array"
,
"items"
:
[
{
"type"
:
"string"
},
{
"type"
:
"string"
}
]
}
Recuerda que el código que extrae la latitud y la longitud espera dos elementos en location
.
Al validar este esquema con algunos datos de prueba, puedes ver que esta definición no es suficiente para garantizar que el campo location
tenga dos elementos. Por ejemplo, el JSON de short_location
sólo tiene una cadena, pero no se produce ninguna salida al ejecutar esta línea, lo que significa que la validación ha tenido éxito:
validate
(
short_location
,
initial_json_schema
)
Para utilizar este esquema JSON para la validación de datos, debes especificar el minItems
en la matriz location
, como en esta definición de esquema actualizada para location
:
"location"
:
{
"type"
:
"array"
,
"minItems"
:
2
,
"items"
:
[
{
"type"
:
"string"
}
]
},
Ahora viene la parte emocionante: comprueba todos los datos erróneos que se marcan con esta nueva definición.
Los siguientes errores de validación de la biblioteca jsonschema de Python te indican exactamente qué había de malo en los datos, proporcionándote una forma tanto de detener la ejecución si se encuentran datos erróneos como de proporcionar información de depuración útil :
- No hay suficientes elementos
validate
(
short_location
,
updated_schema
)
>
ValidationError
:
[
'26.91756'
]
is
too
short
- Si el tipo de datos de
location
cambia de matriz a cadena validate
([{
"user"
:
"someone"
,
"location"
:
"26.91756,82.07842"
}],
updated_schema
)
>
ValidationError
:
'26.91756,82.07842'
is
not
of
type
'array'
- Si el proveedor de datos topográficos decide cambiar el tipo de datos de los valores de latitud y longitud de cadena a numérico
validate
([{
"user"
:
"pc@cats.xyz"
,
"location"
:[
26.91756
,
82.07842
]}],
updated_schema
)
>
ValidationError
:
26.91756
is
not
of
type
'string'
Estos dos últimos errores de que la ubicación cambie de una matriz a una cadena o de una matriz de cadenas a una matriz de flotantes también pueden identificarse cuando se utiliza un esquema Spark. Si trabajas con conjuntos de datos Spark en Scala o Java, puedes utilizar el esquema para lanzar una excepción si los datos de origen no coinciden.
En otras situaciones, puedes comparar el esquema esperado con el esquema que se infiere al leer los datos de origen, como se describe en la sección "Comparar esquemas inferidos frente a explícitos" del cuaderno de validación.
Como ejemplo, digamos que tienes un esquema esperado para los datos del estudio de aves, source_schema
, donde location
es una matriz de cadenas. Para validar un nuevo lote de datos con source_schema
, lee los datos en un DataFrame y compara el esquema inferido con el esquema esperado. En este ejemplo, el campo location
de string_location.json es una cadena:
df
=
(
spark
.
read
.
option
(
"inferSchema"
,
True
)
.
json
(
'string_location.json'
))
inferred_schema
=
df
.
schema
>
StructType
(
[
...
StructField
(
"location"
,
StringType
(),
True
)
...
])
inferred_schema
==
source_schema
False
Esta comprobación es útil para señalar un fallo de validación, pero no es muy útil para informar de las diferencias específicas entre los esquemas. Para obtener más información, el código siguiente comprueba tanto los campos nuevos como las discordancias en los campos existentes:
source_info
=
{
f
.
name
:
f
for
f
in
source_schema
.
fields
}
for
f
in
inferred_schema
.
fields
:
if
f
.
name
not
in
source_info
.
keys
():
(
f
"New field in data source
{
f
}
"
)
elif
f
!=
source_info
[
f
.
name
]:
source_field
=
source_info
[
f
.
name
]
(
f
"Field mismatch for
{
f
.
name
}
Source schema:
{
source_field
}
,
Inferred
schema
:
{
f
}
")
>
Field
mismatch
for
location
Source
schema
:
StructField
(
location
,
ArrayType
(
StringType
,
true
),
true
),
Inferred
schema
:
StructField
(
location
,
StringType
,
true
)
Otra comprobación de validación útil que podrías añadir a este código es marcar los campos de source_schema
que faltan en inferred_schema
.
Este tipo de lógica de comparación de esquemas fue otra técnica de validación utilizada para solucionar el fallo de las columnas omitidas. Si el pipeline hubiera tenido una comprobación de validación como ésta desde el principio, nuestro equipo habría sido alertado de la existencia de nuevas columnas en los datos de origen desde el primer lote en el que se hubiera producido el cambio.
Mantener los esquemas actualizados
Como mencioné al principio de esta sección, es absolutamente esencial que los esquemas se mantengan actualizados. Los esquemas obsoletos son peor que inútiles; pueden provocar fallos de validación erróneos o pasar por alto fallos de validación reales debido a definiciones de datos obsoletas. Esta sección describe algunos métodos para automatizar el mantenimiento de los esquemas.
Para los esquemas que se generan a partir del código fuente, como con el ejemplo de Swagger que he mencionado antes o exportando una clase a un esquema JSON, considera las compilaciones automatizadas y los repositorios de esquemas centralizados. Esto proporciona una única fuente de verdad para todos los que accedan a los esquemas. Como parte del proceso de construcción automatizado, puedes utilizar los esquemas para generar datos de prueba para las pruebas unitarias. Con este enfoque, los cambios en los esquemas que se rompan aparecerán como fallos en las pruebas unitarias. Prometo que ésta es la última vez que digo "como verás en el Capítulo 9". Los esquemas son geniales, y hay más usos geniales para ellos además de su noble uso en la validación de datos.
Las comprobaciones de validación también pueden utilizarse para mantener los esquemas actualizados. Si una canalización está diseñada para ingerir todos los atributos de la fuente de datos, podrías generar un proceso para actualizar el esquema cuando se produzcan cambios no significativos en los datos de origen. Por ejemplo, si la canalización de la Figura 4-3 está diseñada para ingerir todos los campos proporcionados por los datos del estudio de aves, siempre que los campos de usuario y ubicación estén presentes y sean válidos, podrían añadirse nuevas columnas al esquema fuente.
Si adoptas la práctica de actualizar automáticamente los esquemas para que contengan más o menos atributos en función de los datos de origen, asegúrate de tener en cuenta los nulos. Especialmente con formatos semiestructurados como JSON, es posible obtener algunos lotes de datos en los que el valor de un atributo no se haya eliminado de la fuente, sino que simplemente sea nulo.
Otra cosa a tener en cuenta es el impacto de cambiar el esquema. Si se añaden nuevos atributos, ¿hay que rellenar los valores de los datos antiguos? Si se suprime un atributo, ¿deben conservarlo los datos antiguos?
Por último, puede ser eficaz un enfoque pseudoautomatizado de las actualizaciones del esquema, en el que se te avise de los cambios que rompan el esquema en los datos de origen. Esto podría deberse a un fallo de validación, o podrías configurar un trabajo programado para comparar periódicamente el esquema con una muestra de la fuente de datos. Incluso podrías incluir esta comprobación en el proceso, adquiriendo una muestra de datos, ejecutando la validación del esquema y saliendo sin ningún paso posterior.
Te insto a que evites depender de las actualizaciones manuales para mantener los esquemas al día. Si ninguna de las opciones anteriores te funciona, esfuérzate por mantener tus esquemas al mínimo para limitar lo que tienes que mantener. Además, haz que el mantenimiento de los esquemas forme parte del proceso de publicación para asegurarte de que se atienden regularmente.
Resumen
La combinación de la infraestructura de la nube, los servicios de terceros y la idiosincrasia del desarrollo de software distribuido allana el camino para una cornucopia de posibles mecanismos de fallo de la canalización. Cambios en los datos de origen o en el código del canal, credenciales incorrectas, contención de recursos y fallos en la disponibilidad de los servicios en la nube son sólo algunas posibilidades. Estos problemas pueden ser temporales, como unos segundos en el momento exacto en que intentas escribir en el almacenamiento en la nube, o más permanentes, como el cambio sin previo aviso del tipo de datos de un importante campo de datos fuente. En el peor de los casos, pueden dar lugar a un tiempo de inactividad de los datos, como en el caso del error multimillonario.
Si este panorama suena sombrío, no desesperes. Ahora estás bien equipado para construir canalizaciones que puedan prevenir y recuperarse de muchos de estos problemas con un mínimo de gastos generales de ingeniería, reduciendo el coste de los recursos en la nube, el tiempo de ingeniería y la pérdida de confianza en la calidad de los datos por parte de los consumidores de datos.
La idempotencia es un primer paso importante para construir canalizaciones atómicas que admitan reintentos y limiten la duplicación de datos.
Para los procesos por lotes, las transacciones de borrado-escritura y de base de datos soportan la idempotencia, garantizando que el lote se procesa en su totalidad o no se procesa en absoluto, lo que te da una pizarra limpia desde la que reintentar sin el potencial de duplicación de datos. También puedes evitar la duplicación en el sumidero de datos aplicando restricciones únicas, como con claves primarias, que impedirán que se registren datos duplicados.
Para el procesamiento de flujos, construir productores que garanticen IDs únicos basados en los datos de origen y consumidores que procesen cada clave única sólo una vez proporciona idempotencia. Cuando construyas estos sistemas, asegúrate de tener en cuenta cómo se consumen, acusan recibo y reintentan los mensajes. El almacenamiento duradero de los datos de idempotencia te ayudará a garantizar la idempotencia en caso de interrupciones e implementaciones.
Ten en cuenta que puedes tolerar cierta duplicación de datos dependiendo de cómo se utilicen los datos de la tubería. Si los datos se utilizan para verificar la ausencia o presencia de determinadas características, los datos duplicados pueden estar bien. Considera la posibilidad de deduplicar los datos después de la ingesta si hay un intervalo de tiempo entre el momento en que finaliza la ingesta y el momento en que se accede a los datos. Adjuntar metadatos para filtrar los duplicados cuando se consultan los datos es otra opción, como has visto en el ejemplo de utilizar para el análisis sólo los datos del tiempo de ejecución más reciente.
Una vez establecida la idempotencia, el siguiente elemento fundamental del diseño es el punto de control, que proporciona un estado conocido desde el que reintentar si se produce un fallo, así como información de depuración cuando se investigan problemas. Puedes beneficiarte de esta técnica sin sacrificar el rendimiento ni el gasto en la nube automatizando la eliminación de los datos de los puntos de control.
Los puntos de comprobación y la idempotencia sientan las bases para los reintentos automáticos, que permiten a las canalizaciones recuperarse de problemas temporales. Esto reduce el desperdicio de recursos y la intervención manual, y también puede ayudarte a aprovechar la computación barata e interrumpible.
Cuando busques oportunidades para utilizar reintentos, piensa en procesos que puedan fallar temporalmente y recuperarse en un plazo de tiempo razonable en función de las necesidades de rendimiento de tu canalización. Algo como la conectividad de la base de datos podría ser un problema temporal y recuperarse, mientras que una consulta inválida a la base de datos no tendría éxito, no importa cuántas veces lo intentes.
Superar los problemas temporales implica esperar entre intentos de reintento. Los reintentos no bloqueantes te ayudarán a mantener el rendimiento y limitar los ciclos desperdiciados. No olvides registrar los intentos de reintento, ya que esto puede ayudarte a introspeccionar los problemas de rendimiento y escalabilidad.
La idempotencia, los puntos de control y los reintentos automáticos son como el hardware de una cadena de montaje: componentes que trabajan juntos para que todo funcione con eficacia. Esto es necesario pero no suficiente para producir un producto de calidad. También necesitas un par de ojos agudos que comprueben la calidad del producto, expulsen los productos defectuosos antes de que lleguen a los clientes y den la alarma si hay algún problema en la cadena de montaje.
La validación de datos es como Laverne y Shirley, inspeccionando diligentemente y, si es necesario, rechazando los datos a medida que se mueven por la tubería. Si alguna vez has tenido que depurar datos erróneos a mano, peinando registros y conjuntos de datos para determinar qué falló y cuándo, seguro que puedes apreciar los enfoques automatizados que se describen en este capítulo, desde la comprobación de la forma y el formato de los datos hasta el uso de esquemas para validar el nombre, el tipo y la existencia de los atributos en función de las expectativas.
Los esquemas pueden ser herramientas poderosas para la validación de datos y otras actividades de canalización de datos que describiré en capítulos posteriores, pero deben mantenerse actualizados para que sean un activo y no un pasivo.
Del mismo modo que las estrategias tratadas en este capítulo proporcionan una base para el diseño de canalizaciones, un entorno de desarrollo eficaz proporciona la base para la implementación y las pruebas de las canalizaciones. En el próximo capítulo, trataré las técnicas para reducir costes y agilizar los entornos de desarrollo de canalizaciones de datos.
1 Esto supone que la base de datos es compatible con ACID.
Get Canalizaciones de datos rentables now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.