Aller au contenu

txid: Générer des identifiants de transaction ou de requête uniques et triables pour nginx-module-lua/nginx

Installation

Si vous n'avez pas configuré l'abonnement au dépôt RPM, inscrivez-vous. Ensuite, vous pouvez procéder avec les étapes suivantes.

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

Pour utiliser cette bibliothèque Lua avec NGINX, assurez-vous que nginx-module-lua est installé.

Ce document décrit lua-resty-txid v1.0.0 publié le 01 avril 2018.


CircleCI

lua-resty-txid fournit une fonction qui peut être utilisée pour générer des identifiants de transaction/requête uniques pour OpenResty/nginx. Les identifiants peuvent être utilisés pour corréler les journaux ou les requêtes en amont et ont les caractéristiques suivantes :

  • 20 caractères
  • encodé en base32hex
  • Triable temporellement et lexicalement
  • Insensible à la casse
  • Identifiant de 96 bits

lua-resty-txid est un port LuaJIT de ngx_txid pour OpenResty (ou nginx avec ngx_lua). Les identifiants générés par lua-resty-txid suivent exactement le même modèle et sont compatibles avec ngx_txid.

Utilisation

Une seule fonction Lua txid() est exposée par ce module pour générer des identifiants :

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

Chaque fois que txid() est appelé, un nouvel identifiant unique sera renvoyé, donc vous devrez mettre en cache le résultat si vous souhaitez réutiliser le même identifiant à plusieurs endroits pour une seule requête. En fonction de votre utilisation, ngx.ctx ou set_by_lua offrent quelques options simples pour mettre en cache la valeur par requête.

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

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

Enfin, txid() accepte un argument optionnel pour quel horodatage (en millisecondes) utiliser lors de la génération de l'identifiant. Par défaut, l'horodatage actuel est utilisé. Étant donné que les identifiants résultants sont triables temporellement et lexicalement, cela peut être utilisé pour générer des identifiants qui seront triés en fonction d'une date ou d'une heure précédente.

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

Exemple

Un exemple plus complet, avec mise en cache, définition des en-têtes de requête/réponse et intégration avec les journaux de nginx :

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

  init_by_lua_block {
    # Précharger le module.
    require "resty.txid"
  }

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

    # Définir une variable nginx qui est mise en cache par requête et peut être utilisée dans le
    # log_format de nginx.
    set_by_lua_block $lua_txid {
      local txid = require "resty.txid"
      return txid()
    }

    location / {
      # Définir un en-tête sur la réponse fournissant l'identifiant.
      more_set_headers "X-Request-Id: $lua_txid";

      # Définir un en-tête sur la requête fournissant l'identifiant (qui sera envoyé au
      # proxied upstream).
      more_set_input_headers "X-Request-Id: $lua_txid";

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

Performance

Les benchmarks indiquent que la performance est équivalente à celle de l'extension C ngx_txid.

Conception

La conception de l'identifiant de transaction est un port direct de ngx_txid, voici donc toutes les informations originales sur la conception provenant de ngx_txid :

Contexte

La conception de cet identifiant de transaction doit répondre aux exigences suivantes :

  • Être à peu près triable temporellement numériquement avec une granularité d'environ une seconde.
  • Avoir une représentation qui est à peu près triable lexicalement avec une granularité d'environ une seconde.
  • Avoir une probabilité de collision inférieure à 1e-9 pour 1 million de transactions par seconde.
  • Être efficace et facile à décoder en types C de taille fixe.
  • Être toujours disponible au risque d'une probabilité de collision plus élevée.
  • Utiliser le moins de bytes possible.
  • Fonctionner avec des réseaux IPv4 et IPv6.

Technique

Utiliser une horloge à résolution milliseconde monotone dans les 42 bits supérieurs et l'entropie système pour les 54 bits inférieurs. Utiliser suffisamment de bits d'entropie pour satisfaire une probabilité de collision à un taux de requête global souhaité.

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

Un taux de requête de 1 million par seconde sur tous les serveurs signifie 1000 valeurs aléatoires par milliseconde. Estimer la probabilité de collision en utilisant le paradoxe de l'anniversaire peut être fait avec cette formule : 1 - e^(-((m^2)/(2*n)))m est le nombre d'identifiants et n est le nombre de valeurs aléatoires possibles.

Lors de l'utilisation de 54 bits d'entropie :

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

Les chances de collision sont faibles même à 10 millions de requêtes par seconde.

Nginx garde une trace de l'horloge actuelle par incréments de la directive de configuration timer_resolution. La résolution de l'horloge pour $txid est de 1ms, donc une résolution de minuterie supérieure à 1ms signifie que la probabilité de collision augmentera. Si vous avez une timer_resolution de 10ms, 1 million de requêtes par seconde nécessiterait 10 000 valeurs aléatoires par seconde dans le pire des cas.

Encodage

base32hex est utilisé avec un alphabet en minuscules et sans caractères de remplissage pour les raisons suivantes :

  • Ordre de tri lexical équivalent à l'ordre de tri numérique.
  • Égalité insensible à la casse.
  • Les minuscules sont plus faciles pour les comparaisons visuelles.
  • Plus dense que l'encodage hexadécimal de 4 bytes.

Autres techniques

  • snowflake : Utilise time(41) + id unique(10) + séquence(12).
  • Avantage : Séquences uniques garanties.
  • Avantage : Tient dans 63 bits.
  • Inconvénient : Nécessite une coordination d'id unique pour chaque serveur - 16 processus de travail par hôte signifie une limite de 64 instances de nginx.
  • Inconvénient : Seulement 11 bits disponibles pour l'id unique, nécessite une surveillance.
  • Inconvénient : Ordonnancement total possible uniquement dans le même processus.
  • Inconvénient : Interruption de service possible lorsque les horloges perdent leur synchronisation.

  • flake : Utilise le temps + id mac + séquence.

  • Avantage : Séquences uniques garanties.
  • Inconvénient : Utilise 128 bits.
  • Inconvénient : Gaspille 22 bits de données d'horodatage.
  • Inconvénient : Un seul processus par hôte peut générer des identifiants - nécessite de synchroniser l'accès à la séquence depuis chaque processus de travail.
  • Inconvénient : Interruption de service possible lorsque les horloges perdent leur synchronisation.
  • Inconvénient : Semences de recherche d'adresse MAC multiplateforme.

  • UUIDv4 : 122 bits d'entropie.

  • Avantage : Probabilité de collision très faible.
  • Inconvénient : Non triable.

  • UUID avec horodatage : 48 bits de temps + 74 bits d'entropie.

  • Avantage : Probabilité de collision très faible.
  • Inconvénient : La représentation sous forme de chaîne n'est pas temporellement locale.

  • httpd mod_unique_id : ip hôte(32) + pid(32) + temps(32) + séquence (16) + id de thread (32).

  • Avantage : Déterministe.
  • Inconvénient : Utilise 144 bits.
  • Inconvénient : Suppose une IPv4 unique pour l'interface du nom d'hôte.
  • Inconvénient : Représentation personnalisée insensible à la casse non triable - base64 avec un alphabet personnalisé.
  • Inconvénient : Limite stricte de 65535 identifiants par seconde par pid - petite tolérance pour les pas d'horloge.

Développement

Après avoir cloné le dépôt, Docker peut être utilisé pour exécuter la suite de tests :

docker-compose run --rm app make test

Processus de publication

Pour publier des versions sur OPM et LuaRocks :

VERSION=x.x.x make release

GitHub

Vous pouvez trouver des conseils de configuration supplémentaires et de la documentation pour ce module dans le dépôt GitHub pour nginx-module-txid.