Pregúntale a la mayoría de los desarrolladores backend qué es un UUID y obtendrás la misma respuesta: “una cadena aleatoria que básicamente es única, así que no tengo que pensar en ello”. Ese es el error común: tratar un UUID como una sola cosa intercambiable en lugar de una familia de distintas estrategias de generación con compensaciones realmente diferentes. No existe un único UUID. Hay uno aleatorio, uno basado en marca de tiempo, dos deterministas basados en hash y —desde la especificación final de RFC 9562— versiones más nuevas ordenables por tiempo que la mayoría de los equipos todavía no usan por defecto, aunque podría decirse que deberían hacerlo.
Esta diferencia importa más de lo que parece. Elige la versión equivocada para una clave primaria y no obtendrás un error: obtendrás una base de datos que silenciosamente se vuelve más lenta para escribir a medida que crece, porque nadie pensó en cómo interactúa un número aleatorio de 128 bits con el funcionamiento real de los índices B-tree. Esta publicación cubre qué está realmente codificado en un UUID, cómo cada versión construye sus bits, la matemática real detrás de si dos UUID pueden colisionar y por qué “simplemente usa uuid4()” es exactamente ese tipo de pensamiento de una sola casilla que causa dolor en producción un año después.
Aprenderás:
- Qué está realmente codificado en los 128 bits de cada UUID, incluidos los nibble de versión y variante que puedes leer de un vistazo
- La diferencia real entre UUID v1, v4, v3/v5 y los nuevos v6/v7/v8 ordenables por tiempo
- La matemática real detrás de la probabilidad de colisión de UUID, y por qué los UUID duplicados en el mundo real casi nunca provienen de esa matemática
- Por qué los UUID aleatorios (v4) perjudican silenciosamente el rendimiento de los índices B-tree cuando se usan como claves primarias
- Por qué UUIDv7 se está convirtiendo en la opción predeterminada para claves primarias y cómo generar uno en Python
- Cómo migrar un esquema existente con claves UUID sin una reescritura arriesgada de tipo big-bang
Tabla de Contenidos
- Conceptos básicos
- La arquitectura completa
- Capas principales explicadas
- Recorrido de extremo a extremo
- Casos especiales
- Desafíos de escalado y producción
- Ejemplos de código
- Errores comunes
- Mejores prácticas para producción
Conceptos básicos
Qué es realmente un UUID
Un UUID (Universally Unique Identifier) es un valor de 128 bits, normalmente escrito como 32 caracteres hexadecimales divididos en cinco grupos con un patrón 8-4-4-4-12:
550e8400-e29b-41d4-a716-446655440000
Dos de esos dígitos hexadecimales no son datos aleatorios ni de marca de tiempo en absoluto: son metadatos que describen el propio UUID. El nibble de versión te dice cómo fue generado (basado en marca de tiempo, aleatorio, basado en hash o esquemas más nuevos ordenados por tiempo), y el campo variante te dice qué estándar de formato se está usando; casi todo lo que encontrarás sigue el estándar definido en RFC 9562, la actualización de 2024 que añadió formalmente las versiones más nuevas sobre el RFC 4122 original. Esa es la parte elegante del diseño: puedes mirar cualquier UUID y conocer su “receta” con solo un par de caracteres, sin necesidad de una tabla de consulta.
Por qué elegir la versión correcta es una decisión de alto valor
- Los UUID aparecen en todas partes: claves primarias, tokens de sesión, IDs de solicitud, claves de objetos S3, claves de idempotencia, así que una suposición errónea para un caso de uso tiende a copiarse y pegarse en todos los demás.
- La versión equivocada como clave primaria tiene un coste de rendimiento real y medible. Los UUID aleatorios insertados en un índice B-tree no solo “funcionan un poco más lento”: pueden aumentar significativamente la latencia de inserción y el tamaño del índice a escala, por razones que no tienen nada que ver con el riesgo de colisión.
- No todos los casos de uso quieren aleatoriedad. Algunos necesitan reproducibilidad (la misma entrada siempre produce el mismo ID), y usar la herramienta equivocada ahí crea registros duplicados en lugar de evitarlos.
La arquitectura completa
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
│ │ │ │ │
time/random version variant time/random/node
(how built) (layout std)
El principio rector: el nibble de versión es la propia etiqueta de instrucciones integrada de un UUID para indicar cómo debe interpretarse cada uno de sus demás bits. Nada sobre la unicidad o la capacidad de ordenación de un UUID es inherente a “ser un UUID”: depende por completo de qué receta específica de versión lo generó.
Capas principales explicadas
1. UUID1 — Marca de tiempo + nodo
Qué es: Combina la marca de tiempo actual con un identificador de nodo (históricamente una dirección MAC) y una secuencia de reloj para protegerse contra retrocesos del reloj.
Por qué importa: La unicidad aquí proviene de la estructura, no de la probabilidad: dos máquinas que generan un UUID1 en el mismo instante siguen obteniendo valores distintos porque el identificador de nodo es diferente.
Consejo para producción: UUID1 incorpora una marca de tiempo real y potencialmente un identificador de hardware real. Trátalo como una fuga leve de privacidad y seguridad si expones estos IDs externamente, y evítalo para cualquier cosa orientada al usuario.
2. UUID4 — Casi completamente aleatorio
Qué es: 122 bits de pura aleatoriedad (los otros 6 son fijos para versión y variante): sin marca de tiempo, sin identificador de máquina, sin nada que se pueda reconstruir.
import uuid
uuid.uuid4()
# UUID('a3b8f9d2-1c4e-4b7a-9f2d-6e8c1a0b3d5f')
Por qué importa: Por eso UUID4 es el valor predeterminado para tokens de API y claves de idempotencia: no revela nada sobre cuándo o dónde fue creado.
Consejo para producción: UUID4 es una excelente elección para tokens. Es una opción mucho más débil como clave primaria de base de datos de lo que la mayoría supone; más abajo veremos exactamente por qué en la sección sobre claves primarias.
3. UUID3 y UUID5 — Deterministas, basados en hash
Qué es: No es aleatorio en absoluto. Introduce un namespace y un nombre, aplícales hash (MD5 para v3, SHA-1 para v5), y la misma combinación de namespace más nombre siempre produce el mismo UUID.
uuid.uuid5(uuid.NAMESPACE_DNS, "example.com")
# deterministic — identical input always yields identical output
Por qué importa: Esto es realmente útil cuando necesitas un ID estable y reproducible; por ejemplo, un identificador consistente derivado de una URL o del ID de registro de un sistema externo, sin necesitar una tabla de consulta para comprobar si ya viste esa entrada antes.
Consejo para producción: Prefiere UUID5 sobre UUID3 en código nuevo. SHA-1 (v5) es un hash más fuerte que MD5 (v3), y rara vez hay una razón para elegir la opción más antigua y débil salvo que estés igualando la salida de un sistema existente.
4. UUID6, UUID7 y UUID8 — UUID ordenables por tiempo
Qué es: Las versiones más nuevas formalizadas en RFC 9562. UUID7 en particular combina una marca de tiempo Unix con bits aleatorios después de ella, produciendo IDs que son únicos y se ordenan de forma natural por tiempo de creación. UUID6 es una variante reordenada de UUID1 diseñada para la misma ordenación amigable para bases de datos; UUID8 es un formato definido por el usuario para esquemas personalizados.
# Python 3.13+
uuid.uuid7()
# UUID('01928a47-3b30-7c5e-9d1a-f0b8c4a7e923')
En versiones más antiguas de Python, el paquete uuid-utils (respaldado por Rust y compatible como reemplazo directo del tipo de la librería estándar) añade soporte para uuid7() junto con una mejora notable de velocidad incluso para uuid4().
Por qué importa: Los IDs ordenados por tiempo se agrupan en un índice B-tree en lugar de dispersarse aleatoriamente en él, lo cual es la razón práctica más importante por la que UUIDv7 está desplazando a v4 como el valor predeterminado de 2026 para cualquier cosa que termine siendo una clave primaria. PostgreSQL 18 incluye generación nativa de uuidv7(), y MySQL 8.4 añadió soporte auxiliar: el ecosistema ya se puso al día con la especificación.
Consejo para producción: Si un UUID alguna vez va a ser una clave primaria o un índice clustered, usa v7 por defecto salvo que tengas una razón específica para no hacerlo. Si es puramente un token externo que nunca toca un índice, v4 sigue siendo perfectamente válido.
5. UUID como clave primaria: por qué los UUID aleatorios perjudican el rendimiento de B-tree
Qué es: El coste de rendimiento de usar UUID4 como clave primaria no tiene que ver con colisiones: tiene que ver con cómo las bases de datos relacionales almacenan físicamente las claves primarias. PostgreSQL, MySQL y SQLite usan todos una estructura B-tree para los índices de clave primaria, y los B-tree están optimizados para valores que llegan en un orden aproximadamente ordenado: las filas nuevas se agregan a la página más a la derecha, que se mantiene “caliente” en memoria y hace que las escrituras sean rápidas y localizadas.
Por qué importa: Un UUID4 aleatorio cae en una página aleatoria de ese índice en cada inserción. A escala, eso significa divisiones de página, fragmentación del índice y cache thrashing: el conjunto de trabajo de páginas “calientes” que la base de datos necesita en memoria crece hasta cubrir todo el índice en lugar de solo su cola, y las inserciones se vuelven mediblemente más lentas y el índice mediblemente más grande que en la tabla equivalente con clave secuencial. Benchmarks públicos sobre tablas en el rango de decenas de millones de filas han mostrado inserciones con UUID4 funcionando aproximadamente tres veces más lento que las claves enteras auto-incrementales, con un índice resultante alrededor de un 40% más grande; y esa diferencia solo se amplía a medida que la tabla sigue creciendo, porque la fragmentación se acumula en lugar de mantenerse constante.
En MySQL en particular, este coste es incluso más marcado de lo que parece, porque la clave primaria de InnoDB es el índice clustered: cualquier otro índice de la tabla almacena una copia del valor de la clave primaria, de modo que un índice de clave primaria inflado y fragmentado no solo ralentiza las búsquedas por clave primaria, sino que también infla cada índice secundario construido encima.
Consejo para producción: Esta es exactamente la brecha que UUID7 cierra: como lleva un prefijo de marca de tiempo, se comporta como una clave secuencial para fines de indexación mientras sigue generándose con coordinación cero entre servicios. Cambiar una tabla existente es una migración de esquema, no una bandera de configuración, así que planifícalo como un proyecto deliberado en lugar de una corrección de una sola línea.
6. Alternativas que vale la pena conocer: TSID y ULID
Qué es: Formatos de ID ordenables por tiempo que ni siquiera son UUID: TSID suele ser un valor compacto de 64 bits, y ULID es un formato de 128 bits compatible con UUID con un diseño similar de marca de tiempo más aleatoriedad al de UUID7.
Por qué importa: Si quieres la menor huella de índice posible y no necesitas el formato específico de 128 bits de UUID, vale la pena evaluar TSID como alternativa propia de 2026. Si quieres seguir siendo compatible con UUID y aun así ganar localidad de índice, tanto UUID7 como ULID encajan en esa necesidad.
Recorrido de extremo a extremo
Sigue el registro de un nuevo usuario a través de la generación del ID, el almacenamiento y lo que realmente sucede en el momento de la inserción:
- Solicitud recibida. Un nuevo usuario se registra; la aplicación necesita generar una clave primaria para la nueva fila antes o durante la inserción.
- ID generado. Con una estrategia UUID4,
uuid.uuid4()produce un valor aleatorio completo de 128 bits. Con una estrategia UUID7,uuid.uuid7()produce un valor con un componente inicial de marca de tiempo. - Fila insertada. El índice B-tree de la base de datos para la clave primaria recibe el nuevo valor. Un UUID4 cae en una página efectivamente aleatoria del árbol; un UUID7 cae en la página más a la derecha, ya caliente, o muy cerca de ella, junto a todas las demás filas insertadas recientemente.
- Mantenimiento del índice. Con UUID4, esta inserción puede provocar una división de página si la página aleatoria de destino está llena. Con UUID7, el patrón de anexado predominante evita eso con mucha más frecuencia.
- Ruta de lectura. Una búsqueda por clave primaria funciona de forma idéntica en ambos casos: toda esta diferencia solo aparece en la ruta de escritura y en el tamaño general del índice, no en el rendimiento de lectura de una sola fila.
- A escala. Multiplica esto por millones de filas, y el índice de la tabla con UUID4 habrá crecido más y estará más fragmentado de lo que habría estado la tabla con UUID7, puramente por el patrón de inserción; nada más del esquema cambió.
Casos especiales
IDs de recursos idempotentes y reproducibles. Cuando necesitas que la misma entrada externa siempre se asigne al mismo ID interno —por ejemplo, al deduplicar registros traídos de un sistema externo— UUID5 es la herramienta correcta precisamente porque no es aleatorio. Reprocesar la misma entrada nunca crea un registro duplicado.
IDs orientados al exterior vs. claves foráneas internas. Un patrón híbrido común: usar una clave interna rápida y secuencial (un entero o un TSID) para relaciones de clave foránea y joins con mucho uso de índices, y exponer una columna UUID separada como identificador orientado al exterior. Esto evita filtrar IDs secuenciales (un riesgo de IDOR: adivinar que /orders/1002 existe porque /orders/1001 existe) sin pagar internamente el coste completo de B-tree en todas partes.
Sistemas offline-first y con múltiples escritores. Esta es la razón original de la existencia de UUID: cualquier nodo, servicio o cliente offline puede generar un ID globalmente válido con coordinación cero y sin ida y vuelta a una secuencia central. Esa propiedad no desaparece con v7: obtienes generación sin coordinación y mejor comportamiento del índice al mismo tiempo.
Desafíos de escalado y producción
La hinchazón del índice se acumula a medida que las tablas crecen. La diferencia entre una tabla con claves UUID aleatorias y una secuencial u ordenada por tiempo se amplía con el tamaño de la tabla, no solo con el número de filas: la fragmentación empeora, no mejora, cuanto más tiempo haya estado una tabla recibiendo inserciones en orden aleatorio.
Migrar un esquema UUID4 existente sin tiempo de inactividad. Añade una nueva columna UUID7, rellénala para las filas existentes, escribe en ambas columnas durante una ventana de transición y luego cambia lecturas y escrituras una vez que se verifique que el relleno se completó correctamente; un cambio en vivo del formato de clave primaria no es algo que deba intentarse como un único paso de migración.
Generación de IDs entre servicios sin coordinación. En un sistema distribuido con múltiples servicios escribiendo de forma independiente, cualquier versión de UUID sigue resolviendo el problema de “sin autoridad central” que motivó a UUID en primer lugar; la elección de versión solo cambia el rendimiento del índice y la capacidad de ordenación, no la garantía de generación sin coordinación.
De dónde vienen realmente los UUID duplicados. La matemática teórica de colisión (abajo) prácticamente nunca es la causa de un duplicado real en producción. Los incidentes reales se remontan a generadores de números aleatorios débiles o predecibles, procesos que hacen fork y comparten inadvertidamente una semilla, concurrencia defectuosa donde el estado compartido se reutiliza incorrectamente entre workers o simples errores de implementación, no a que el espacio subyacente de 128 bits sea demasiado pequeño.
Ejemplos de código
import uuid
uuid.uuid4() # random — 122 bits of entropy
uuid.uuid1() # timestamp + node
uuid.uuid7() # Python 3.13+: timestamp-prefixed, sortable
uuid.uuid5(uuid.NAMESPACE_DNS, "example.com") # deterministic, SHA-1
Para versiones que aún no están en la stdlib de tu Python, o para una mejora significativa de velocidad incluso con uuid4():
# pip install uuid-utils
import uuid_utils as uuid
uuid.uuid7() # drop-in compatible with stdlib UUID objects
Un esquema mínimo de PostgreSQL que refleja el patrón v7-como-clave-primaria:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuidv7(),
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
Errores comunes
Error: tratar “UUID” como una sola cosa intercambiable. Usar el helper uuid() que esté más a mano sin considerar qué versión encaja con el caso de uso. Solución: saber si necesitas aleatoriedad (v4), reproducibilidad (v5) u ordenación (v7) antes de generar nada.
Error: usar UUID4 por defecto para cada clave primaria. Esta es la fuente más común de incidentes del tipo “¿por qué nuestras escrituras se volvieron más lentas a medida que la tabla crecía?”. Solución: usa UUID7 (o un diseño híbrido de clave interna/externa) para cualquier cosa que se convierta en un índice clustered o de clave primaria.
Error: preocuparse por las probabilidades de colisión de la matemática del cumpleaños en lugar de por las causas reales. La probabilidad de colisión de UUID4 es astronómicamente baja: necesitarías del orden de 2.7 quintillones de valores generados antes de tener siquiera un 50% de probabilidad de una colisión. Solución: si ves un duplicado en producción, audita tu generador de números aleatorios y tu modelo de proceso/concurrencia; ahí es de donde realmente vienen los duplicados, no de la matemática.
Error: usar UUID3 en código nuevo. MD5 es un hash más débil que SHA-1 sin ninguna ventaja para sistemas nuevos. Solución: usa UUID5 por defecto salvo que estés igualando específicamente un sistema existente basado en UUID3.
Error: almacenar UUID como texto de 36 caracteres en todas partes. Esto desperdicia almacenamiento y espacio de índice en comparación con la representación binaria nativa que admiten la mayoría de las bases de datos. Solución: usa el tipo UUID nativo de tu base de datos (almacenado como 16 bytes) en lugar de una columna de cadena simple, cuando esté disponible.
Mejores prácticas para producción
- Haz coincidir la versión con el caso de uso, no con la costumbre. Aleatorio para tokens, determinista para IDs reproducibles, ordenado por tiempo para cualquier cosa que se convierta en un índice.
- Usa UUID7 por defecto para nuevas claves primarias, no UUID4. El beneficio de localidad del índice es casi gratuito y se acumula a tu favor a medida que las tablas crecen.
- Separa las claves internas de los identificadores externos cuando importe. Una clave interna rápida más un identificador externo basado en UUID evita tanto el riesgo IDOR de los IDs secuenciales como el coste completo de indexación de UUID aleatorios en todas partes.
- Almacena los UUID en su tipo binario nativo. Dieciséis bytes superan a treinta y seis caracteres a cualquier escala real.
- Si ves un duplicado, revisa tu generador, no la matemática. Las colisiones del mundo real provienen de aleatoriedad débil o concurrencia defectuosa casi siempre; trátalo como un error de implementación que debes encontrar, no como prueba de que los UUID “no son realmente únicos”.
Conclusión
Un UUID no es una sola cosa sobre la que puedas razonar de forma genérica: es una familia de distintas estrategias de generación de bits, cada una con un trabajo específico, y elegir la equivocada para una clave primaria es exactamente el tipo de decisión que parece gratuita hasta que tu tabla tiene diez millones de filas. La matemática alrededor de las colisiones es una preocupación resuelta e insignificante; la decisión real que importa es la versión, no la unicidad. Usa v4 por defecto para tokens, v5 para IDs reproducibles y v7 para cualquier cosa que se convierta en una clave primaria, y la mayoría de las preguntas de “¿por qué nuestra base de datos está más lenta que antes?” ni siquiera llegarán a surgir.
¿Sigues usando uuid4() por defecto para claves primarias o ya te pasaste a algo ordenado por tiempo?
