UUID vs ULID vs Nano ID vs IDs autoincrementales
Cinco esquemas de ID habituales comparados en lo que importa en la práctica: tamaño, orden, probabilidad de colisión, comportamiento en la base de datos y lo que delata cada ID.
Elegir un formato de ID se reduce a tres preguntas. ¿Los IDs deben ordenarse por fecha de creación? ¿Aparecerán en URL o en API públicas? ¿Y la base de datos los guardará en una columna nativa de 128 bits o como texto? UUID v7, estandarizado en el RFC 9562 en mayo de 2024, cambió la respuesta para muchos proyectos: ofrece el orden temporal que hizo popular a ULID, en un formato que ya aceptan todas las columnas y bibliotecas de UUID.
De un vistazo
| Autoincremental | UUID v4 | UUID v7 | ULID | Nano ID | |
|---|---|---|---|---|---|
| Ejemplo | 1042 | f47ac10b-58cc-4372-a567-0e02b2c3d479 | 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 | 01J9QKMZ1BFN79M7SVBJ6JWVRG | V1StGXR8_Z5jdHi6B-myT |
| Tamaño binario | 4 u 8 bytes | 16 bytes | 16 bytes | 16 bytes | Se guarda como texto |
| Longitud en texto | Hasta 10 o 19 dígitos | 36 | 36 | 26 | 21 por defecto |
| Bits aleatorios | Ninguno | 122 | Hasta 74 | 80 | 126 por defecto |
| Se ordena por fecha de creación | Sí | No | Sí, al milisegundo | Sí, al milisegundo | No |
| Se genera sin la base de datos | No | Sí | Sí | Sí | Sí |
| Especificación | Función de la base de datos | RFC 9562 | RFC 9562 | Especificación comunitaria | Biblioteca |
| Revela | Cantidad y orden | Nada | Fecha de creación | Fecha de creación | Nada |
Cómo se construye cada uno
- Autoincremental es un contador que mantiene la base de datos. Un
INTde 32 bits con signo termina en 2 147 483 647, y unBIGINTcon signo, en 9 223 372 036 854 775 807. La aplicación solo conoce el ID después de la inserción, a menos que reserve antes valores de una secuencia. - UUID v4 son 122 bits aleatorios más 6 bits fijos que marcan la versión y la variante.
- UUID v7 empieza con una marca de tiempo Unix de 48 bits en milisegundos, seguida de la versión, 12 bits llamados
rand_a, la variante y 62 bits llamadosrand_b. El RFC 9562 permite que un generador use parte de esos bits como contador, para que los IDs creados en el mismo milisegundo sigan saliendo en orden. El generador de este sitio lo hace con los 12 bits derand_a. - ULID es una marca de tiempo de 48 bits en milisegundos más 80 bits aleatorios, escrita como 26 caracteres en el Base32 de Crockford: 10 para el tiempo y 16 para la parte aleatoria. El alfabeto excluye I, L, O y U, y no distingue mayúsculas de minúsculas. En modo monótono, un ULID creado en el mismo milisegundo que el anterior toma la parte aleatoria de ese ULID y le suma uno.
- Nano ID es una cadena de caracteres aleatorios, por defecto 21 tomados del alfabeto de 64 caracteres
A-Z a-z 0-9 _ -. Cada carácter aporta 6 bits, lo que da 126 bits. Tanto la longitud como el alfabeto se pueden cambiar, y el número de bits aleatorios cambia con ellos.
Un ULID y un UUID son números de 128 bits escritos de forma distinta. 01J9QKMZ1BFN79M7SVBJ6JWVRG y 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 son el mismo valor, en Base32 y en hexadecimal. Por eso un ULID puede guardarse en una columna UUID nativa, como el tipo uuid de PostgreSQL, y conserva allí su orden. Eso sí, un ULID que no nació como UUID v7 tiene bits arbitrarios donde deberían ir la versión y la variante, así que un validador de UUID estricto puede rechazarlo.
Probabilidad de colisión, calculada
Para los IDs puramente aleatorios se aplica la aproximación del cumpleaños: con n IDs tomados de N = 2 elevado al número de bits aleatorios, la probabilidad de que haya al menos un duplicado es de aproximadamente 1 − e^(−n²/2N), y llega al 50% cerca de n = 1,18 × √N.
| Esquema | Bits aleatorios | IDs para un 50% de probabilidad de duplicado | Probabilidad de duplicado entre 1000 millones de IDs |
|---|---|---|---|
| UUID v4 | 122 | 2,7 × 10^18 | 9,4 × 10^-20 |
| Nano ID, 21 caracteres | 126 | 1,1 × 10^19 | 5,9 × 10^-21 |
| Nano ID, 10 caracteres | 60 | 1,3 × 10^9 | Alrededor del 35% |
La última fila es la que conviene recordar: acortar un Nano ID para tener URL más bonitas reduce muy deprisa la resistencia a las colisiones, así que haga estas cuentas antes de elegir una longitud.
En UUID v7 y ULID, dos IDs solo pueden colisionar si llevan el mismo milisegundo, así que n es el número de IDs creados en un milisegundo por generadores que no comparten contador. Con 1000 de esos IDs en el mismo milisegundo, la probabilidad de duplicado es de unos 2,6 × 10^-17 con 74 bits aleatorios y de 4,1 × 10^-19 con los 80 de ULID. En la práctica, el riesgo de todos estos esquemas es una fuente aleatoria débil, como Math.random, y no la aritmética.
Comportamiento en los índices de la base de datos
Las claves primarias suelen vivir en un índice B-tree. Cuando cada clave nueva es mayor que la anterior, como ocurre con los autoincrementales, UUID v7 y ULID, las inserciones caen en el borde derecho del índice, las páginas se llenan por completo y las filas escritas recientemente quedan juntas. Las claves aleatorias, UUID v4 y Nano ID, caen en cualquier parte: las páginas se dividen, quedan medio vacías y, cuando el índice ya no cabe en memoria, cada inserción puede suponer una lectura aleatoria.
El efecto es mayor en MySQL y MariaDB con InnoDB, que guardan las propias filas en el orden de la clave primaria, de modo que las claves aleatorias dispersan la tabla y no solo el índice. PostgreSQL guarda las filas en un heap aparte, así que allí la diferencia se limita al índice.
El formato de almacenamiento importa tanto como el esquema:
- Guarde los UUID y los ULID en un tipo nativo de 16 bytes cuando exista:
uuiden PostgreSQL,UUIDen MariaDB 10.7 y posteriores,BINARY(16)en MySQL.CHAR(36)ocupa más del doble y agranda todos los índices. - En MySQL, no pase el indicador de intercambio a
UUID_TO_BIN(uuid, 1)con valores v7. Reordena los campos de tiempo pensando en los UUID v1 y rompería el orden de los v7. - El tipo
uniqueidentifierde SQL Server compara primero los seis últimos bytes, así que los valores v7 guardados en él no se ordenan por tiempo. Encontrará los detalles en UUID vs GUID. - Los Nano ID son cadenas y se guardan como
CHARoVARCHAR, con una intercalación (collation) binaria si hay que distinguir mayúsculas de minúsculas.
URL y lo que revela un ID
Los cinco formatos son seguros en una URL sin codificación porcentual: los dígitos hexadecimales, los guiones, el Base32 de Crockford y el alfabeto de Nano ID son caracteres no reservados según el RFC 3986. La diferencia está en la longitud (36, 26 o 21 caracteres) y en las mayúsculas. Los UUID y los ULID no distinguen mayúsculas de minúsculas, así que sobreviven a los sistemas que las cambian; los Nano ID, no.
Lo que revela un ID suele ser el factor decisivo:
- Autoincremental revela volumen. Si su factura es la 1042 hoy y la 1342 una semana después, la empresa emitió unas 300 facturas esa semana. Los IDs secuenciales también hacen trivial probar registros vecinos, lo que convierte cualquier comprobación de acceso olvidada en una fuga de datos.
- UUID v7 y ULID revelan cuándo se creó el registro, al milisegundo. En pedidos rara vez es un problema; en cuentas de usuario o historiales médicos puede serlo. Pegue un UUID v7 en el inspector del generador de UUID v7 y verá la marca de tiempo.
- UUID v4 y Nano ID no revelan nada sobre cuándo ni dónde se crearon.
Ninguno de estos IDs es un secreto. Un ID imposible de adivinar dificulta el sondeo, pero no sustituye a la comprobación de si el usuario tiene permiso para ver el registro.
Cuál elegir
- Tablas internas en una sola base de datos que los usuarios nunca ven:
BIGINTautoincremental. Es la clave más pequeña y sencilla. - Claves primarias creadas por la aplicación o entre servicios, cuando hay una columna UUID nativa disponible: UUID v7.
- Identificadores públicos cuya fecha de creación debe permanecer privada, IDs de correlación y nombres de archivo: UUID v4, del generador de UUID v4 o de
crypto.randomUUID(). - IDs cortos en URL donde el orden no importa: Nano ID con su longitud por defecto o una cercana.
- ULID cuando su stack ya lo usa o prefiere la forma de texto más corta e insensible a mayúsculas. En proyectos nuevos, UUID v7 cubre lo mismo con un formato estándar.
Una combinación habitual reúne lo mejor de ambos mundos: una clave primaria BIGINT o UUID v7 para joins e índices, más un ID público aleatorio aparte, con índice único, para las URL y las API.