Skip to content

Latest commit

 

History

History
141 lines (87 loc) · 10.2 KB

File metadata and controls

141 lines (87 loc) · 10.2 KB

Claves Subrogadas (Surrogate Keys)

Implementación de Claves Subrogadas para el Seguimiento de Cambios en Licitaciones mediante PySpark

En el diseño y la construcción de un Data Lake que sigue los principios de la Arquitectura Medallion (capas Bronze, Silver y Gold), el seguimiento preciso de la evolución temporal de las entidades es un pilar fundamental. Para el proyecto de la Plataforma de Contratación del Sector Público (PLACSP), hemos evolucionado desde el uso de claves naturales simples (identificadores oficiales) hacia una estrategia de Claves Subrogadas Deterministas.

Este documento detalla exhaustivamente los motivos, la implementación técnica y los beneficios arquitectónicos de este enfoque, así como la justificación del uso de MD5 como algoritmo principal.

1. El Desafío: Integridad Temporal y Normalización de Entidades

Las licitaciones públicas en España no son entidades estáticas; son procesos dinámicos que atraviesan múltiples estados y fases. Una licitación puede ser:

  1. Publicada: Se anuncia inicialmente el proceso.
  2. Modificada (Enmiendas/Rectificaciones): Se corrigen errores en los pliegos o se amplían los plazos.
  3. Adjudicada: Se selecciona provisionalmente a un ganador.
  4. Formalizada/Cancelada: Se cierra el contrato o se desiste del proceso.

El problema de la Clave Natural

Si utilizáramos el identificador oficial (normalmente una URI o un ID único proporcionado por la fuente) como clave primaria, cada actualización del proceso sobrescribiría el registro anterior. Esto resultaría en la pérdida irremediable del historial de cambios. No podríamos auditar qué cambió entre una versión y otra, ni reconstruir el estado de la licitación en una fecha pasada específica.

Además, el XML de CODICE estructura una licitación como un árbol único que contiene, anidados, sus lotes, documentos y resultados. Para almacenar estas entidades en tablas Delta separadas — manteniendo la integridad referencial entre ellas desde el momento de la extracción — es necesario contar con un identificador de versión estable que actúe como clave foránea desde la primera capa de procesamiento.

2. La Solución: Claves Subrogadas Basadas en Hashing (MD5)

Para resolver ambos problemas sin sacrificar eficiencia en sistemas distribuidos, implementamos Claves Subrogadas (SK) mediante hashing determinista. Aunque inicialmente se consideró SHA-256, hemos optado por MD5 por motivos de eficiencia y rendimiento.

MD5 vs. SHA-256: ¿Por qué MD5?

La elección del algoritmo de hashing depende del caso de uso. En este proyecto, priorizamos el rendimiento y el ahorro de recursos:

Criterio MD5 SHA-256
Longitud 32 caracteres (128 bits) 64 caracteres (256 bits)
Espacio Ocupa un 50% menos en disco y RAM Mayor consumo de almacenamiento
Velocidad Cálculo significativamente más rápido en Spark Más costoso computacionalmente
Colisiones Probabilidad despreciable ($1$ en $3.4 \times 10^{38}$) Prácticamente nula
Uso Ideal Generación de SK, CDC, Joins internos Anonimización (RGPD), Seguridad, Criptografía

Justificación técnica:

  1. Rendimiento en JOINs: Al generar identificadores de solo 32 caracteres, las operaciones de unión (JOIN) en Spark son ligeramente más veloces y consumen menos memoria en el clúster.
  2. Bajo Riesgo: Las claves subrogadas no se utilizan para cifrar datos sensibles, sino para identificar filas. Dado que no hay riesgo de ataques maliciosos sobre estos identificadores, la debilidad criptográfica de MD5 frente a SHA-256 es irrelevante.
  3. Probabilidad de Colisión: La probabilidad de una colisión accidental en MD5 es tan baja que es prácticamente imposible en datos normales de licitaciones.

3. Dónde Vive la Clave: Bronze y Silver con Roles Distintos

Un aspecto clave del diseño es que la sk_id se genera en la capa Bronze y se hereda y aprovecha en Silver. Estas dos capas la utilizan con propósitos diferentes pero complementarios.

3.1 Bronze: Clave Subrogada Estructural

La sk_id se calcula en bronze/src/extraction.py durante la extracción del XML. Su propósito en Bronze es estructural: permite vincular mediante clave foránea las tablas de entidades derivadas (lots, documents, results) con la tabla principal (entries), haciendo posible almacenarlas en tablas Delta separadas desde el momento de la extracción.

Sin esta clave, la única alternativa sería mantener lotes, documentos y resultados como columnas anidadas (ArrayType<StructType>) dentro de una única tabla entries. Eso simplificaría Bronze pero trasladaría la complejidad de normalización a Silver, donde ya existen suficientes responsabilidades semánticas.

La sk_id en Bronze responde a la pregunta: ¿a qué versión de qué entry pertenece este lote/documento/resultado?

from pyspark.sql.functions import md5, concat_ws, col

# Generación de la Clave Subrogada Estructural en Bronze
md5(
  concat_ws(
    "||",
    col("entry.id"),      # Identificador natural de la licitación
    col("entry.updated")  # Timestamp de la última modificación
  )
).alias("sk_id")

Explicación de los Componentes:

  • concat_ws("||", ...): El separador || evita colisiones de hash en casos donde dos valores diferentes, al concatenarse, pudieran dar el mismo resultado (p. ej., "12" + "3" vs. "1" + "23").
  • md5(...): Equilibrio óptimo entre velocidad de cálculo y longitud del identificador para el rendimiento de los joins.

3.2 Silver: Clave Subrogada para SCD Tipo 2

En Silver, la misma sk_id generada por Bronze no se recalcula: se hereda directamente de las tablas Bronze. Su rol en esta capa pasa de ser estructural a ser histórico: permite almacenar múltiples filas para el mismo expediente (una por cada versión publicada) e implementar Slowly Changing Dimensions de Tipo 2 (SCD Tipo 2).

La sk_id en Silver responde a la pregunta: ¿cuándo fue válida esta versión del expediente, y qué cambió respecto a la anterior?

4. Implementación Técnica en PySpark

La lógica de generación de estas claves está integrada en bronze/src/extraction.py, en las funciones extract_bidding_entries() y extract_deleted_entries().

Impacto en el Modelo de Datos Relacional

Para mantener la integridad referencial en todo el Data Lake, la sk_id se propaga a todas las entidades relacionadas desde Bronze:

  • Lotes (Lots): Cada lote se vincula a la sk_id de la versión de la licitación con la que fue publicado.
  • Documentos: Los pliegos y anexos se asocian a la versión exacta del anuncio que los referenciaba originalmente.
  • Resultados de Adjudicación: Permite trazar cómo cambiaron las condiciones de adjudicación en respuesta a enmiendas previas.

5. Beneficios Arquitectónicos y de Análisis

La implementación de Claves Subrogadas mediante hashing desbloquea capacidades analíticas avanzadas:

Normalización de Entidades desde Bronze

Al generar la sk_id en Bronze, es posible separar lotes, documentos y resultados en tablas Delta propias desde el momento de la extracción. Esto simplifica radicalmente la capa Silver, que trabaja con tablas ya normalizadas en lugar de tener que resolver arrays anidados antes de poder aplicar transformaciones semánticas.

Soporte para SCD Tipo 2 (Slowly Changing Dimensions)

Gracias a la sk_id, Silver puede almacenar múltiples filas para el mismo ID de licitación. Esto permite implementar una estrategia de Dimensiones de Cambio Lento de Tipo 2, donde cada fila representa una "fotografía" válida del proceso en un momento determinado del tiempo.

Rendimiento en Consultas (Query Performance)

En Delta Lake, las operaciones de unión (JOIN) sobre columnas de hash de longitud fija son extremadamente rápidas. Al evitar uniones basadas en cadenas de texto largas y variables (como las URIs originales), reducimos drásticamente el tiempo de ejecución de procesos ETL y consultas de BI.

Auditabilidad Total y Reconstrucción del Estado

Podemos reconstruir el estado de cualquier licitación en cualquier punto del tiempo simplemente filtrando por la columna entry_updated asociada a la clave hash. Esto es crítico para el cumplimiento normativo y el análisis forense de la contratación pública.

6. Gobernanza de Datos: Metadatos y Documentación

Para garantizar que el Data Lake sea autodescriptivo y facilite la labor de los científicos de datos y analistas, cada columna de clave subrogada se enriquece con metadatos específicos durante el proceso de transformación:

# Metadatos aplicados en Bronze (extraction.py)
df = df.withMetadata("sk_id", {
    "comment": "Clave Subrogada estructural generada en Bronze. Identifica unívocamente una versión del registro (id + updated). Heredada por Silver para SCD Tipo 2.",
    "business_logic": "MD5(id || updated)",
    "layer": "Bronze"
})

Esta práctica permite que herramientas de catálogo de datos o el comando DESCRIBE TABLE de Spark proporcionen contexto inmediato sobre el origen y el propósito de la clave.

7. Conclusión

La sk_id es una clave de doble propósito que actúa en dos capas con roles distintos pero complementarios:

Capa Rol de la sk_id Pregunta que responde
Bronze Clave foránea estructural ¿A qué versión de qué entry pertenece este lote/documento?
Silver Clave de versión para SCD Tipo 2 ¿Cuándo fue válida esta versión y qué cambió?

Generarla en Bronze —la capa de extracción estructural— permite normalizar las entidades del XML en tablas separadas sin perder integridad referencial, y sin trasladar esa complejidad a Silver. Silver hereda la clave y la aprovecha para el seguimiento histórico, manteniendo su responsabilidad exclusivamente en la semántica de negocio.

La transición de claves naturales a Claves Subrogadas basadas en Hash marca un hito en la madurez técnica del proyecto PLACSP. No solo asegura la preservación del historial completo de la contratación pública española, sino que también establece las bases para un sistema de datos escalable, performante y preparado para auditorías complejas en la capa analítica (Gold).