Generación de identificadores
1) Por qué prestar atención a los identificadores
ID (ID) es la clave fundamental de la entidad: líneas de BD, mensajes, archivo, orden. Sus propiedades dependen de:- Singularidad y escala (colisiones, crecimiento horizontal).
- Orden y ordenación (correlación temporal, replicación, dedoup).
- Rendimiento de almacenamiento (índices, páginas calientes, tamaño de clave).
- Seguridad (imprevisibilidad, filtraciones, adivinación).
- Usabilidad/integración (corto, URL-seguro, no sensible al registro).
Selección de ID: compromiso entre entropía, ordenabilidad, longitud, velocidad de generación y operación.
2) Requisitos y términos clave
Singularidad: la probabilidad de conflicto debe ser inferior al riesgo aceptable.
Entropía: «cuánta aleatoriedad» contiene un ID (bit).
Ordenabilidad (tiempo-sorteable/k-sortable): ordenamiento lexicográfico ≈ ordenamiento por tiempo.
Monotonía: secuencia no descendente dentro del nodo/flujo.
Localización del registro: cuánto se concentra la nueva inserción en la «cola» del índice (peligro de páginas calientes).
Previsibilidad: puede adivinar los ID vecinos (importante para la seguridad/API).
Vista: binario/cadena, Base16/32/36/58/64, guiones, registro.
3) Principales familias de identificadores
3. 1 UUID
v4 (random): 122 bits de entropía. Desordenamos, es bueno para la seguridad y la simplicidad. Menos: índices «haotit» debido a la distribución aleatoria - que, sin embargo, disipa uniformemente las cargas y elimina las «páginas calientes».
v1 (tiempo + MAC): ordenamos, pero lleva MAS/tiempo (privacidad); a menudo se evita.
v7 (tiempo-ordenado): tiempo milisegundo + parte aleatoria. Diseño bajo clasificación lexicográfica por tiempo y buena compresión en DB. Compromiso: aparece la «cola caliente» del índice; se trata con charding/prefijos/incrementos.
Consejos
Para APIs externas y requisitos de orden no estrictos - v4.
Para BD de eventos/logs y claves «ordenables» - v7.
3. 2 ULID (Crockford Base32)
128 bits: 48 bits de tiempo (ms) + 80 bits de aleatoriedad. Se clasifica lexicográficamente por tiempo, hombre-amistoso (sin 'I, L, O, U'), URL-seguro. Hay una variación monótona (con la misma marca de tiempo, la parte aleatoria aumenta).
Pros: legibilidad, ordenabilidad, portabilidad.
Contras: con una frecuencia de inserción muy alta en un punto del tiempo - «cola caliente».
3. 3 KSUID
160 bits: 32 bits de tiempo (sec) con respecto a la era + 128 bits de aleatoriedad. ¿Mayor rango de tiempo y ordenabilidad estable, cadenas más cortas que ULID? (no - más largo, pero con su codificación), bueno para los registros distribuidos y los objetos.
3. 4 Snowflake-similar (k-sortable flake IDs)
Esquema clásico (personalizable):
[ timestamp bits ][ region/datacenter bits ][ worker bits ][ sequence bits ]
Propiedades: crecimiento monótono en el nodo, singularidad cuasi global, representación binaria corta (64 bits).
Riesgos: dependencia del reloj (deriva/retroceso del tiempo), agotamiento de la sequence en un solo tick, coordinación de bits region/worker.
Se trata: protección contra «clock back», reserva de sequence, detector de tiempo, disciplina PTP/NTP.
3. 5 Secuencias de BD (SEQUENCE/IDENTITY)
La generación monótona más simple en una sola DBM/sanda.
Pros: corto, rápido, conveniente para las tablas locales.
Contras: difícil globalmente en un clúster distribuido; predecible (inseguro como clave pública), crea la cola caliente del índice.
3. 6 ID de dirección de contenido (hash content)
SHA-256/Blake3 del contenido → ID estable, deduplicación, verificación de integridad, almacenamiento en caché.
Pros: determinismo, protección de sustitución.
Contras: generación costosa (CPU), colisiones ceros prácticos, sin clasificación temporal, longitud.
4) Colisiones y «paradoja de cumpleaños» (intuitivamente)
La probabilidad de colisión para un ID aleatorio de tamaño 'b' bit con 'n' generaciones es aproximada:
p ≈ 1 - exp (-n (n-1 )/2/2 ^ b) ≈ n ^ 2/2 ^ (b + 1) (for small p)
Ejemplos:
- UUIDv4 (122 bits) a n = 10 ^ 12 (billón) → p ~ 1e-14 (descuidado).
- 64-bit rand → a n = 10 ^ 9 ya p ~ 0. 027 (riesgo notable).
- Conclusión: 64-bit aleatorio es a menudo poco para los sistemas enormes; utilice 96/128 bits.
5) Índices, páginas calientes y almacenamiento
Las claves aleatorias (v4) distribuyen uniformemente las inserciones en el árbol del índice → no hay «cola», pero la localización en caché es peor.
Los ordenados por tiempo (v7/ULID/Snowflake) se insertan «en la cola» → mejor localidad y compresión, pero el riesgo de páginas calientes bajo un registro paralelo alto.
- prefijos/charding por tenant/region (añadir 1-2 bytes antes del tiempo);
- interleaving: parte del azar en los bits más antiguos;
- inserciones de batch, fillfactor en árbol B, corte automático en BRIN/clustering para grandes registros.
- 'UUID (16B)' vs 'BIGINT (8B) '/' INT8' ahorra memoria/caché; las filas Base32/58/64 aumentan el tamaño en un 20-60%. Para la DB, almacenar binario, serializar en una cadena en el borde.
6) Seguridad y privacidad
No utilice SEQUENCE/INT como ID pública en URL/API: es adivinable → enumerar recursos.
Agregue identificaciones aleatorias e impredecibles (v4/v7/ULID/KSUID) para referencias externas.
No codifique PII en ID. Si desea habilitar un atributo, cifre/firme (por ejemplo, JWE/JWS) o utilice tokens opacos.
Codificaciones URL seguras: Base32 Crockford, Base58 (sin '0OIl'), Base64url.
7) Multi-tenencia, prefijos y enrutamiento
Formato: '[TENANT _ PREFIX] - [ID]' o binario: 'tenant _ id || id'.
Ventajas: filtros rápidos/lotes por inquilino, protección contra N + 1 escáneres.
Contras: puede empeorar la densidad de entropía en los bits más antiguos → pensar en la distribución (hash prefijo).
El sufijo hash (2-3 bytes) reduce los conflictos y ayuda al routing shard: 'shard = hash (id)% N'.
8) Recomendaciones prácticas para la selección
API, enlaces públicos, servicios distribuidos sin orden estricto: UUIDv4, ULID/KSUID.
Logs/eventos/pedidos, donde a menudo ordenamos por tiempo: UUIDv7 o ULID (monótono).
Ancho de banda ultra alto con monotonía local y clave corta: Snowflake-similar 64-bit (requiere disciplina de tiempo).
Repositorios de artefactos/builds/blobs: content-address (SHA-256), y en la parte superior, un corto «showcase» (Hashids/link) amigable con las personas.
Tablas locales en un solo DB: SEQUENCE/IDENTITY + «envoltorio» externo para referencias públicas (masking).
9) Implementaciones y ejemplos
9. 1 PostgreSQL
Almacena el UUID binario, los índices son 'btree' o 'hash' por necesidad.
sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- или uuid_generate_v4()
created_at timestamptz NOT NULL DEFAULT now(),
tenant smallint NOT NULL
);
-- For time-sortable (UUIDv7) store binary (uuid), generation in the application.
-- If you want a cluster by time:
CREATE INDEX ON orders (created_at DESC);
Sequential hot fix: para un ID de tiempo-sorteado, agregue «sal» a los bits más antiguos o lote por tenant:
sql
CREATE TABLE orders_t1 PARTITION OF orders FOR VALUES IN (1);
CREATE TABLE orders_t2 PARTITION OF orders FOR VALUES IN (2);
9. 2 Redis (contadores atómicos/monutonio)
bash
INCR "seq: orders" # local sequence combine: epoch_ms<<20 (worker_id<<10) (seq & 1023)
9. 3 Generador tipo Snowflake (pseudocódigo)
pseudo const EPOCH = 1704067200000 # custom epoch (ms)
state: last_ms=0, seq=0, worker=7, region=3
next():
now = epoch_ms()
if now < last_ms: wait_until(last_ms) # защита от clock back if now == last_ms:
seq = (seq + 1) & ((1<<12)-1) # 12 бит if seq == 0: wait_next_ms()
else:
seq = 0 last_ms = now return (now-EPOCH)<<22 region<<17 worker<<12 seq
9. 4 ULID/UUID en aplicaciones
Go
go
// ULID t:= time. Now(). UTC()
entropy:= ulid. Monotonic(rand. New(rand. NewSource(t. UnixNano())), 0)
id:= ulid. MustNew(ulid. Timestamp(t), entropy)
//UUID v7 (if there is a library)
id:= uuid. Must(uuid. NewV7())
Node. js
js import { ulid } from 'ulid';
import { v4 as uuidv4 } from 'uuid';
const id1 = ulid();
const id2 = uuidv4(); // v4
Python
python import uuid, time id_v4 = uuid. uuid4()
For v7, use a library (for example, uuid6/7 third-party packages)
10) Codificaciones y vistas
Binario en BD ('BYTEA', 'UUID') → compacta y rápida. En el borde, convierta a:- Base32 Crockford (ULID): insensible al registro, sin caracteres visualmente similares.
- Base58: más corto Base32/64 para tokens humanos, URL-safe.
- Base64url: corto, pero '-' y '_' en la URL.
Estabilice el registro y el formato (guiones/ausencia) para evitar duplicados al comparar filas.
11) Pruebas de reproducción y observabilidad
Colisiones: métrica 'id _ collision _ total' (debe ser 0), alerta a> 0.
Distribución de prefijos: histograma de los bytes más antiguos: buscamos la compra.
Velocidad de generación: 'ids _ per _ sec', p99 latencia del generador.
Clock skew (para Snowflake): offset de nodos, eventos «clock went back».
Colas de índice: p95/p99 'INSERT' latencia; porcentaje de bloqueos/páginas calientes.
- El inyector «clock drift/back» → nos aseguramos de que el generador espera/cambia.
- Desbordamiento de 'sequence' en milisegundos → verificación de espera de next_ms.
- Paralelismo masivo → si no hay tormentas de bloqueo en el índice.
12) Anti-patrones
AUTO_INCREMENT/SEQUENCE como un ID público: adivina, filtraciones. Utilice un ID público opaco en la parte superior del interior.
UUIDv1 (MAS/tiempo) hacia fuera: privacidad.
Identificación aleatoria de 64 bits por billones de entradas: riesgo real de colisiones.
Un «generador central» global sin HA: SPOF y cuello de botella.
IDs de tiempo sin protección contra clock back: duplicados/regresión de orden.
Mezclar diferentes formatos de ID sin una versión/prefijo explícito → caos en el debag/migraciones.
Guardar ID como cadenas con diferentes registros/formularios → duplicados ocultos.
13) Lista de verificación de implementación
- Se ha seleccionado un formato (v4/v7/ULID/KSUID/Snowflake/SEQ/hash) para los requisitos de dominio.
- Se han definido los requisitos de orden (si se necesita ordenabilidad).
- Se ha evaluado la probabilidad de colisiones (b bit, n generaciones) y se ha establecido un umbral de riesgo.
- Codificación diseñada (binario en un escaparate DB + humano).
- Para time-sorted - protección contra clock back, sequence-limits y disciplina NTP/PTP.
- Para las identificaciones públicas: imprevisibilidad (rand/ULID/KSUID), ausencia de PII.
- Se ha pensado en el routing shard (hash (id)% N), prefijos multi-tenant.
- Observabilidad: métricas de colisión, distribución, latencia, clock skew.
- Casos de prueba de desbordamiento sequence/alta competencia/longitud de ventana.
- Documentación del formato, versión, era, marca de bits y plan de migración.
14) FAQ
P: ¿Qué elegir «predeterminado» para microservicios?
R: UUIDv7 o ULID: ordenabilidad en el tiempo, mucha entropía, generación simple en el borde. Para APIs externas - ULID/UUIDv4 también aprox.
P: Se necesita un ID corto y humanizado.
A: ULID/KSUID o codificación Base58 de 128 bits de ID aleatoria/temporal. Recuerda la longitud y los conflictos.
P: ¿Es posible hacer un ID «numérico corto», pero seguro?
R: Sí: guarde el SEQ interno, y afuera dé el token opaque (rande 96-128 bits) o Hashids con sal + firma.
P: ¿Cómo migrar de SEQ a UUIDv7?
R: Escriba la nueva columna 'id _ new' (UUID), duplique, publique los vínculos al nuevo ID, luego cambie las claves RC/externas y elimine el antiguo.
P: ¿Por qué mis inserciones con ULID se volvieron «calientes»?
R: Inserte claves estrictamente crecientes en un solo índice. Divida por lotes/tenant, mezcle los bits más antiguos, use inserciones de batch.
15) Resultados
Un buen ID es el conjunto adecuado de propiedades para la tarea: suficiente entropía, clasificación predecible (si es necesario), publicidad segura y explotación saludable de los índices. Elija UUIDv4/ULID/UUIDv7/KSUID para la simplicidad y la distribución, Snowflake - para la monotonía densa y las claves cortas (bajo la disciplina del tiempo), secuencia - para las tablas locales, hashes de contenido - para los artefactos. Anote la observabilidad y las pruebas - y los identificadores dejarán de ser una fuente de sorpresas.