¿Qué es un ULID? El identificador único y ordenable, explicado
Cada UUIDv4 aleatorio que insertas como clave primaria cae en un punto impredecible del índice de la base de datos. Hazlo unos cuantos millones de veces y el índice se fragmenta, la caché se satura y las escrituras se ralentizan. Un ULID resuelve eso sin renunciar a lo que te gustaba de los UUID: aún puedes generar uno en cualquier lado, sin coordinador central, pero cae en orden temporal en vez de dispersarse.
Entonces, ¿cómo hace una cadena de 26 caracteres para ordenarse por tiempo? Ahí está todo el truco, y conviene entenderlo antes de echar mano de uno.
Un ULID (identificador único universalmente ordenable de forma lexicográfica) es un identificador de 128 bits escrito como 26 caracteres en Crockford Base32. Los primeros 10 caracteres codifican una marca de tiempo en milisegundos y los últimos 16 codifican bits aleatorios, de modo que los ULID creados más tarde siempre se ordenan después de los anteriores cuando se comparan como cadenas de texto simples. Es un identificador único ordenable que puedes generar sin conexión.
Esta guía lo desarma por partes: la anatomía decodificada carácter a carácter, la prueba de que realmente se ordena, las matemáticas de árbol B detrás de la ventaja en la base de datos y una mirada honesta a lo que filtra la marca de tiempo incrustada. Puedes seguir el hilo con un valor real en el generador de ULID —genera uno, decodifícalo, conviértelo a UUID— mientras lees.
¿Qué es un ULID?
Un ULID (identificador único universalmente ordenable de forma lexicográfica) es un identificador de 128 bits diseñado como una alternativa más ordenable y más compacta a un UUID. Se escribe como 26 caracteres en Crockford Base32: los primeros 10 contienen una marca de tiempo de 48 bits en milisegundos desde el Unix epoch, y los 16 restantes contienen 80 bits de aleatoriedad. Como el tiempo va primero, la cadena se ordena cronológicamente.
Esa última propiedad es la razón de ser del formato. UUIDv4 es totalmente aleatorio, lo cual es excelente para la unicidad pero significa que dos ID creados con un segundo de diferencia no guardan ninguna relación entre sí. Los ULID mantienen el modelo de generación sin coordinación, en cualquier lado, y le suman el orden temporal, así que una columna de ellos queda naturalmente ordenada por hora de creación sin nada extra.
Aquí tienes el formato de un vistazo:
| Propiedad | Valor |
|---|---|
| Bits | 128 |
| Codificación | 26 caracteres en Crockford Base32 |
| Estructura | marca de tiempo de 48 bits + aleatoriedad de 80 bits |
El resto del artículo explica cómo funciona cada pieza. La codificación y la capacidad de ordenarse merecen sus propias secciones, así que llegaremos pronto a Base32 y a la prueba del orden; primero, la estructura.
Anatomía de un ULID: 48 bits de tiempo + 80 bits de aleatoriedad
Los 26 caracteres de un ULID se dividen limpiamente en dos mitades. Los primeros 10 caracteres son la marca de tiempo; los últimos 16 son la parte aleatoria. Despliega el ejemplo canónico y el límite salta a la vista:
01ARYZ6S41 TSV4RRFFQ69G5FAV
└────────┘ └──────────────┘
10 chars 16 chars
48-bit ms 80-bit random
timestamp
Dos componentes, dos trabajos. Uno registra el cuándo; el otro garantiza la unicidad. Cada uno se decodifica por separado.
La marca de tiempo de 48 bits (primeros 10 caracteres)
Los 10 caracteres iniciales codifican un entero de 48 bits: el número de milisegundos desde el Unix epoch en el momento en que se creó el ULID. Toma el ejemplo canónico directamente de la especificación:
01ARYZ6S41 -> 1469918176385 ms -> 2016-07-30T22:36:16.385Z
Esa es una decodificación real y reversible: pega 01ARYZ6S41TSV4RRFFQ69G5FAV en un decodificador y obtienes exactamente 2016-07-30T22:36:16.385Z de vuelta. El componente de tiempo es dato puro, no un hash, así que leerlo no cuesta nada.
Un pequeño detalle que confunde a la gente: el primer carácter de un ULID siempre está entre 0 y 7. Un carácter de Crockford contiene 5 bits, y 48 bits no es múltiplo de 5; la marca de tiempo ocupa los 48 bits inferiores de los 50 bits que pueden cargar 10 caracteres, dejando los 2 bits superiores del primer carácter permanentemente en cero. Dos bits en cero limitan el valor de ese carácter a 7. Si alguna vez ves un ULID que empieza por 8 o más alto, está mal formado.
Los 80 bits de aleatoriedad (últimos 16 caracteres)
Los 16 caracteres restantes cargan 80 bits de aleatoriedad, y de esta mitad proviene la unicidad. Los bits deben venir de una fuente criptográficamente segura: crypto.getRandomValues en el navegador, no Math.random. La diferencia importa: Math.random es lo bastante predecible como para que un atacante pueda adivinar o colisionar valores, mientras que un CSPRNG no lo es.
¿Cuánto espacio son 80 bits? Aproximadamente 1,2 × 10²⁴ valores posibles, y eso es por milisegundo. Aunque generes millones de ULID dentro de un mismo milisegundo, las probabilidades de que dos saquen los mismos 80 bits siguen siendo ínfimas. A diferencia de la marca de tiempo, esta mitad no carga ningún significado decodificable: es ruido cuyo único propósito es hacer que cada ULID sea distinto.
Crockford Base32: por qué los ULID descartan I, L, O y U
Los ULID se codifican con Crockford Base32, un alfabeto de 32 símbolos: los dígitos 0–9 y las letras A–Z con cuatro removidas.
0123456789ABCDEFGHJKMNPQRSTVWXYZ
Las letras que faltan son I, L, O y U. Tres se descartan porque se parecen a dígitos —I y L recuerdan al 1, O recuerda al 0—, de modo que una persona leyendo un ULID en una pantalla no puede confundir una letra con un número. La contracara es una entrada tolerante: un decodificador conforme reasigna I y L a 1 y O a 0, y trata toda la cadena sin distinguir mayúsculas de minúsculas. La U se excluye por separado, para evitar formar accidentalmente palabras ofensivas.
Las matemáticas de bits son la otra razón. Cada carácter de Base32 codifica 5 bits, mientras que un carácter hexadecimal codifica solo 4. Empaqueta 128 bits a 5 bits por carácter y necesitas 26; empaqueta esos mismos 128 bits a 4 bits cada uno —como lo hace un UUID— y necesitas 32, más cuatro guiones, para un total de 36 caracteres. Así que un ULID es notablemente más corto que un UUID y, al no tener guiones, encaja directo en una URL, un nombre de archivo o un encabezado sin necesidad de escaparlo.
Crockford Base32 es un alfabeto de 32 símbolos (0–9 y A–Z menos I, L, O, U) que codifica 5 bits por carácter. Los ULID lo usan para empaquetar 128 bits en 26 caracteres seguros para URL e insensibles a mayúsculas y, lo crucial, el alfabeto está en orden ascendente, que es lo que permite que la cadena codificada se ordene igual que los bits en bruto.
Por qué los ULID se ordenan por tiempo
Muchos artículos te dicen que los ULID se ordenan por tiempo. Pocos muestran por qué, así que aquí está el argumento real. Se apoya en dos hechos que ya tienes: la marca de tiempo es la parte más significativa del valor, y el alfabeto de Crockford está dispuesto en orden ascendente.
Junta esos dos y obtienes una cadena de equivalencias:
string compare == 128-bit integer compare == creation-time compare
Comparar dos ULID carácter a carácter (como funciona un orden de cadenas) da la misma respuesta que comparar sus enteros subyacentes de 128 bits, porque el alfabeto preserva el orden: un carácter “más alto” siempre significa un valor más alto. Comparar los enteros de 128 bits da la misma respuesta que comparar las horas de creación, porque la marca de tiempo está en los bits más significativos, así que domina la comparación; la cola aleatoria solo desempata dentro del mismo milisegundo. El orden de cadena, el orden de bits y el orden temporal son el mismo orden.
Una demostración rápida. Dos ULID generados con un milisegundo de diferencia:
01ARYZ6S41... (created at T)
01ARYZ6S42... (created at T + 1 ms)
El décimo carácter avanza de 1 a 2, y un orden de texto simple coloca el segundo después del primero, sin que haga falta una columna de marca de tiempo aparte ni un comparador especial. La recompensa práctica, que la siguiente sección amplía, cabe en una línea: ORDER BY id devuelve las filas en orden cronológico sin ningún índice adicional.
Los ULID como claves primarias de base de datos: localidad en el árbol B
Aquí es donde los ULID se ganan el sueldo. La mayoría de las bases de datos relacionales almacenan el índice de clave primaria como un árbol B, y el lugar donde cae una nueva clave en ese árbol decide qué tan costosa es la inserción.
Un UUIDv4 aleatorio cae en un punto impredecible en cada inserción:
UUIDv4: cada nueva clave apunta a una página hoja aleatoria. La página suele estar llena, así que el motor la divide, copia la mitad de las filas a otro lado y ensucia páginas por todo el árbol. A lo largo de millones de filas esto fragmenta el índice, expulsa páginas útiles de la caché de búfer y arrastra hacia abajo el rendimiento de inserción. (Para las cifras concretas de división de páginas de índice —típicamente una diferencia de 2 a 10× en tablas con muchas escrituras— consulta la guía comparativa.)
Un ULID con prefijo de tiempo cae al final cada vez:
ULID: como los bits altos son una marca de tiempo, cada nueva clave es mayor que la anterior, así que se añade en el borde derecho del índice o cerca de él. Las inserciones se mantienen secuenciales, las divisiones de página casi desaparecen, el índice se mantiene compacto y un escaneo de rango sobre una ventana de tiempo lee una serie contigua de páginas.
Obtienes la generación sin coordinación de un UUID con la localidad de inserción de un entero autoincremental, sin exponer un contador secuencial adivinable, ya que la cola aleatoria sigue ocultando el siguiente valor exacto.
Consejo de almacenamiento: guarda los 128 bits como 16 bytes binarios —una columna uuid en PostgreSQL, BINARY(16) en MySQL—, no como un campo de texto de 26 caracteres, que desperdicia espacio e infla el índice. Codifica a la cadena Base32 solo en los bordes donde una persona o una URL la ven. La pestaña Convertir del generador convierte un ULID a un UUID precisamente para esto, ya que las dos formas son los mismos 128 bits.
ULID monotónicos: orden estricto dentro de un milisegundo
La prueba de ordenabilidad tiene una brecha honesta: dentro de un solo milisegundo, los ULID simples no están estrictamente ordenados. Comparten el mismo prefijo de tiempo de 10 caracteres, pero sus colas aleatorias de 80 bits se obtienen de forma independiente, así que cuál de dos ULID del mismo milisegundo se ordena primero es esencialmente cuestión de azar. Para la mayoría de los usos eso está bien. Cuando necesitas orden estricto incluso a ritmos por debajo del milisegundo, no lo está.
La generación monotónica cierra la brecha. La regla es simple: el primer ULID de un milisegundo dado recibe aleatoriedad fresca como de costumbre, y cada ULID posterior dentro de ese mismo milisegundo se produce tomando el valor aleatorio anterior de 80 bits e incrementándolo en uno (tratado como un entero big-endian, propagando el acarreo a bits más altos según haga falta). Cada valor es, por tanto, estrictamente mayor que el anterior.
Puedes verlo en un lote generado dentro de un milisegundo: solo se mueve el carácter final.
01KVT0F720ZK9N4T2QX7VR8WMC
01KVT0F720ZK9N4T2QX7VR8WMD
01KVT0F720ZK9N4T2QX7VR8WME
…WMC < …WMD < …WME, garantizado. Esto importa cada vez que las filas pueden crearse más rápido de lo que avanza el reloj de milisegundos: inserciones de alto rendimiento, registros de eventos, ID de mensajes en un bucle apretado. Cuando el reloj avanza al siguiente milisegundo, la generación vuelve a la aleatoriedad fresca y el ciclo se repite.
ULID vs UUID: cuándo usar cuál
La pregunta con la que la mayoría de la gente llega en realidad es ULID vs UUID. Aquí está la comparación enfocada: ULID contra las dos versiones de UUID que realmente sopesarías frente a él. (Para la matriz de decisión completa de cinco vías que incluye Snowflake y NanoID, consulta la comparación completa de ULID, UUID y Snowflake.)
| Propiedad | ULID | UUIDv4 | UUIDv7 |
|---|---|---|---|
| Longitud | 26 chars | 36 chars | 36 chars |
| Codificación | Crockford Base32 | Hexadecimal con guiones | Hexadecimal con guiones |
| ¿Ordenable por tiempo? | Sí | No | Sí |
| ¿Incrusta marca de tiempo? | Sí (48 bits ms) | No | Sí (48 bits ms) |
| ¿Estandarizado? | Especificación comunitaria | RFC 9562 | RFC 9562 |
| Ideal para | ID cortos y ordenables | ID opacos y aleatorios | ID ordenables en formato UUID |
En prosa: echa mano de un ULID cuando quieras la cadena más corta, segura para URL y ordenable. Echa mano de UUIDv4 cuando quieras un identificador opaco, totalmente aleatorio y sin tiempo incrustado; por ejemplo, un token público donde prefieres no revelar cuándo se creó. Echa mano de UUIDv7 cuando necesites orden temporal pero debas permanecer dentro del formato UUID estándar, con los bits de versión y variante en sus posiciones fijas y una columna uuid nativa donde colocarlo.
Los tres son de 128 bits, así que la conversión ULID ↔ UUID no tiene pérdidas en ningún sentido. La relación entre ULID y ulid vs uuid v7 es más estrecha de lo que parece: UUIDv7 es, en esencia, la versión estandarizada por la IETF de la misma idea de prefijo de tiempo que ULID inauguró. Si eres nuevo en los UUID por completo, empieza primero por los fundamentos y luego vuelve a esta comparación.
El compromiso de privacidad: los ULID filtran su hora de creación
La marca de tiempo incrustada es a la vez una funcionalidad y una filtración, según quién lea el ID. Cualquiera que tenga un ULID puede decodificar la marca de tiempo en un solo paso y saber el milisegundo exacto en que se creó el registro, sin necesidad de acceder a tu base de datos.
Dentro de tus propios sistemas eso es puro beneficio: tienes auditoría instantánea y orden sin pagar nada por él. En un identificador de cara al público es una divulgación real. La hora de creación puede ser sensible para el negocio por sí sola, y un puñado de ULID muestreados a lo largo del tiempo filtra tu tasa de creación —cuántos pedidos, cuentas o mensajes generas por segundo—, el tipo de cosa que a competidores y scrapers les gusta estimar.
Para ser justos, esta es una filtración más acotada que la de UUIDv1, que históricamente incrustaba la dirección MAC de la máquina generadora; un ULID expone solo el tiempo, nunca la identidad del hardware. Aun así, sopésalo. La mitigación simple: mantén los ULID internos y entrega un UUIDv4 totalmente aleatorio para los ID de cara al público donde el orden no importa.
Errores comunes con los ULID
La mayoría de los problemas con ULID son un puñado de decisiones de ingeniería evitables, no fallos del formato. Los recurrentes:
- Suponer que los ULID simples del mismo milisegundo están ordenados. Comparten un prefijo de tiempo pero tienen colas aleatorias independientes, así que su orden es indefinido. Solución: usa el modo monotónico cuando necesites orden estricto a ritmos por debajo del milisegundo.
- Almacenar un ULID como texto de 26 caracteres. Eso desperdicia espacio e infla el índice. Solución: guarda los 128 bits como 16 bytes (
uuid/BINARY(16)) y codifica a Base32 solo en los bordes. - Esperar que una conversión ULID→UUID se reporte como v4 o v7. La conversión vuelve a codificar los mismos bits; no establece los campos de versión y variante del UUID, así que una biblioteca que los inspeccione no verá una versión etiquetada. Solución: trata el resultado como un valor opaco de 128 bits, o genera un UUIDv7 real cuando necesites la etiqueta.
- Llenar la aleatoriedad con
Math.random. Es predecible y puede colisionar. Solución: usa siempre un CSPRNG comocrypto.getRandomValues. - Exponer ULID públicamente sin sopesar la filtración de la marca de tiempo. Consulta la sección de privacidad de arriba. Solución: ULID internos, UUIDv4 aleatorio para los ID públicos.
- Teclear a mano
I,L,OoUdentro de un ULID. Esas letras no están en el alfabeto, y reteclearlas invita a errores. Solución: copia los ULID, no los retecles.
Preguntas frecuentes
¿Es ULID un estándar oficial como UUID?
No. ULID es una especificación comunitaria publicada en GitHub, no un RFC de la IETF. Está ampliamente implementado y es estable, pero no tiene ningún organismo de estándares detrás. Si necesitas un identificador estandarizado y ordenado por tiempo, UUIDv7 (RFC 9562) aplica la misma idea dentro del formato UUID oficial.
¿Cuántos caracteres tiene un ULID y por qué es más corto que un UUID?
26 caracteres, frente a los 36 de un UUID. ULID usa Crockford Base32, que empaqueta 5 bits por carácter; el hexadecimal de un UUID empaqueta solo 4 bits y añade cuatro guiones. Por eso los mismos 128 bits necesitan menos caracteres en Base32, y ninguno de ellos necesita escaparse en una URL.
¿Pueden colisionar alguna vez dos ULID?
Prácticamente nunca. Dentro de un milisegundo un ULID tiene 80 bits aleatorios —alrededor de 1,2 × 10²⁴ posibilidades—, así que incluso generando millones por milisegundo las probabilidades de colisión siguen siendo ínfimas. El único requisito es que un RNG criptográficamente seguro llene la aleatoriedad; Math.random anula la garantía.
¿Puedo almacenar ULID en PostgreSQL o MySQL?
Sí. Un ULID es de 128 bits, así que conviértelo a forma de UUID y guárdalo en una columna uuid (PostgreSQL) o BINARY(16) (MySQL), y luego renderiza la cadena Base32 solo en los bordes. No hay un tipo de columna ULID nativo, pero la representación UUID cuesta los mismos 16 bytes y mantiene el índice compacto.
¿Distinguen los ULID entre mayúsculas y minúsculas?
La forma canónica es en mayúsculas, pero Crockford Base32 es insensible a mayúsculas en la entrada: un decodificador lee las letras minúsculas de la misma manera, y reasigna I/L a 1 y O a 0. Para evitar sorpresas en comparaciones de igualdad e índices, normaliza a una sola caja antes de almacenar o comparar.
¿Se agotará alguna vez la marca de tiempo de 48 bits?
No por muchísimo tiempo. 48 bits de milisegundos alcanzan el año 10889 antes de que el contador se desborde, así que el componente de marca de tiempo está, en la práctica, a prueba de futuro para cualquier aplicación real. Reemplazarás el sistema, el lenguaje y la base de datos mucho antes de que al formato se le acabe el espacio.
¿Puedo generar ULID en el navegador o en el móvil sin un servidor?
Sí, ese es un beneficio central. Los ULID no necesitan coordinador central, así que cualquier nodo, edge worker, navegador o dispositivo puede generar uno a partir de su reloj más un RNG seguro. Los valores creados en máquinas distintas siguen ordenándose juntos por tiempo después, porque la marca de tiempo vive dentro del propio ID.
Conclusión
Los ULID resuelven un problema específico y real —las claves aleatorias fragmentando tu índice— sin quitarte la generación descentralizada. Vale la pena tener presente la mecánica:
- Un ULID es una marca de tiempo de 48 bits en milisegundos + 80 bits de aleatoriedad, codificada como 26 caracteres en Crockford Base32.
- Se ordena por tiempo porque la marca de tiempo es el componente más significativo y el alfabeto preserva el orden: el orden de cadena equivale al orden temporal.
- Ese orden le da a un árbol B la localidad de inserción que a un UUIDv4 aleatorio le falta, manteniendo las escrituras rápidas y el índice compacto.
- Usa el modo monotónico cuando necesites orden estricto para ID generados en el mismo milisegundo.
- Sopesa la filtración de la marca de tiempo antes de exponer ULID en identificadores de cara al público.
- Elige UUIDv7 en su lugar cuando debas permanecer dentro del formato UUID estándar.
Cuando estés listo para ponerlo a trabajar, abre el generador de ULID para generar, decodificar y convertir ULID enteramente en tu navegador, sin que nada salga de tu equipo hacia un servidor.