La API para aplicaciones
Latet protege lo que un agente envía a un modelo. Pero los programas también envían texto sensible a otros sitios: sus registros a Datadog, Splunk o Elastic, sus documentos al índice de una aplicación RAG. La API para aplicaciones permite a un programa pedir a un servidor de Latet, con el token de una clave de API, que:
- analice un texto (
scan): qué contiene de sensible, y dónde; - lo enmascare (
mask): el mismo texto con valores ficticios realistas en lugar de sus valores; - lo restaure (
unmask): los valores reales de nuevo en lugar de los ficticios; - olvide valores (
forget): los quite de la caja fuerte de la clave, cuando una persona pide que sus datos desaparezcan (olvidar valores).
Es una función del servidor de Latet: las claves de API y sus tokens solo existen allí, y un equipo
sin servidor no tiene nada de ella. Para probarla en tu propio equipo, arranca en él un servidor
Community con el perfil de servidor de compose.yaml (operación del servidor).
Lo que aporta frente a una regla de ocultación:
- El mismo valor ficticio cada vez. Un valor recibe el mismo valor ficticio en cada llamada, en cada instancia del servidor, mientras la caja fuerte de la clave lo conserve. En un servicio de registros, los gráficos y las búsquedas siguen funcionando, y se puede seguir a un cliente durante días de registros sin que nadie vea quién es. En un índice RAG, una persona sigue siendo la misma persona de un documento a otro.
- Una sola caja fuerte para todo el flujo. Los documentos y las preguntas enmascarados con la
misma clave comparten sus valores ficticios, así que un modelo razona sobre valores ficticios
coherentes, y
unmaskvuelve a poner los valores reales en su respuesta. - Reversible, bajo control. Restaurar es un permiso propio, solo alcanza los valores ficticios de la clave, y cada restauración a través de la API para aplicaciones queda escrita en el registro de administración del servidor.
Dos páginas recorren los dos usos para los que se hizo: los registros y el RAG. La
referencia HTTP da el contrato para programas en cualquier lenguaje, y la
descripción OpenAPI de la API permite generar un cliente a partir de
ella. Los programas en Python pueden usar el paquete
latet-client, que funciona con LangChain, y los flujos de
n8n los nodos de Latet para n8n.
Una primera llamada
Sección titulada «Una primera llamada»Un programa envía una lista de textos y recibe una lista, en el mismo orden:
printf 'Authorization: Bearer %s' "$(cat shop.key)" | curl -sS -H @- \ -H "Content-Type: application/json" \ -d '{"texts": ["Reembolsa el pedido de mario.rossi@acme.it a IT60X0542811101000000123456, por favor."]}' \ https://latet.acme.lan/_latet/apps/v1/mask{"results": [{"text": "Reembolsa el pedido de tommaso.gallo@margie-6e5a.example a IT17F3945471087320356678487, por favor.", "replacements": [{"type": "EMAIL", "class": "pii", "start": 23, "end": 56}, {"type": "IBAN", "class": "pii", "start": 59, "end": 86}], "counts": {"EMAIL": 1, "IBAN": 1}}]}shop.key contiene el primer token de la clave, el que escribe keys add
(más abajo).
printf, que la propia shell ejecuta, pasa la cabecera a curl por su entrada estándar (-H @-): así
el token no aparece en la línea de órdenes de curl, donde otros usuarios del equipo podrían leerlo.
Los valores ficticios son los de la clave: otra clave, u otro servidor, da otros. Con un token que
puede restaurar, el mismo texto enviado a /_latet/apps/v1/unmask vuelve como era, con
"restored": 2.
Claves de API y tokens
Sección titulada «Claves de API y tokens»Una clave de API es un espacio en el servidor: sus reglas, su caja fuerte de valores ficticios, su historial. Puede tener varios tokens, los secretos que actúan en ese espacio, y cada token tiene sus propios permisos:
| Permiso | Lo que puede hacer el token |
|---|---|
scan |
Decir qué contiene un texto y dónde; no cambia nada. |
mask |
Sustituir los valores de un texto por los valores ficticios de la clave. Un token que puede enmascarar también puede analizar. |
unmask |
Volver a poner los valores reales en lugar de los valores ficticios de la clave. |
forget |
Quitar valores de la caja fuerte de la clave (olvidar valores). Ningún otro permiso lo incluye. |
Da a cada programa solo lo que necesita. Una aplicación que envía sus registros necesita mask; la
herramienta de soporte que lee una línea copiada del servicio de registros necesita unmask; una
aplicación RAG que enmascara sus documentos y restaura la respuesta del modelo necesita los dos.
latet server --server https://latet.acme.lan keys add "Shop" --out shop.keylatet server --server https://latet.acme.lan keys add-token "Shop" shipper --mask --out shipper.tokenlatet server --server https://latet.acme.lan keys add-token "Shop" support --unmask --out support.tokenlatet server --server https://latet.acme.lan keys tokens "Shop"El primer token de una clave, el que crea keys add, tiene scan, mask y unmask, igual que el
token de cada clave creada antes de que los tokens tuvieran permisos: esas claves funcionan como antes.
El primer token de la clave tiene además una dirección para modelos, la
puerta de enlace, y a través de ella
sirve a los agentes de la clave: keys tokens lo señala con serves agents (has an address), para que
no se revoque en lugar de un token añadido con los mismos permisos. Un token creado con keys add-token no tiene dirección, tenga los
permisos que tenga, --mask --unmask incluidos: la puerta de enlace y el bridge del plugin lo
rechazan (403), y solo llama a la API para aplicaciones.
Cada token se detiene por separado: keys tokens "Shop" --revoke t_4kxq2m7zap revoca uno por el id
que muestra keys tokens, en pocos segundos en cada instancia. keys rotate "Shop" --token t_4kxq2m7zap --out shipper.token sustituye ese token por uno nuevo, con un secreto nuevo y un id
nuevo, el mismo nombre y los mismos permisos; una clave con un solo token no necesita --token. Cada
cambio queda en el registro de administración (token.added, token.revoked, key.rotated), con los
ids de los tokens, nunca un secreto.
La API para aplicaciones solo acepta tokens de claves de API: el token del equipo de una persona se rechaza (403), igual que una solicitud que hace una página web. Las personas usan su panel.
Prosa y datos
Sección titulada «Prosa y datos»scan y mask aceptan un kind para los textos de una llamada:
prose(el predeterminado): texto escrito por personas, como mensajes, tickets y documentos. Se enmascara como la puerta de enlace enmascara tus propios mensajes.data: registros, fichas, la salida de un programa. Se enmascaran como la puerta de enlace enmascara la salida de una herramienta.
La diferencia está en lo que es probable: en el perfil balanced, los nombres solo se buscan en la
prosa (con el modelo de nombres local
instalado en el servidor), y una serie de dígitos solo cuenta como número de teléfono en la prosa,
mientras que en un registro es mucho más a menudo un id. Con phone_regions: [IT] en las reglas de la
clave, el número en Llámame mañana al 3471234567. recibe un valor ficticio como prosa y se queda
como está como datos. Un documento JSON enviado como texto se enmascara como texto, conservando su
formato, así que las posiciones de la respuesta son posiciones en él.
Qué lee un enmascaramiento
Sección titulada «Qué lee un enmascaramiento»scan y mask usan los ajustes de la clave tal como están cuando llega la llamada: las reglas
propias de la clave sobre los ajustes compartidos del servidor, como hace la puerta de enlace con las
solicitudes de la clave. No hay reglas solo para la API. Se configuran con latet server config --key
y latet server rules --key
(ajustes compartidos y reglas de las claves de API).
- El revisor local, cuando las reglas de la clave indican uno, lee cada texto de una llamada
maskantes de la respuesta, diga lo que digallm_review.inlinepara la puerta de enlace: una aplicación no tiene un momento posterior. Dispone dellm_review.budget_mssi está configurado, y si no de 30 segundos. Un texto cuya revisión falla siguellm_review.on_failure: constop(el predeterminado) la respuesta esblocked, consendes el texto tal como lo enmascararon las reglas. Así que, con un revisor caído, cada texto se bloquea hasta que vuelva a responder. scanno ejecuta el revisor local. El revisor enseña a la caja fuerte lo que encuentra, y el análisis no cambia nada. En una clave con revisor,scanpuede informar de menos de lo quemasksustituye.- El Revisor en directo nunca retiene el texto de una aplicación. Una aplicación no puede esperar a una persona: un texto se enmascara solo con las reglas y el revisor local, aunque las reglas de la clave activen el Revisor en directo.
- La protección contra volcados se aplica a los textos
datacomo a la salida de una herramienta: uno que contiene más valores personales de los que permitedump_guardrecibeblocked.
Un texto bloqueado vuelve como {"blocked": "<motivo>"} sin texto, y el motivo no nombra ningún
valor. Cuando algo funcionó de forma reducida en la llamada (un detector que no pudo leer un texto,
una revisión que no terminó), la respuesta lo dice en "degraded", con los nombres de lo afectado,
como ner o llm-review.
Enmascarado dos veces, restaurado dos veces
Sección titulada «Enmascarado dos veces, restaurado dos veces»mask toma por real cada valor que encuentra, también los propios valores ficticios de la clave:
nadie puede saber por una cadena si es un valor ficticio o un valor real que se le parece. Un texto
enmascarado dos veces contiene valores ficticios de valores ficticios, y una restauración lo devuelve
un paso atrás:
| Paso | Texto |
|---|---|
| Original | Escribe a mario.rossi@acme.it |
| Enmascarado | Escribe a tommaso.gallo@margie-6e5a.example |
| Enmascarado de nuevo | Escribe a simone.desantis@humongous-0e54.example |
| Restaurado una vez | Escribe a tommaso.gallo@margie-6e5a.example |
Enmascara un texto una sola vez, a partir de su original. La puerta de enlace sigue la misma regla: un texto enmascarado con la API y luego enviado a un modelo a través de la puerta de enlace de Latet se vuelve a enmascarar allí, y la respuesta del modelo vuelve con los valores ficticios de la API. Por eso una aplicación RAG envía el texto enmascarado directamente al modelo y restaura la respuesta con la API (RAG).
Quién puede saber qué
Sección titulada «Quién puede saber qué»Restaurar da los valores reales de los valores ficticios de la clave, sea cual sea la llamada o la
conversación de la clave que los creó, también los del año pasado. No alcanza los valores ficticios de
otra clave ni los de una persona: el texto que no es un valor ficticio de la clave se queda como está.
El nombre de una persona solo vuelve entero: una parte suya sola (un nombre de pila) puede ser una
palabra corriente, o el nombre de otra persona, y se queda como está. Cada restauración se escribe en el
registro de administración antes de enviar su respuesta (apps.unmasked, con el token, cuántos textos y
cuántos valores; nunca un texto ni un valor), y un registro que no puede escribirla rechaza la llamada
(503). El primer token de la clave, que puede analizar, enmascarar y restaurar, también sirve a agentes
a través de la puerta de enlace: sus respuestas se restauran como las de cualquier agente, sin una
entrada del registro de administración por cada restauración. Un token creado con keys add-token
nunca lo hace, así que cada una de sus restauraciones es una entrada apps.unmasked. Da unmask a las
herramientas de las
personas que pueden ver los datos reales, y lee el registro con
latet server log --action apps.unmasked.
Enmascarar también revela algo. Con un token que puede enmascarar y acceso a los registros
enmascarados, alguien puede confirmar una sospecha: enmascarar mario.rossi@acme.it y buscar su valor
ficticio en los registros. Si está, Mario Rossi es cliente. Por eso da mask solo a los programas que
lo necesitan, guarda el token donde guardan sus secretos, y trata los registros enmascarados como
datos seudonimizados, no anónimos.
Analizar también revela algo. No crea valores ficticios ni cambia nada, pero informa de los
valores que la caja fuerte de la clave ya conoce, dondequiera que aparezcan. Un token que solo puede
analizar puede, por tanto, confirmar que un valor que ningún detector encontraría por sí solo (un
nombre en un texto data, un valor que una regla de la clave encontró antes de que la quitaran) es
uno de los de la clave. Da scan con el mismo cuidado que mask.
Olvidar también revela algo. Su respuesta dice cuántos de los valores guardaba la caja fuerte de la
clave, así que un token que puede olvidar puede confirmar que un valor es de la clave, como uno que
puede analizar. Da forget solo al proceso que atiende las peticiones de borrado; cada olvido queda en
el registro de administración (apps.forgotten, con el token y las cifras, nunca un valor). Ninguno
de los tokens que crea keys add puede olvidar: forget se da a un token propio
(keys add-token "Shop" erasure --forget).
La caja fuerte de una clave
Sección titulada «La caja fuerte de una clave»La caja fuerte de una clave conserva cada valor que ha enmascarado, con su valor ficticio, mientras la
clave exista: una restauración funciona con una línea de registro del año pasado, y un cliente conserva
el mismo valor ficticio durante años. Los valores no caducan: un valor sale de la caja fuerte cuando se
olvida (olvidar valores), y todos se van con la clave, ya que latet server keys remove
borra su espacio, y con él su caja fuerte. Un valor encontrado una vez se enmascara desde entonces
dondequiera que aparezca, incluso donde ningún detector lo volvería a encontrar: la dirección de un
cliente que una regla de la clave encontró una vez, y que luego se quitó, sigue enmascarada.
Un espacio de registros puede contener millones de valores, así que una instancia no guarda en
memoria la caja fuerte de una clave. Guarda los valores que la clave usó por última vez
(key_working_values, 5.000 por defecto, unos 10 MiB por clave) y las huellas de toda la caja
fuerte: una huella de 64 bits de cada palabra de sus valores y de sus valores ficticios, calculada
con una clave secreta, unos 15 MB por un millón de valores. Las palabras de una llamada se buscan
primero entre las huellas: una palabra sin huella no es de ningún valor ni de ningún valor ficticio,
y a PostgreSQL no se le pregunta por ella. Los valores y los valores ficticios cuyas palabras tienen
una se leen de PostgreSQL, todos los de la llamada de una vez, y cada uno se descifra y se compara
con el texto antes de enmascarar o restaurar nada, porque dos palabras pueden tener la misma huella.
Ni las huellas ni el índice de las palabras en PostgreSQL contienen una palabra en claro. Antes de
cada llamada la instancia lee en PostgreSQL el contador de cambios de la clave y recoge lo que las
otras instancias guardaron u olvidaron desde entonces, así que un valor guardado en una instancia
antes de que empiece una llamada lo encuentra esa llamada en cualquier otra, aunque el mensaje que lo
anunciaba se haya perdido. Una clave con pocos valores funciona igual: solo la caja fuerte de una
persona de 35.000 valores o menos se guarda entera en memoria
(operación del servidor).
Secretos pegados a otras letras. En PostgreSQL, un secreto escrito como una sola serie de letras y dígitos,
sin espacios ni puntuación, se encuentra por su palabra, y por sus seis primeros y seis últimos caracteres
cuando otras letras o dígitos están pegados a él: por un lado, en el borde de la serie del texto que lo
contiene, y por ambos lados (xhunter2y), dentro de esa serie, que una llamada lee de seis en seis caracteres.
Una llamada lee como mucho un millón de esas posiciones: por encima, sus series siguientes se leen solo por sus
palabras y sus bordes. Un secreto de seis caracteres o menos, o cuya palabra en un borde pegado tiene seis
caracteres o menos, no lo encuentra ninguna palabra del texto cuando está pegado. Por eso una instancia guarda
en memoria para siempre los secretos cortos de una clave, los de una sola serie de 16 caracteres o menos o con
una palabra así de corta en un borde: los key_held_secrets más recientes (1.000 si los ajustes del servidor no
dicen otra cosa), que se encuentran estén pegados como estén, también por encima del millón de posiciones de una
llamada. Uno más antiguo, o uno más largo, se encuentra donde nada está pegado a él, pegado por un lado cuando
la palabra de ese borde tiene siete caracteres o más, y pegado por ambos lados cuando es una sola serie de siete
caracteres o más, dentro del millón de posiciones de la llamada. Sube key_held_secrets para una clave que ve
muchos secretos cortos, como un token de sesión en cada línea de registro: cada uno que se guarda ocupa unos 23
KB en cada instancia que sirve a la clave, la mayor parte en el detector que lo encuentra, y cada texto que la
clave enmascara o restaura se recorre para buscarlo, lo que hace sus llamadas un poco más lentas. Medida con
Latet 0.10.0 en el equipo de velocidad, con un token de sesión propio en cada línea (un valor más
que guardar en cada una), una clave nueva con 1.000 de esos secretos guardados enmascaró 73 líneas por segundo y
restauró 1.935, frente a 75 y 2.103 sin ninguno guardado. Un valor ficticio se encuentra pegado como su secreto:
conserva los signos del secreto en su sitio, así que un valor ficticio pegado de modo que ninguna de sus
palabras quede suelta es el de un secreto guardado en memoria, y se encuentra mientras el secreto se guarda.
Como en todo Latet, un valor se vuelve a reconocer a partir de cuatro caracteres, el nombre de una persona a
partir de tres (valores conocidos).
Secretos cortos. Un secreto de menos de doce caracteres (password=1234, un token corto) recibe
un valor ficticio más largo de los mismos tipos, con sus signos en su sitio: quince dígitos para un
número (con su signo y su punto decimal, para que un número JSON siga siendo un número), doce
dígitos hexadecimales para un valor hexadecimal, si no doce letras y dígitos. Un valor ficticio tan
corto como su valor aparecería por casualidad en los identificadores y los números de otras líneas
de registro, y una restauración cambiaría líneas que nunca contuvieron el secreto.
Un valor ficticio corto que la caja fuerte de una clave dio antes de Latet 0.10.2 (de ocho
caracteres o menos) se queda, como todo valor ficticio. unmask, que lee cada valor ficticio de la
caja fuerte de la clave, solo lo sustituye por el valor donde está como palabra completa, sin letra,
dígito ni guion bajo al lado. El coste: donde el valor de un secreto así se enmascaró dentro de una
palabra o un número más largo (postgres en postgresql://, 1234 en order_id=9912343),
unmask deja ahí su valor ficticio. Una conversación del primer token de la clave, a través del
gateway o del plugin, lo restaura como antes, también dentro de una palabra, porque solo restaura
los valores ficticios que se le mostraron. Mientras una instancia del servidor siga ejecutando la
0.10.0, da valores ficticios cortos a los nuevos secretos cortos, y también esos se quedan:
actualiza cada instancia.
Qué conserva una llamada que falla. Una llamada mask que falla mientras enmascara (un 507, una
confirmación que no llega a PostgreSQL) no conserva ninguno de sus valores nuevos cuando ocupaba para
ellos una de las transacciones de la instancia, tres a la vez con el database_pool predeterminado: el
espacio de la clave los olvida. Si esas estaban ocupadas, la llamada conservó cada valor nuevo sobre la
marcha, y esos se quedan en la caja fuerte de la clave. Un fallo después del enmascaramiento, en la revisión local o al preparar la
respuesta, llega después de la confirmación, así que los valores nuevos de la llamada también se
quedan. Volver a enmascarar los textos es seguro en cualquier caso: un valor que la caja fuerte
conservó tiene su valor ficticio, y uno que olvidó recibe otra vez uno. Un agente de la misma clave
que usó mientras tanto un valor olvidado a través de la puerta de enlace ya envió su valor ficticio,
que ninguna restauración posterior devolverá.
Límites
Sección titulada «Límites»- La caja fuerte. La caja fuerte de una clave admite los
vault_limitvalores del servidor (200.000 si los ajustes del servidor no dicen otra cosa), o un límite propio:latet server keys limit "Shop" 5000000(de 1.000 a 100.000.000;defaultle devuelve el del servidor). Una caja fuerte llena rechaza con 507 la llamadamaskque añadiría un valor: no vuelve nada de la llamada, y nada sale en claro. El estado del servidor lista las identidades al 90 % de su límite, y la métricalatet_vault_near_limitlas cuenta, así que sube un límite antes de alcanzarlo. Cada instancia cuenta los valores nuevos de una llamada antes de confirmarlos: dos instancias, o una llamada y un agente de la misma clave, pueden superar el límite en los valores nuevos de una llamada cada uno antes de que se rechace el siguiente valor. Un índice de las huellas de una caja fuerte se construye hasta 16.777.216 huellas, unos ocho millones de valores del tipo que mide velocidad: por encima, no se construye ningún índice mientras dura el espacio de la clave, y sus llamadas buscan sus palabras solo en PostgreSQL, más despacio. - Una llamada admite como mucho 1.000 textos y el
apps_request_mbdel servidor (8 MiB si sus ajustes no dicen otra cosa, nunca más que sumax_request_mb): una llamada espera en memoria al hilo de la clave, ylatet-clientdivide una lista más larga en llamadas de 8 MiB como mucho. Los textos cuyas palabras nombran más de 100.000 valores o valores ficticios de la caja fuerte se rechazan enteros con 422, con palabras fijas que no nombran ningún valor: no se enmascara ni se restaura nada de la llamada, así que envía menos textos por llamada. Un 503 es otra cosa: un almacén del servidor (PostgreSQL, Valkey) no respondió, y la misma llamada puede volver a enviarse más tarde (referencia HTTP). - Llamadas simultáneas. Una clave tiene como mucho
requests_in_flightsolicitudes de aplicaciones en una instancia (16 si los ajustes no dicen otra cosa); por encima, 429 conRetry-After.
Velocidad
Sección titulada «Velocidad»Las llamadas de una clave se ejecutan una tras otra en una instancia, en el hilo de su espacio; las llamadas de claves distintas, y las que llegan a instancias distintas, se ejecutan a la vez. Para ir más rápido con una clave, pon más instancias detrás del equilibrador de carga, o varios workers en una máquina Linux (varias instancias en una máquina).
Medido con Latet 0.11 en una instancia (Intel i9-10850K, 64 GB, Windows 10, PostgreSQL 18 en Docker
Desktop en el mismo equipo, los ajustes por defecto del servidor): 40.000 líneas de registro de unos
200 caracteres, cada una con una dirección de correo y una dirección IPv4 pública, la mitad de las
cuales nombra a un cliente ya visto, en llamadas de 500 como data; después 50 documentos de unos
20 KB de prosa con diez valores cada uno, uno por llamada. Las líneas por segundo son las líneas
divididas por el tiempo que tardaron sus llamadas; el tiempo de un documento es la mediana de sus 50
llamadas:
| La caja fuerte de la clave | Enmascarar, líneas por segundo | Restaurar, líneas por segundo | Un documento, enmascarar / restaurar |
|---|---|---|---|
| Nueva, aprende sus valores sobre la marcha | 191 | 2.894 | 126 / 31 ms |
| 200.000 valores | 871 | 2.960 | 117 / 32 ms |
| 1.000.000 de valores | 773 | 2.967 | 124 / 32 ms |
Cada documento trae valores nuevos para la clave, que los escribe en PostgreSQL mientras los enmascara: los mismos 50 documentos enmascarados de nuevo, con sus valores ya conocidos, tardaron 16,6 ms con 200.000 valores y 16,4 ms con 1.000.000 (restaurar, 23 ms en ambos casos). Una clave nueva también escribe la mayoría de los valores de sus líneas, y por eso enmascara menos.
Latet 0.10.0, medido de la misma manera en el mismo equipo, enmascaraba 350 y 304 líneas por segundo con 200.000 y 1.000.000 de valores y 153 para una clave nueva, restauraba 2.114 y 2.014, y enmascaraba un documento en entre 163 y 182 ms. Latet 0.11 se proponía enmascarar al menos 1.000 líneas como estas por segundo en una instancia con 200.000 y con 1.000.000 de valores, y un documento en 30 ms o menos: enmascara 871 y 773, y un documento en 30 ms solo cuando la clave ya conoce sus valores. La mayor parte del tiempo de una llamada de enmascarar se va en los detectores, y una parte en esperar a PostgreSQL.
Una instancia encuentra los valores de una clave mediante las huellas de las palabras de su caja fuerte (la caja fuerte de una clave), que ocupan unos 15 MB por un millón de valores (14,77 MB con 1.000.000) y se construyen en segundo plano mientras se abre el espacio de la clave: en 1,5 segundos con 1.000.000 de valores (la mediana de tres aperturas, de 1,42 a 1,62 s). Hasta que están listas, las llamadas de la clave buscan sus palabras solo en PostgreSQL: en una medición anterior con Latet 0.11, con las huellas desactivadas, la clave de 200.000 valores enmascaró 494 líneas por segundo y restauró 1.382. Cada llamada lee primero el contador de cambios de la clave, 0,51 ms de mediana, y recoge lo que cambió desde entonces: 1,87 ms por un valor nuevo y 2,11 ms por 100, también de mediana.
En una llamada de textos enmascarados antes, solo los nuevos se vuelven a leer, pero cada uno de los demás se sigue buscando y comparando con lo que la clave enmascaró: 500 textos enmascarados antes y uno nuevo tardaron 168 ms, el nuevo solo 6,1 ms (medianas de tres ejecuciones).
En una máquina Linux, los workers se suman. Medido en Docker en el mismo equipo (una máquina virtual
Linux de 20 procesadores y 16 GB, con PostgreSQL 18.6 y Valkey 9.1.2 en contenedores al lado), con
latet server run --workers y una clave de API de 200.000 valores: 200.000 líneas distintas del tipo
descrito arriba en llamadas de 62, cuatro conexiones por worker, cada número de workers durante 20
segundos, la mediana de tres rondas; la memoria es la memoria residente de cada worker tras las
rondas:
| Workers | Enmascarar, líneas por segundo | Restaurar, líneas por segundo | Memoria por worker |
|---|---|---|---|
| 1 | 1.190 | 1.947 | 227 MB |
| 2 | 2.260 | 3.711 | 227 MB |
| 4 | 4.253 | 6.594 | 216 MB |
| 8 | 7.477 | 10.147 | 185 MB |
Ocho workers enmascararon 6,28 veces lo que uno y restauraron 5,21 veces, donde Latet 0.11 se proponía llegar al menos a 5 veces en ambos; ninguna ronda con ocho workers restauró a menos de 5,07 veces. El kernel reparte las conexiones entre los workers según un hash, no por turnos, así que unos workers reciben más que otros: de 1 a 10 de las 32 en una ronda con ocho workers. El proceso que arranca los workers ocupaba de 47 a 48 MB a su lado. Estas llamadas llegan desde varias conexiones a la vez, así que compara las filas de esta tabla entre sí, no con la primera tabla.
Escribir valores nuevos necesita conexiones a PostgreSQL: una instancia, y cada worker, mantiene como
mucho database_pool (10 si los ajustes del servidor no dicen otra cosa) para el trabajo de todas
las identidades, y deja que tres décimas partes de ellas (tres de diez) las ocupen llamadas que
conservan sus valores nuevos en una transacción cada una; las demás conservan cada valor según llega,
más despacio. Sube database_pool para una instancia que enmascara para muchas claves nuevas a la
vez, y con él el max_connections de PostgreSQL
(operación del servidor).
scripts/bench_apps.py, en el repositorio, mide un servidor tuyo como se midió la primera tabla.
Actualizar un servidor
Sección titulada «Actualizar un servidor»Latet 0.11 cambia las tablas. Detén cada instancia y cada worker, en cada máquina, ejecuta
latet server migrate una vez con la 0.11 y luego arráncalos todos con ella
(actualizar). Una instancia no
arranca sobre tablas de otra versión, así que entre la parada y el arranque no se responde ninguna
llamada, y latet server migrate no cambia nada mientras una instancia está viva, una de la 0.10
durante 30 segundos después de detenerse. La migración añade a cada fila de palabra de cada caja
fuerte un sitio vacío para su huella y para el número del cambio que la escribió por última vez, con
sus índices: en una base de datos con cajas fuertes grandes tarda más que las migraciones anteriores,
y las huellas mismas se calculan después, en segundo plano.
Una caja fuerte guardada por Latet 0.10 aún no tiene huellas. Tras el reinicio, la primera instancia que construye el espacio de una clave responde enseguida a las llamadas de la clave, buscando sus palabras en PostgreSQL como hacía la 0.10, y calcula las huellas de la caja fuerte en segundo plano, una vez, una instancia cada vez; después cada instancia construye su propio índice de esas huellas y lo usa. Hasta entonces las llamadas de la clave enmascaran los mismos valores, más despacio (velocidad: 494 líneas por segundo con 200.000 valores y las huellas desactivadas, en una medición anterior), y ninguna se rechaza por ello. Mientras una instancia calcula las huellas de una caja fuerte, las demás claves y personas cuyo espacio abre entretanto pueden servirse también con la búsqueda en PostgreSQL, hasta que su índice esté construido. Cuando las huellas aún no están todas calculadas o no puede construirse un índice (PostgreSQL o Valkey que no responden, una actualización del índice que falló), las llamadas siguen con la búsqueda en PostgreSQL hasta que el índice se construye de nuevo: enseguida cuando otra instancia ha calculado las huellas, y si no 10, 20 y 30 segundos después de cada intento y luego una vez por minuto. El registro dice una vez cuando un índice deja de usarse y una vez cuando vuelve a usarse. Una caja fuerte por encima del tamaño para el que se construye un índice (límites) sigue con la búsqueda en PostgreSQL mientras dura su espacio. Las palabras mismas quedan como las hizo la 0.10: la 0.11 no las vuelve a hacer.
Una versión que cambia cómo se hacen las palabras de una caja fuerte (el registro de cambios lo dice) vuelve a hacer las palabras de una clave en la primera instancia que construye su espacio, antes de que ese espacio responda a la primera llamada de la clave: Latet 0.10.0 tardaba unos 40 segundos para 200.000 valores y cuatro minutos y medio para 1.000.000 en el equipo indicado arriba. Una llamada que llega mientras tanto espera, y una instancia construye cuatro espacios a la vez, así que unas pocas claves grandes retrasan las primeras llamadas de las demás claves en ella: tras una versión así, envía a cada clave grande una llamada, con un tiempo de espera suficiente, antes de que sus programas vuelvan a arrancar.
Mientras instancias de dos versiones se ejecutan a la vez, lo que permite una versión que no cambia las tablas, un valor que escribe una instancia más antigua lo encuentran las más nuevas mientras lo tienen en memoria, pero no en PostgreSQL hasta que el espacio de la clave se vuelve a construir. Cuando la API para aplicaciones está en uso, detén cada instancia más antigua antes de que arranquen las nuevas, aunque las tablas no cambien.
© 2026 Emanuele Casabona · NIF-IVA IT02293040990 · Política de privacidad · Aviso legal · latet.ai