ToolsOps

UUID v4 y v7: cuándo usar cada uno y cómo validar

Qué es un UUID, en qué se diferencian v4 (aleatorio) y v7 (ordenable por tiempo), cuándo usar cada uno como clave primaria, cómo validar y normalizar UUIDs y errores comunes al manejar identificadores.

Qué es un UUID

Un UUID (Universally Unique Identifier) es un identificador de 128 bits diseñado para ser único sin coordinación entre máquinas. Se representa habitualmente como 32 caracteres hexadecimales en grupos 8-4-4-4-12 separados por guiones (por ejemplo, 550e8400-e29b-41d4-a716-446655440000). El estándar vigente es RFC 9562, publicado en 2024 como sucesor de RFC 4122; mantiene compatibilidad con todos los UUIDs ya en circulación y añade las nuevas versiones v6, v7 y v8.

De los ocho tipos definidos, dos cubren prácticamente todo el uso real moderno: v4 (aleatorio) y v7 (timestamp + aleatorio, ordenable por tiempo). El resto tiene casos de uso específicos (v3/v5 derivan un UUID determinista a partir de un namespace y un nombre, v1 incrusta el timestamp y el node field del emisor, v6 es una variante ordenable anterior a v7 que quedó obsoleta, v8 es completamente custom).

UUID v4: aleatorio y ampliamente soportado

v4 es la opción universal por defecto. 122 bits de aleatoriedad criptográfica (los otros 6 codifican versión y variante), formato canonical reconocible y soporte nativo en todas las plataformas (Postgres tiene gen_random_uuid(), Java tiene UUID.randomUUID(), Node tiene crypto.randomUUID()). Si solo necesitas un identificador único y no importa el orden, v4 es la opción con menos fricción.

La cantidad de entropía es astronómicamente alta. El espacio de valores posibles es de unos 5.3 × 10^36. Para que la probabilidad de colisión llegue al 0.0001%, tendrías que generar miles de millones de UUIDs por segundo durante décadas. En la práctica, siempre que el generador use una fuente criptográfica (Web Crypto, /dev/urandom, BCryptGenRandom), las colisiones no son un riesgo real.

UUID v7: ordenable por tiempo

v7 reserva los 48 bits más significativos para un timestamp Unix en milisegundos big endian. El resultado es un UUID lexicográficamente ordenable: si generas dos UUIDs v7 con segundos de diferencia, el más reciente es siempre mayor en orden alfabético. El resto del espacio (los 74 bits que no son timestamp, version ni variant) es aleatorio, así que dos UUIDs generados en el mismo milisegundo siguen siendo distintos.

La motivación principal son las claves primarias de bases de datos. Postgres, MySQL, SQLite y otros motores relacionales usan índices B-tree para sus PKs. Con v4, cada inserción cae en una posición aleatoria del índice, lo que provoca splits frecuentes de páginas y fragmentación. Con v7, las inserciones son cuasi- secuenciales: las nuevas filas casi siempre van al final del índice, lo que mejora la localidad de cache, reduce los splits y baja el coste de I/O en tablas grandes. Es el patrón que ULID popularizó y que el estándar UUID adoptó en RFC 9562.

UUID v4 vs UUID v7: comparativa

Aspectov4v7
Estructura128 bits aleatorios (122 efectivos)48 bits timestamp ms + 74 bits aleatorios
Ordenable por tiempoNoSí (lexicográficamente)
Localidad en índices B-treeMala (inserción aleatoria)Buena (inserción cuasi-secuencial)
Revela tiempo de creaciónNoSí (precisión ms)
Soporte nativo en libs/DBsUniversalCreciente desde 2024
RFCRFC 4122 / RFC 9562 §5.4RFC 9562 §5.7

Cuándo usar UUID en bases de datos

La elección entre integer autoincremental y UUID como clave primaria depende de tres ejes:

  • Sistemas distribuidos: si necesitas generar IDs en varios servidores o en cliente sin coordinación, UUID es la opción. Un integer autoincremental requiere ronda al servidor o secuencias coordinadas.
  • Exposición pública del ID: un integer autoincremental filtra información de orden y volumen (un cliente con ID 1234 sabe que probablemente hay 1234 clientes). Un UUID no.
  • Coste de almacenamiento e índice: un UUID ocupa 16 bytes (Postgres lo guarda binario, no como string), frente a 4-8 bytes de un integer. En tablas con miles de millones de filas, la diferencia importa. Con v4 además fragmenta el índice; con v7 menos.

Recomendación pragmática: usar v7 como PK cuando el sistema necesita IDs distribuibles y no quieres pagar la fragmentación de v4. Mantener integer autoincremental cuando ya funciona y no tienes el problema de generación distribuida.

Validar y normalizar UUIDs

La forma canonical RFC 9562 es lowercase con guiones en el formato 8-4-4-4-12. Cualquier sistema serio debería normalizar a esa forma al recibir un UUID externo: al comparar UUIDs en una tabla, en una cookie o en una URL, un mismatch de casing puede dar falsos negativos. La herramienta UUID generator de ToolsOps tiene un modo Validar que acepta input con guiones, sin guiones, en mayúsculas o minúsculas, y devuelve la versión, la variante y el canonical normalizado.

La versión se lee del nibble alto del 7º byte (carácter 14 con guiones); la variante se lee de los bits altos del 9º byte (carácter 19). El subconjunto realmente usado en producción es variant RFC 4122 (bits 10xx), que cubre todas las versiones que un sistema moderno emite o consume.

Errores comunes

  • Usar v4 como clave primaria de una tabla grande y descubrir tarde la fragmentación del índice. Si llegas tarde, valora tablas particionadas por tiempo antes que regenerar IDs.
  • Tratar el formato canonical como secret cuando lo es solo estructural. v4 tiene entropía suficiente pero su forma es reconocible; para secrets prefiere crypto.getRandomValues(new Uint8Array(32)) en base64url.
  • Generar UUIDs con Math.random(). Sin fuente criptográfica las colisiones reales son posibles y los IDs son predecibles. Usa siempre Web Crypto o el equivalente del runtime.
  • Aceptar un v1 como input sin saber qué hace con el node field. Si tu sistema procesa UUIDs externos, mejor restringir a v4 y v7 a propósito.
  • Suponer que un v7 tiene precisión sub-milisegundo. Solo los 48 bits altos son timestamp; dos UUIDs generados en el mismo ms difieren en los bits aleatorios y su orden relativo es arbitrario.
  • Persistir UUIDs como string en una tabla grande sin convertir a binario. Postgres y MySQL guardan UUID binario nativo (16 bytes) que ocupa la mitad que el VARCHAR(36) y es más rápido de indexar.

Cómo usar el generador de ToolsOps

El generador y decodificador de UUID de ToolsOps tiene tres modos. En Generar eliges v4 o v7 y una cantidad entre 1 y 100, con opciones de mayúsculas y de quitar los guiones para formatos no canonical. En Validar pegas un UUID y la herramienta detecta versión y variante. En Decodificar pegas un v7 y obtienes el timestamp embebido como ISO UTC, hora local y edad relativa.

La generación corre 100% en el navegador con Web Crypto API. Ni los UUIDs producidos ni el input que pegas en Validar o Decodificar se envían a un servidor. Para validar firmas u otro contenido autenticado, la herramienta hermana es el decodificador y verificador de JWT. Para verificar la integridad de archivos descargados, la calculadora de hash y checksum.

Preguntas frecuentes

¿Por qué dicen que v7 es mejor que v4 como clave primaria?
Las bases de datos relacionales como Postgres y MySQL usan índices B-tree para sus claves primarias. Un v4 inserta filas en posiciones aleatorias dentro del índice, lo que provoca fragmentación, splits frecuentes de páginas y peor localidad de cache. Un v7 inserta cuasi-secuencialmente (los primeros 48 bits son timestamp), así que las nuevas filas casi siempre van al final del índice. En tablas grandes esto se traduce en menos I/O y menos fragmentación.
¿Un UUID v4 puede repetirse?
Teóricamente sí. Prácticamente no. 122 bits efectivos dan un espacio de unos 5.3 × 10^36 valores posibles. Para que la probabilidad de colisión llegue al 0.0001%, tendrías que generar miles de millones de UUIDs por segundo durante décadas. Si tu generador usa una fuente criptográfica (Web Crypto, /dev/urandom, BCryptGenRandom), las colisiones no son un riesgo real.
¿Es seguro usar UUID v4 como token de sesión?
Tiene suficiente entropía (122 bits), pero el formato canonical (8-4-4-4-12 con guiones, minúsculas) es muy reconocible. Si aparece en un log o en una URL, queda claro qué es. Para secrets de sesión es preferible un valor opaco generado con `crypto.getRandomValues(new Uint8Array(32))` codificado en base64url (256 bits, sin forma reconocible). Para identificadores de fila o de registro, un v4 está bien.
¿Por qué la herramienta no genera v1?
v1 codifica el timestamp del momento de generación y el node field, que originalmente era la MAC del emisor. Aunque RFC 9562 §5.1 ahora permite aleatorizar el node field, el dato sigue siendo ambiguo: si te llega un v1 de fuera, no sabes si revela la MAC del servidor que lo generó o no. v7 cubre el caso de uso real (UUID ordenable por tiempo) sin esa ambigüedad, así que prefiero no facilitar la adopción de v1.
¿Qué pasa si recibo un UUID en mayúsculas o sin guiones?
El estándar RFC 9562 canoniza UUID a lowercase con guiones, pero acepta mayúsculas como input. Lo razonable es normalizar a canonical lowercase al recibir un UUID externo: al comparar con otros UUIDs (en una tabla, en una cookie), un mismatch de casing puede dar falsos negativos. La herramienta normaliza siempre al validar.
¿Cómo se compara UUID v7 con ULID?
ULID es un identificador similar al v7 con un layout distinto (48 bits timestamp en ms + 80 bits aleatorios) y un encoding Crockford Base32 en lugar de hex. El concepto es prácticamente idéntico: ordenable por tiempo, único, sin datos personales. v7 tiene la ventaja de ser un UUID estándar (todas las bases de datos lo soportan como uuid nativo); ULID requiere almacenamiento como string o adaptador. Si arrancas un proyecto nuevo y quieres tiempo-ordenable, v7 es la opción con menos fricción.
¿Tengo que migrar mis UUID v4 existentes a v7?
No. Mezclar v4 y v7 en la misma columna funciona; lo único que cambia es que las inserciones futuras serán más localizadas. Si tu tabla es pequeña o la fragmentación no es un cuello de botella, el coste de migrar (regenerar IDs, actualizar foreign keys) no compensa. Si estás empezando un proyecto o reescribiendo el modelo de datos, v7 es el default razonable.
¿La librería que uso ya emite v7?
Depende de la versión. Algunas referencias: `uuid` en npm añadió v7 en la versión 10. Python 3.11 todavía no lo trae nativo (necesitas `uuid7` desde pip). PostgreSQL espera `gen_random_uuid()` para v4; v7 requiere extensión o función custom. Java tiene `UUID.randomUUID()` (v4); para v7 necesitas una librería externa. Antes de fijar v7 como contrato, verifica que tu stack lo emite y lo lee correctamente.