Fundamentos
UUID v4 vs v7 — Cuándo importan los IDs ordenados por tiempo
UUID v7 añade un prefijo de timestamp en milisegundos a bytes aleatorios. Cuándo importa para Postgres, MySQL y sistemas distribuidos, y cuándo v4 sigue sirviendo.
UUID v7 aterrizó en la RFC 9562 (mayo 2024) y la mayoría de librerías estándar ya lo traen. Es la primera alternativa real a v4 para primary keys desde que v4 se convirtió en default allá por principios de los 2010. Esta guía es el “cuándo cuál”.
Modelo mental rápido
Un UUID v4 son 122 bits de aleatoriedad envueltos en un string de 36 caracteres. Un UUID v7 son 48 bits de timestamp en milisegundos seguidos de 74 bits de aleatoriedad, también 36 caracteres. Ambos son globalmente únicos con probabilidad astronómicamente alta.
La única diferencia que importa para diseño de sistemas: una lista ordenada de v7s está en orden temporal. Una lista ordenada de v4s no está en ningún orden.
v4: f47ac10b-58cc-4372-a567-0e02b2c3d479 (todo aleatorio)
v7: 018f4bea-7e83-7abc-9d0e-0a1b2c3d4e5f
└──timestamp──┘v└─rand─┘v└──random──┘
Ambos usan el mismo formato hex 8-4-4-4-12 y ambos caben en una columna binaria de 16 bytes.
Dónde v4 va bien
Si tu primary key está en una tabla hash-particionada, en un key-value store en memoria, o en un document store NoSQL que indexa por hash de todas formas, no necesitas v7. La distribución aleatoria es feature ahí.
Usa v4 cuando:
- Tu BD es MongoDB, DynamoDB, Cassandra, o cualquier store hash-particionado.
- Nunca necesitas range-scan, paginación por rango, o bulk-insert en un índice B-tree por ID.
- Quieres deliberadamente IDs impredecibles en URLs para resistir enumeración. Esto es solo débilmente cierto — tanto v4 como v7 tienen suficiente entropía aleatoria para frenar fuerza bruta. La diferencia es que v7 filtra la hora de creación; si eso te preocupa, v4 es más seguro.
La mayoría de runtimes ya tienen generador nativo. crypto.randomUUID() lleva en Node desde 14.17 (2021) y en todos los navegadores evergreen desde principios de 2022. uuid.uuid4() de Python es librería estándar. Postgres trae gen_random_uuid() de serie desde v13.
Dónde v7 es mejor default
El problema del índice B-tree es el que hay que interiorizar.
Postgres, MySQL/InnoDB, SQLite y casi toda BD OLTP almacena filas clustered o indexed por primary key en un B-tree. Insertar en un B-tree es barato cuando la nueva clave es mayor que la última (extremo derecho del árbol) y caro cuando es aleatoria (cada insert ensucia una página de hoja distinta, la localidad de caché colapsa, los page splits cascadean).
En la práctica, las primary keys v4 cuestan 2-5× menos throughput de inserción que enteros autoincrement o UUIDs v7 en una tabla cargada. El efecto no se ve en un benchmark de portátil con unos miles de filas — aparece bajo presión de escritura con un índice que ya no cabe en RAM.
| Carga | v4 | v7 | auto-incremento int |
|---|---|---|---|
| Insert en PK indexada | Lento (aleatorio) | Rápido (monotónico) | Más rápido |
| Range scan por fecha de creación | Necesita índice created_at | Funciona en la PK | Funciona en la PK |
| Clustering en disco | Aleatorio | Temporalmente ordenado | Temporalmente ordenado |
| Filtra hora de creación | No | Sí | Sí (más o menos) |
| Globalmente único entre nodos | Sí | Sí | No (requiere coordinación) |
Usa v7 cuando:
- El ID es primary key en una BD B-tree y la tabla pasará de unos pocos millones de filas.
- Quieres ordenar por creación sin una columna
created_atseparada, o queries aproximadas por rango temporal sobre la PK. - Construyes logs de eventos distribuidos donde el orden temporal aproximado en ingesta ayuda a los consumidores.
Generar v7 en código
La RFC es directa: 48 bits iniciales timestamp Unix en ms, 4 bits de versión (0x7), 12 bits de aleatoriedad para orden intra-ms, 2 bits de variant, y 62 bits de cola aleatoria.
# Python 3.13+: uuid.uuid7() es librería estándar
import uuid
uuid.uuid7()
# UUID('018f4bea-7e83-7abc-9d0e-0a1b2c3d4e5f')
# Python antiguo o si quieres control explícito:
import os, time
def uuid7() -> str:
ts = int(time.time() * 1000).to_bytes(6, "big")
rand = os.urandom(10)
b = bytearray(ts + rand)
b[6] = (b[6] & 0x0F) | 0x70 # version 7
b[8] = (b[8] & 0x3F) | 0x80 # variant
h = b.hex()
return f"{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:]}"
En Postgres puedes instalar la extensión pg_uuidv7 o generarlo del lado aplicación. Node aún no tiene crypto.randomUUID({ version: 7 }) nativo — usa el paquete npm uuid (v10+) que incluye v7().
Notas de migración e interop
Tres puntos prácticos si mueves un sistema existente:
- No mezcles v4 y v7 en la misma columna PK a mitad de tabla. Te comes lo peor de ambos — la cola aleatoria histórica sigue degradando el lado izquierdo del B-tree, y la nueva cola ordenada solo ayuda en las páginas nuevas. O cambias de golpe o aceptas que el beneficio solo aplica a tablas nuevas.
- La filtración temporal es real. Un v7 revela el milisegundo de creación a cualquiera que tenga el ID. Para números de factura, IDs de pedido o user IDs, normalmente vale. Para cualquier cosa sensible de seguridad (tokens de reseteo de password, share links), usa un token aleatorio separado — nunca un UUID a secas de cualquier versión.
- El skew de reloj importa menos de lo que se teme. Un reloj que salta 10 segundos hacia atrás produce 10 segundos de v7s fuera de orden y luego se recupera. La garantía de unicidad se mantiene gracias a la cola aleatoria.
Cuándo no usar ni uno ni otro
Los ULIDs (Crockford base-32, 26 caracteres) preceden a v7 y resuelven el mismo problema. Si ya tienes infra ULID, mantenla; la ganancia en disco es la misma. Para IDs monotónicos internos dentro de una sola BD, un bigint con secuencia sigue siendo la primary key más barata por mucho margen — úsalo salvo que necesites específicamente unicidad global.
Para formatos de token construidos sobre Base64 en lugar de hex, la guía de Base64 en producción cubre por qué un UUID v4 reencodeado como base64url es más corto pero normalmente no compensa el coste cognitivo.
Conclusiones
Por defecto v7 para primary keys de BD en proyectos nuevos. Mantén v4 cuando la BD no cuida el orden de inserción (stores hash-particionados) o cuando debes ocultar la hora de creación. Ambos son 128 bits de “básicamente único”; la diferencia es si tu B-tree sufre. Para patrones de tokens de auth y por qué los UUIDs por sí solos no son secretos, mira la pieza JWT en 2026.