10 puntos por GN⁺ 2025-12-16 | 1 comentarios | Compartir por WhatsApp
  • UUID v4 tiene un alto grado de aleatoriedad, lo que provoca ineficiencia en los índices y exceso de I/O; al usarse como clave primaria en PostgreSQL, causa una degradación del rendimiento
  • Debido a las inserciones aleatorias, se vuelven frecuentes las divisiones de página (page splits) y la fragmentación de índices, lo que aumenta el tamaño del WAL y genera latencia de escritura
  • Un UUID ocupa 16 bytes, el doble que un bigint, lo que reduce la tasa de aciertos de caché y lleva a desperdicio de memoria
  • A menudo se confunde con un identificador de seguridad, pero según la RFC 4122, UUID no es un mecanismo de seguridad para evitar adivinación
  • Para nuevas bases de datos se recomienda usar claves basadas en secuencias enteras y, si no hay alternativa, optar por UUID v7 ordenado por tiempo

Problemas de rendimiento de UUID v4

  • Las bases de datos en PostgreSQL que usan UUID v4 como clave primaria han mostrado de forma consistente durante los últimos 10 años degradación del rendimiento y exceso de I/O
    • UUID v4 genera 122 bits aleatorios, por lo que no permite ordenamiento eficiente en índices
    • Como las inserciones no se guardan en páginas secuenciales, se produce acceso aleatorio y también se requieren búsquedas ineficientes en actualizaciones y eliminaciones
  • Los índices B-Tree asumen datos ordenados, pero UUID v4 no tiene orden, así que la eficiencia de inserción es baja
    • Cada inserción se escribe en una página arbitraria, lo que provoca frecuentes divisiones de páginas intermedias
    • Esto genera latencia de escritura y aumento del WAL

Estructura de UUID y alternativas

  • UUID es un identificador de 128 bits (16 bytes), y en PostgreSQL se almacena como tipo uuid binario
  • UUID v4 se basa en bits aleatorios, mientras que UUID v7 incluye un timestamp en los primeros 48 bits, lo que mejora la eficiencia de los índices
  • PostgreSQL 18 (previsto para 2025) incluirá soporte nativo para UUID v7
  • UUID v7 permite ordenamiento cronológico, mejorando la densidad de páginas y la eficiencia de caché

Motivos para elegir UUID y sus límites

  • UUID se usa cuando se necesita generación de identificadores sin colisiones en entornos con múltiples clientes o microservicios
    • Ejemplo: generar IDs al mismo tiempo en varias instancias de base de datos
  • Sin embargo, la RFC 4122 indica que “no se debe asumir que UUID es difícil de adivinar”, por lo que no es adecuado como identificador de seguridad
  • La probabilidad de colisión llega al 50% al generar 2.71×10¹⁸ UUID, así que en la práctica las colisiones son poco probables, pero el costo en rendimiento es alto

Ineficiencia de espacio e I/O de UUID

  • UUID ocupa el doble que bigint (8 bytes) y cuatro veces más que int (4 bytes)
    • En tablas grandes, esto se traduce en más almacenamiento y en mayores tiempos de respaldo y restauración
  • Resultados del experimento de densidad de páginas de índice
    • índice integer: 97.64%
    • índice UUID v4: 79.06%
    • índice UUID v7: 90.09%
  • En una prueba de Cybertec se confirmó que, al consultar un índice UUID v4, hubo 8.5 millones de accesos adicionales a páginas y un 31229% más de I/O
    • Bajo las mismas condiciones, el índice bigint tuvo 27,332 accesos a búfer, mientras que UUID v4 tuvo 8,562,960

Impacto en caché y memoria

  • UUID, por su distribución aleatoria, reduce la tasa de aciertos de la caché de búfer (cache hit ratio)
    • Hay que cargar más páginas en caché, y las páginas necesarias se expulsan (eviction) con frecuencia
  • La menor eficiencia de caché provoca latencia en las consultas y mayor uso de memoria
  • Para mantener el rendimiento se recomienda usar reconstrucción periódica de índices (REINDEX CONCURRENTLY) o pg_repack

Cómo mitigar el impacto en el rendimiento

  • Ampliar memoria: se recomienda contar con RAM equivalente a 4 veces el tamaño de la base de datos (ejemplo: DB de 25 GB → 128 GB de memoria)
  • Ajustar work_mem: asignar más memoria a operaciones de ordenamiento puede mejorar el rendimiento
  • En entornos Rails, se puede usar la configuración implicit_order_column para ordenar por un campo como created_at en lugar de UUID
  • Con el comando CLUSTER se puede reorganizar la tabla según un campo ordenable, aunque requiere un bloqueo exclusivo

Recomendación: claves enteras y secuencias

  • Para bases de datos nuevas se recomienda usar claves basadas en secuencias enteras
    • integer (4 bytes) permite unos 2 mil millones de valores, y bigint (8 bytes) ofrece muchos más valores únicos
  • Para la mayoría de las aplicaciones de negocio, integer es suficiente; en servicios de gran escala, conviene usar bigint
  • Una alternativa realista a UUID v4 es usar UUID v7 o la extensión sequential_uuids

Resumen

  • UUID v4 provoca ineficiencia en índices, alto I/O y baja eficiencia de caché debido a su aleatoriedad
  • No puede usarse como identificador de seguridad y además implica desperdicio de espacio
  • Las claves de secuencia enteras son más adecuadas para la mayoría de las aplicaciones
  • Si es indispensable usar UUID, se debe elegir UUID v7 ordenado por tiempo
  • Conviene evitar usar gen_random_uuid() como clave primaria en PostgreSQL

1 comentarios

 
GN⁺ 2025-12-16
Comentarios en Hacker News
  • Este es un ejemplo típico de optimización prematura
    Meter datos dentro de un identificador permanente es algo casi prohibido en la gestión de datos
    Si pones la fecha de nacimiento en un ID, como en el número de identificación noruego, luego pueden surgir casos de inmigrantes cuya fecha de nacimiento se registró mal, o problemas porque hay demasiadas personas con fecha 1 de enero y se agotan los números
    En la época de los catálogos en fichas tenía sentido mezclar datos e identificadores para reducir el costo de búsqueda, pero hoy, con bases de datos potentes, ya no hace falta

    • En realidad creo que este ejemplo trata de un problema de valor por defecto mal definido
      El problema fue usar el 1 de enero para cumpleaños desconocidos, no el hecho de poner la fecha dentro de la clave
      Si hubieran usado un valor no válido como 00 o 99, no habría habido conflicto
      Poner una marca de tiempo en un UUID no busca darle significado, sino optimizar el rendimiento
      Las claves que aumentan en orden temporal reducen el costo de reescritura del B-tree y mejoran el rendimiento de inserción en la BD
    • El número de identificación italiano también incluye el género, y eso genera problemas después de una cirugía de reasignación de sexo
      “No metas datos en un identificador permanente” es solo una regla general; según el caso, se puede aceptar cierto trade-off
      Por ejemplo, si usas un hash md5 como UUID para construir un índice, habrá fragmentación, pero en un nivel manejable
    • UUIDv7 no guarda información real; simplemente usa una forma de generación con sesgo temporal (random bias)
      Elegir entre UUID aleatorio y UUID basado en tiempo puede generar diferencias de rendimiento no de milisegundos, sino de segundos
    • En una BD pequeña esto sería optimización prematura, pero a gran escala hace falta el enfoque contrario
      En bases de datos grandes, el sharding y la distribución son imprescindibles, así que UUID puede funcionar mejor que autoincrement
    • Sobre el ejemplo del número de identificación noruego, cuesta creer que realmente pueda haber tanta gente nacida el 1 de enero
      Si el formato es DDMMYYXXXXX, debería cubrir hasta 100 mil personas; me pregunto si de verdad puede concentrarse tanta gente en esa fecha
      Probablemente sería una situación especial, como una llegada masiva de refugiados en un año concreto
  • No se debería usar UUID como si fuera un token de seguridad
    Es peligroso usarlo como mecanismo de seguridad solo porque es difícil de adivinar
    El propósito de un valor aleatorio no es solo evitar la adivinación, sino también ocultar la relación entre IDs consecutivos

  • Según el tipo de BD, la estrategia de PK cambia por completo
    En Postgres, una PK aleatoria es ineficiente, pero en bases distribuidas como Cockroach o Spanner, una clave monótonamente creciente puede causar el problema de hot shards

    • Incluso en una BD distribuida, es mejor una clave con tendencia creciente que una totalmente aleatoria
      UUIDv7 tiene bits altos ordenables y bits bajos aleatorios, así que permite obtener distribución entre nodos y eficiencia de almacenamiento local al mismo tiempo
    • Más que por el tipo de BD, hay que verlo como una diferencia en la estructura de la BD
      En una BD típica no shardeda, las claves aleatorias provocan fragmentación del B-tree
    • En Google Cloud Bigtable se usan claves secuenciales en orden inverso (reverse) para inducir la distribución automática
    • En un Postgres con sharding, una PK aleatoria puede ser ventajosa
      Pero si hay muchas consultas por rango, una clave aleatoria sale perdiendo
      Al final hay que elegir según las características de la carga de trabajo
    • Si la carga está centrada en escritura y tiene un fuerte sesgo temporal, incluso en Postgres una PK aleatoria podría ser mejor
  • El artículo señala bien las desventajas de usar UUIDv4 como PK, pero el método de ofuscación de enteros que propone no parece apto para un servicio real
    En una BD pequeña, UUIDv7 parece un compromiso razonable

    • Yo prefiero UUIDv4 en vez de UUIDv7
      Porque no quiero que se exponga la hora de generación
      Mientras la aleatoriedad de UUIDv4 no cause problemas de rendimiento por el volumen de datos, v4 es la opción más segura
    • En Postgres me gusta usar una sola secuencia
      Hay una pequeña filtración de información, pero a nivel operativo es suficientemente ambigua
    • Si solo quieres ocultar la cantidad de usuarios, puedes aplicar una permutación criptográfica a una clave autoincremental
      Por ejemplo, convertirla con AES-128 y luego codificarla en base64 para que parezca un ID de video de YouTube
  • Veo muchas empresas durante procesos de due diligence técnica, y la posibilidad de hacer sharding rápido es clave para el crecimiento de una empresa
    Si todas las tablas usan UUID, puedes escalar en el momento de shardear sin cambiar la estructura
    Eso da una ventaja de escalabilidad mucho mayor que la pequeña pérdida de espacio y tiempo

    • UUIDv7 también aporta ventajas de rendimiento en Postgres gracias a su característica monótonamente creciente
    • Nosotros también sufrimos en un proceso de sharding por no tener UUID
      Al final, como el modelo de datos era complejo, la migración en sí fue difícil independientemente de si había UUID o no
  • En nuestra app ciframos la PK entera para que parezca un UUID
    Si se expone un ID secuencial, se puede estimar la cantidad de clientes o lanzar un ataque de diccionario
    Con IDs cifrados, si falla el descifrado se puede detectar de inmediato un intento de escaneo

    • Pero si se pierde la clave o hay que rotarla, puede surgir el problema de no poder descifrar
    • Da curiosidad saber cómo manejan las claves: si las inyectan por variable de entorno, si van embebidas en el código, si usan un esquema AEAD como AES-GCM, etc.; la gestión de seguridad es importante
  • Decir “2 mil millones son suficientes” es peligroso
    Todo DBA tiene al menos una historia de pesadilla que empezó con una decisión así

  • El artículo dice que “los valores aleatorios se ordenan de forma ineficiente”, pero en realidad el ordenamiento por bytes sí es posible
    Lo que pasa es que, como las claves aleatorias no se insertan en secuencia, el B-tree se rebalancea con frecuencia y eso degrada el rendimiento

    • UUIDv4 es útil en entornos distribuidos, pero hay que aceptar el costo de un espacio de 128 bits y de la no secuencialidad
    • El autor dijo después que añadió un experimento comparando índices B-tree
      La PK entera hacía que el índice cupiera bien en memoria, mientras que UUIDv4 requería más accesos a páginas y aumentaba la latencia
    • También hubo opiniones de que el sustento técnico era débil
    • En un B-tree, cuanto más creciente es la clave, más eficiente es la inserción; las claves aleatorias tienen peor afinidad con caché
    • Cuanto más ligado esté el acceso a los datos al momento de creación, más favorable será en rendimiento el orden temporal
  • Este artículo parece otro caso de optimización prematura donde la solución apareció antes que el problema
    UUIDv4 está perfectamente bien en la mayoría de los casos
    Los problemas de rendimiento deberían evaluarse cuando realmente aparezcan

    • Pero una vez que empiezas con UUIDv4, luego es casi imposible rekeyear a int64
    • Cuando aparece un problema real de rendimiento, normalmente la empresa ya está en fase de crecimiento y no tiene margen para cambiar la PK
  • En resumen, en Postgres UUIDv7 muestra un rendimiento ligeramente mejor que v4
    En versiones recientes ya se puede usar UUIDv7 sin plugins

    • Aun así, la idea central del artículo es recomendar que, cuando sea posible, se usen PK secuenciales de tipo entero
    • Desde Postgres 18 existe una función integrada uuidv7(), aunque todavía no está claro si las extensiones ofrecen más funcionalidades
    • La mayoría de los usuarios ya no necesitarán una extensión aparte