Pular para conteúdo

txid: Gere IDs de transação ou solicitação únicos e ordenáveis para nginx-module-lua/nginx

Instalação

Se você ainda não configurou a assinatura do repositório RPM, inscreva-se. Depois, você pode prosseguir com os seguintes passos.

CentOS/RHEL 7 ou 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 com NGINX, certifique-se de que o nginx-module-lua esteja instalado.

Este documento descreve lua-resty-txid v1.0.0 lançado em 01 de abril de 2018.


CircleCI

lua-resty-txid fornece uma função que pode ser usada para gerar IDs de transação/solicitação únicos para OpenResty/nginx. Os IDs podem ser usados para correlacionar logs ou solicitações upstream e têm as seguintes características:

  • 20 caracteres
  • codificado em base32hex
  • Ordenável temporal e lexicalmente
  • Insensível a maiúsculas e minúsculas
  • Identificador de 96 bits

lua-resty-txid é uma porta LuaJIT de ngx_txid para OpenResty (ou nginx com ngx_lua). Os IDs gerados pelo lua-resty-txid seguem exatamente o mesmo padrão e são compatíveis com ngx_txid.

Uso

Uma única função txid() Lua é exposta por este módulo para gerar IDs:

local txid = require "resty.txid"
local id = txid() -- b2g6q94qdn6h84an7vfg

Cada vez que txid() é chamado, um novo ID único será retornado, então você precisará armazenar em cache o resultado se desejar reutilizar o mesmo ID em vários lugares para uma única solicitação. Dependendo do seu uso, ngx.ctx ou set_by_lua oferecem algumas opções simples para armazenar o valor em uma base por solicitação.

txid() -- b2g83t2oshrg092mjggg
txid() -- b2g83t2oodncokuges00

ngx.ctx.txid = txid() -- b2g83t2od939mdvb2l0g
ngx.ctx.txid          -- b2g83t2od939mdvb2l0g

Finalmente, txid() aceita um argumento opcional para qual timestamp (em milissegundos) usar ao gerar o ID. Por padrão, o timestamp atual é usado. Como os IDs resultantes são ordenáveis temporal e lexicalmente, isso pode ser usado para gerar IDs que serão ordenados com base em uma data ou 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

Exemplo

Um exemplo mais completo, com cache, configuração de cabeçalhos de solicitação/resposta e integração com o log do nginx:

http {
  log_format agent "$lua_txid $http_user_agent";
  log_format addr "$lua_txid $remote_addr";

  init_by_lua_block {
    # Pré-carregar o módulo.
    require "resty.txid"
  }

  server {
    listen 8080;
    access_log logs/agents.log agent;
    access_log logs/addrs.log addr;

    # Defina uma variável nginx que é armazenada em cache por solicitação e pode ser usada no
    # log_format do nginx.
    set_by_lua_block $lua_txid {
      local txid = require "resty.txid"
      return txid()
    }

    location / {
      # Defina um cabeçalho na resposta fornecendo o ID.
      more_set_headers "X-Request-Id: $lua_txid";

      # Defina um cabeçalho na solicitação fornecendo o ID (que será enviado para o
      # upstream proxy).
      more_set_input_headers "X-Request-Id: $lua_txid";

      proxy_pass http://localhost:8081;
    }
  }
}

Desempenho

Os benchmarks indicam que o desempenho é equivalente à extensão C ngx_txid.

Design

O design do ID de transação é uma porta direta de ngx_txid, então aqui estão todas as informações originais sobre o design do ngx_txid:

Contexto

O design deste ID de transação deve atender aos seguintes requisitos:

  • Ser aproximadamente ordenável temporalmente com granularidade de ~segundo.
  • Ter uma representação que seja aproximadamente ordenável lexicalmente com granularidade de ~segundo.
  • Ter uma probabilidade de colisão inferior a 1e-9 para 1 milhão de transações por segundo.
  • Ser eficiente e fácil de decodificar em tipos C de tamanho fixo.
  • Estar sempre disponível, mesmo que isso aumente a probabilidade de colisão.
  • Usar o menor número possível de bytes.
  • Funcionar com redes IPv4 e IPv6.

Técnica

Use um relógio de resolução de milissegundos monotônico nos 42 bits altos e entropia do sistema nos 54 bits baixos. Use bits de entropia suficientes para satisfazer uma probabilidade de colisão a uma taxa de solicitação global desejada.

+------------- 64 bits------------+--- 32 bits ----+
+------ 42 bits ------+--22 bits--|----------------+
| msec desde 1970-1-1 | random    | random         |
+---------------------+-----------+----------------+

Uma taxa de solicitação de 1 milhão por segundo em todos os servidores significa 1000 valores aleatórios por milissegundo. Estimar a probabilidade de colisão usando o paradoxo do aniversário pode ser feito com esta fórmula: 1 - e^(-((m^2)/(2*n))) onde m é o número de ids e n é o número de valores aleatórios possíveis.

Ao usar 54 bits de entropia:

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

As chances de colisão são pequenas mesmo a 10 milhões de solicitações por segundo.

O Nginx mantém o controle do relógio atual em incrementos da diretiva de configuração timer_resolution. A resolução do relógio para $txid é de 1ms, então uma resolução de timer maior que 1ms significa que a probabilidade de colisão aumentará. Se você tiver uma timer_resolution de 10ms, 1 milhão de solicitações por segundo exigiria 10.000 valores aleatórios por segundo no pior caso.

Codificação

base32hex é usado com um alfabeto em letras minúsculas e sem caracteres de preenchimento, escolhido pelas seguintes razões:

  • Ordem de classificação lexical equivalente à ordem de classificação numérica.
  • Igualdade insensível a maiúsculas e minúsculas.
  • Letras minúsculas são mais fáceis para comparações visuais.
  • Mais denso que a codificação hexadecimal por 4 bytes.

Outras técnicas

  • snowflake: Usa time(41) + unique id(10) + sequence(12).
  • Prós: Sequências únicas garantidas.
  • Prós: Cabe em 63 bits.
  • Contras: Requer coordenação de ID único para cada servidor - 16 processos de trabalho por host significa um limite de 64 instâncias de nginx.
  • Contras: Apenas 11 bits disponíveis para ID único, precisa de monitoramento.
  • Contras: Ordenação total só é possível no mesmo processo.
  • Contras: Interrupção de serviço possível quando os relógios perdem sincronização.

  • flake: Usa time + mac id + sequence.

  • Prós: Sequências únicas garantidas.
  • Contras: Usa 128 bits.
  • Contras: Desperdiça 22 bits de dados de timestamp.
  • Contras: Apenas um único processo por host pode gerar ids - precisa sincronizar o acesso à sequência de cada processo de trabalho.
  • Contras: Interrupção de serviço possível quando os relógios perdem sincronização.
  • Contras: Seeds cross platform MAC Address lookup.

  • UUIDv4: 122 bits de entropia.

  • Prós: Probabilidade de colisão muito baixa.
  • Contras: Não ordenável.

  • UUID com timestamp: 48 bits de tempo + 74 bits de entropia.

  • Prós: Probabilidade de colisão muito baixa.
  • Contras: A representação em string não é local temporalmente.

  • httpd mod_unique_id: Host ip(32) + pid(32) + time(32) + sequence (16) + thread id (32).

  • Prós: Determinístico.
  • Contras: Usa 144 bits.
  • Contras: Assume IPv4 único para a interface do hostname.
  • Contras: Representação personalizada sensível a maiúsculas e minúsculas não ordenável - base64 com um alfabeto personalizado.
  • Contras: Limite rígido de 65535 ids por segundo por pid - pequena tolerância para passos de relógio.

Desenvolvimento

Após clonar o repositório, o Docker pode ser usado para executar o conjunto de testes:

docker-compose run --rm app make test

Processo de Lançamento

Para publicar lançamentos no OPM e LuaRocks:

VERSION=x.x.x make release

GitHub

Você pode encontrar dicas adicionais de configuração e documentação para este módulo no repositório GitHub para nginx-module-txid.