txid: Generar IDs de transacción o solicitud únicos y ordenables para nginx-module-lua/nginx
Instalación
Si no has configurado la suscripción al repositorio RPM, regístrate. Luego puedes proceder con los siguientes pasos.
CentOS/RHEL 7 o Amazon Linux 2
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-txid
CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-txid
Para usar esta biblioteca Lua con NGINX, asegúrate de que nginx-module-lua esté instalado.
Este documento describe lua-resty-txid v1.0.0 lanzado el 01 de abril de 2018.
lua-resty-txid proporciona una función que se puede usar para generar IDs de transacción/solicitud únicos para OpenResty/nginx. Los IDs se pueden usar para correlacionar registros o solicitudes ascendentes y tienen las siguientes características:
- 20 caracteres
- codificado en base32hex
- Ordenable temporal y léxicamente
- Insensible a mayúsculas
- Identificador de 96 bits
lua-resty-txid es un puerto de LuaJIT de ngx_txid para OpenResty (o nginx con ngx_lua). Los IDs generados por lua-resty-txid siguen el mismo patrón exacto y son compatibles con ngx_txid.
Uso
Una única función Lua txid() es expuesta por este módulo para generar IDs:
local txid = require "resty.txid"
local id = txid() -- b2g6q94qdn6h84an7vfg
Cada vez que se llama a txid(), se devolverá un nuevo ID único, por lo que necesitarás almacenar en caché el resultado si deseas reutilizar el mismo ID en múltiples lugares para una sola solicitud. Dependiendo de tu uso, ngx.ctx o set_by_lua ofrecen algunas opciones simples para almacenar en caché el valor por solicitud.
txid() -- b2g83t2oshrg092mjggg
txid() -- b2g83t2oodncokuges00
ngx.ctx.txid = txid() -- b2g83t2od939mdvb2l0g
ngx.ctx.txid -- b2g83t2od939mdvb2l0g
Finalmente, txid() acepta un argumento opcional para qué marca de tiempo (en milisegundos) usar al generar el ID. Por defecto, se utiliza la marca de tiempo actual. Dado que los IDs resultantes son ordenables temporal y léxicamente, esto se puede usar para generar IDs que se ordenarán en función de una fecha u hora anterior.
local timestamp_ms = 655829050000 -- 1990-10-13 14:44:10
txid(timestamp_ms) -- 4om9qi54la8ffr4bd9sg
local timestamp_ms = 655929050000 -- 1990-10-14 12:30:50
txid(timestamp_ms) -- 4on1lg74nt0ud2ssllu0
Ejemplo
Un ejemplo más completo, con almacenamiento en caché, configuración de encabezados de solicitud/respuesta e integración con el registro de nginx:
http {
log_format agent "$lua_txid $http_user_agent";
log_format addr "$lua_txid $remote_addr";
init_by_lua_block {
# Pre-cargar el módulo.
require "resty.txid"
}
server {
listen 8080;
access_log logs/agents.log agent;
access_log logs/addrs.log addr;
# Establecer una variable de nginx que se almacena en caché por solicitud y se puede usar en el
# log_format de nginx.
set_by_lua_block $lua_txid {
local txid = require "resty.txid"
return txid()
}
location / {
# Establecer un encabezado en la respuesta proporcionando el ID.
more_set_headers "X-Request-Id: $lua_txid";
# Establecer un encabezado en la solicitud proporcionando el ID (que se enviará al
# upstream proxied).
more_set_input_headers "X-Request-Id: $lua_txid";
proxy_pass http://localhost:8081;
}
}
}
Rendimiento
Las pruebas de rendimiento indican que el rendimiento es equivalente a la extensión C ngx_txid.
Diseño
El diseño del ID de transacción es un puerto directo de ngx_txid, así que aquí tienes toda la información original sobre el diseño de ngx_txid:
Antecedentes
El diseño de este ID de transacción debe cumplir con los siguientes requisitos:
- Ser aproximadamente ordenable temporalmente con granularidad de ~segundos.
- Tener una representación que sea aproximadamente ordenable léxicamente con granularidad de ~segundos.
- Tener una probabilidad de menos de 1e-9 de colisión a 1 millón de transacciones por segundo.
- Ser eficiente y fácil de decodificar en tipos C de tamaño fijo.
- Estar siempre disponible a riesgo de una mayor probabilidad de colisión.
- Usar la menor cantidad de bytes posible.
- Funcionar con redes IPv4 e IPv6.
Técnica
Usar un reloj de resolución de milisegundos monotónico en los 42 bits altos y entropía del sistema para los 54 bits bajos. Usar suficientes bits de entropía para satisfacer una probabilidad de colisión a una tasa de solicitud global deseada.
+------------- 64 bits------------+--- 32 bits ----+
+------ 42 bits ------+--22 bits--|----------------+
| msec desde 1970-1-1 | random | random |
+---------------------+-----------+----------------+
Una tasa de solicitudes de 1 millón por segundo en todos los servidores significa 1000 valores aleatorios por milisegundo. Estimar la probabilidad de colisión usando la paradoja del cumpleaños se puede hacer con esta fórmula: 1 - e^(-((m^2)/(2*n))) donde m es el número de ids y n es el número de valores aleatorios posibles.
Al usar 54 bits de entropía:
1mil req/s = 1 - exp(-((1000^2) /(2*2^54))) = 2.775558e-11
10mil req/s = 1 - exp(-((10000^2)/(2*2^54))) = 2.775558e-09
Las probabilidades de colisión son pequeñas incluso a 10 millones de solicitudes por segundo.
Nginx lleva un seguimiento del reloj actual en incrementos de la directiva de configuración timer_resolution. La resolución del reloj para $txid es de 1ms, por lo que una resolución de temporizador mayor a 1ms significa que la probabilidad de colisión aumentará. Si tienes una timer_resolution de 10ms, 1 millón de solicitudes por segundo requeriría 10,000 valores aleatorios por segundo en el peor de los casos.
Codificación
Se utiliza base32hex con un alfabeto en minúsculas y sin caracteres de relleno por las siguientes razones:
- Orden de clasificación léxica equivalente al orden de clasificación numérica.
- Igualdad insensible a mayúsculas.
- Las minúsculas son más fáciles para comparaciones visuales.
- Más denso que la codificación hex por 4 bytes.
Otras técnicas
- snowflake: Usa tiempo(41) + id único(10) + secuencia(12).
- Pro: Secuencias únicas garantizadas.
- Pro: Se ajusta en 63 bits.
- Con: Requiere coordinación de id único para cada servidor - 16 procesos de trabajo por host significa un límite de 64 instancias de nginx.
- Con: Solo 11 bits disponibles para id único, necesita monitoreo.
- Con: El orden total solo es posible en el mismo proceso.
-
Con: Posible interrupción del servicio cuando los relojes pierden sincronización.
-
flake: Usa tiempo + id mac + secuencia.
- Pro: Secuencias únicas garantizadas.
- Con: Usa 128 bits.
- Con: Desperdicia 22 bits de datos de marca de tiempo.
- Con: Solo un proceso por host puede generar ids - necesita sincronizar el acceso a la secuencia desde cada proceso de trabajo.
- Con: Posible interrupción del servicio cuando los relojes pierden sincronización.
-
Con: Semillas de búsqueda de dirección MAC multiplataforma.
-
UUIDv4: 122 bits de entropía.
- Pro: Muy baja probabilidad de colisión.
-
Con: No ordenable.
-
UUID con marca de tiempo: 48 bits de tiempo + 74 bits de entropía.
- Pro: Muy baja probabilidad de colisión.
-
Con: La representación en cadena no es local temporalmente.
-
httpd mod_unique_id: Host ip(32) + pid(32) + tiempo(32) + secuencia (16) + id de hilo (32).
- Pro: Determinista.
- Con: Usa 144 bits.
- Con: Asume IPv4 único para la interfaz del nombre de host.
- Con: Representación personalizada sensible a mayúsculas no ordenable - base64 con un alfabeto personalizado.
- Con: Límite duro de 65535 ids por segundo por pid - pequeña tolerancia para pasos de reloj.
Desarrollo
Después de clonar el repositorio, se puede usar Docker para ejecutar la suite de pruebas:
docker-compose run --rm app make test
Proceso de lanzamiento
Para publicar lanzamientos en OPM y LuaRocks:
VERSION=x.x.x make release
GitHub
Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-txid.