Zum Inhalt springen

Die API für Anwendungen

Latet schützt, was ein Agent an ein Modell sendet. Programme senden vertraulichen Text aber auch an andere Stellen: ihre Logs an Datadog, Splunk oder Elastic, ihre Dokumente an den Index einer RAG-Anwendung. Mit der API für Anwendungen bittet ein Programm einen Latet-Server, mit dem Token eines API-Schlüssels, einen Text:

  • zu analysieren (scan): was darin vertraulich ist, und wo;
  • zu maskieren (mask): derselbe Text mit realistischen Ersatzwerten anstelle seiner Werte;
  • wiederherzustellen (unmask): die echten Werte wieder anstelle der Ersatzwerte;
  • Werte zu vergessen (forget): sie aus dem Tresor des Schlüssels zu entfernen, wenn eine Person verlangt, dass ihre Daten verschwinden (Werte vergessen).

Sie ist eine Funktion des Latet-Servers: API-Schlüssel und ihre Token gibt es nur dort, und ein Rechner ohne Server hat nichts davon. Um sie auf Ihrem eigenen Rechner auszuprobieren, starten Sie dort einen Community-Server mit dem Serverprofil von compose.yaml (Serverbetrieb).

Was sie mehr bietet als eine Schwärzungsregel:

  • Jedes Mal derselbe Ersatzwert. Ein Wert erhält in jedem Aufruf, auf jeder Instanz des Servers denselben Ersatzwert, solange der Tresor des Schlüssels ihn behält. In einem Log-Dienst funktionieren Diagramme und Suchen weiter, und ein Kunde lässt sich über Tage von Logs verfolgen, ohne dass jemand sieht, wer er ist. In einem RAG-Index bleibt eine Person über alle Dokumente dieselbe Person.
  • Ein Tresor für den ganzen Ablauf. Dokumente und Fragen, die mit demselben Schlüssel maskiert werden, teilen ihre Ersatzwerte. So arbeitet ein Modell mit stimmigen Ersatzwerten, und unmask setzt die echten Werte in seine Antwort wieder ein.
  • Umkehrbar, unter Kontrolle. Wiederherstellen ist eine eigene Berechtigung, erreicht nur die Ersatzwerte des Schlüssels, und jede Wiederherstellung über die API für Anwendungen wird im Administrationsprotokoll des Servers festgehalten.

Zwei Seiten führen durch die beiden Einsätze, für die sie gebaut wurde: Logs und RAG. Die HTTP-Referenz beschreibt den Vertrag für Programme in jeder Sprache, und mit der OpenAPI-Beschreibung der API lässt sich ein Client dafür erzeugen. Python-Programme können das Paket latet-client verwenden, das mit LangChain zusammenarbeitet, und n8n-Workflows die Latet-Nodes für n8n.

Ein Programm sendet eine Liste von Texten und erhält eine Liste zurück, in derselben Reihenfolge:

Terminal-Fenster
printf 'Authorization: Bearer %s' "$(cat shop.key)" | curl -sS -H @- \
-H "Content-Type: application/json" \
-d '{"texts": ["Bitte erstatten Sie die Bestellung von mario.rossi@acme.it auf IT60X0542811101000000123456."]}' \
https://latet.acme.lan/_latet/apps/v1/mask
{"results": [{"text": "Bitte erstatten Sie die Bestellung von tommaso.gallo@margie-6e5a.example auf IT17F3945471087320356678487.",
"replacements": [{"type": "EMAIL", "class": "pii", "start": 39, "end": 72},
{"type": "IBAN", "class": "pii", "start": 77, "end": 104}],
"counts": {"EMAIL": 1, "IBAN": 1}}]}

shop.key enthält den ersten Token des Schlüssels, den keys add schreibt (weiter unten). printf, das die Shell selbst ausführt, übergibt den Header über die Standardeingabe an curl (-H @-). So steht der Token nicht in der Befehlszeile von curl, wo andere Benutzer des Rechners ihn lesen könnten. Die Ersatzwerte sind die des Schlüssels: Ein anderer Schlüssel oder ein anderer Server liefert andere. Mit einem Token, der wiederherstellen darf, kommt derselbe Text, an /_latet/apps/v1/unmask gesendet, so zurück, wie er war, mit "restored": 2.

Ein API-Schlüssel ist ein Bereich auf dem Server: seine Regeln, sein Tresor mit Ersatzwerten, sein Verlauf. Er kann mehrere Token haben, die Geheimnisse, die in diesem Bereich handeln, und jeder Token hat seine eigenen Berechtigungen:

Berechtigung Was der Token darf
scan Sagen, was ein Text enthält und wo; er ändert nichts.
mask Die Werte eines Textes durch die Ersatzwerte des Schlüssels ersetzen. Ein Token, der maskieren darf, darf auch analysieren.
unmask Die echten Werte wieder anstelle der Ersatzwerte des Schlüssels einsetzen.
forget Werte aus dem Tresor des Schlüssels entfernen (Werte vergessen). Keine andere Berechtigung schließt sie ein.

Geben Sie jedem Programm nur, was es braucht. Eine Anwendung, die ihre Logs versendet, braucht mask; das Support-Werkzeug, das eine aus dem Log-Dienst kopierte Zeile liest, braucht unmask; eine RAG-Anwendung, die ihre Dokumente maskiert und die Antwort des Modells wiederherstellt, braucht beides.

Terminal-Fenster
latet server --server https://latet.acme.lan keys add "Shop" --out shop.key
latet server --server https://latet.acme.lan keys add-token "Shop" shipper --mask --out shipper.token
latet server --server https://latet.acme.lan keys add-token "Shop" support --unmask --out support.token
latet server --server https://latet.acme.lan keys tokens "Shop"

Der erste Token eines Schlüssels, den keys add erzeugt, hat scan, mask und unmask, ebenso der Token jedes Schlüssels, der angelegt wurde, bevor Token Berechtigungen hatten: Diese Schlüssel funktionieren wie bisher. Der erste Token des Schlüssels hat außerdem eine Adresse für Modelle, das Gateway, und bedient darüber die Agenten des Schlüssels: keys tokens kennzeichnet ihn mit serves agents (has an address), damit er nicht anstelle eines Tokens widerrufen wird, der mit denselben Berechtigungen hinzugefügt wurde. Ein Token, der mit keys add-token erstellt wurde, hat keine Adresse, welche Berechtigungen er auch hat, --mask --unmask eingeschlossen: Das Gateway und die Bridge des Plugins weisen ihn ab (403), und er ruft nur die API für Anwendungen auf.

Jeder Token lässt sich einzeln stoppen: keys tokens "Shop" --revoke t_4kxq2m7zap widerruft einen anhand der ID, die keys tokens auflistet, innerhalb von Sekunden auf jeder Instanz. keys rotate "Shop" --token t_4kxq2m7zap --out shipper.token ersetzt diesen Token durch einen neuen, mit neuem Geheimnis und neuer ID, demselben Namen und denselben Berechtigungen; ein Schlüssel mit nur einem Token braucht kein --token. Jede Änderung steht im Administrationsprotokoll (token.added, token.revoked, key.rotated), mit den IDs der Token, nie mit einem Geheimnis.

Die API für Anwendungen nimmt nur Token von API-Schlüsseln an: Der Rechner-Token einer Person wird abgewiesen (403), ebenso eine Anfrage, die eine Webseite stellt. Personen verwenden ihr Dashboard.

scan und mask nehmen eine Art (kind) für die Texte eines Aufrufs:

  • prose (die Voreinstellung): Text, den Menschen geschrieben haben, etwa Nachrichten, Tickets und Dokumente. Er wird maskiert, wie das Gateway Ihre eigenen Nachrichten maskiert.
  • data: Logs, Datensätze, die Ausgabe eines Programms. Sie werden maskiert, wie das Gateway die Ausgabe eines Werkzeugs maskiert.

Der Unterschied liegt in dem, was wahrscheinlich ist: Im Profil balanced werden Namen nur in Prosa gesucht (mit dem auf dem Server installierten lokalen Namensmodell), und eine Ziffernfolge zählt nur in Prosa als Telefonnummer, während sie in einem Log weit öfter eine ID ist. Mit phone_regions: [IT] in den Regeln des Schlüssels erhält die Nummer in Rufen Sie mich morgen unter 3471234567 an. als Prosa einen Ersatzwert und bleibt als Daten, wie sie ist. Ein JSON-Dokument, das als Text gesendet wird, wird als Text maskiert und behält sein Layout, sodass die Positionen in der Antwort Positionen darin sind.

scan und mask verwenden die Einstellungen des Schlüssels, wie sie beim Eintreffen des Aufrufs sind: die eigenen Regeln des Schlüssels über den gemeinsamen Einstellungen des Servers, wie es das Gateway für die Anfragen des Schlüssels tut. Es gibt keine Regeln nur für die API. Sie werden mit latet server config --key und latet server rules --key gesetzt (gemeinsame Einstellungen und Regeln von API-Schlüsseln).

  • Der lokale Prüfer liest, wenn die Regeln des Schlüssels einen festlegen, jeden Text eines mask-Aufrufs vor der Antwort, was auch immer llm_review.inline für das Gateway sagt: Eine Anwendung hat keinen späteren Zeitpunkt. Er erhält llm_review.budget_ms, wenn gesetzt, sonst 30 Sekunden. Ein Text, dessen Prüfung scheitert, folgt llm_review.on_failure: Mit stop (der Voreinstellung) lautet die Antwort blocked, mit send kommt der Text so, wie ihn die Regeln maskiert haben. Bei einem ausgefallenen Prüfer wird also jeder Text blockiert, bis er wieder antwortet.
  • scan führt den lokalen Prüfer nicht aus. Der Prüfer bringt dem Tresor bei, was er findet, und eine Analyse mit scan ändert nichts. Bei einem Schlüssel mit Prüfer kann scan weniger melden, als mask ersetzt.
  • Der Live-Prüfer hält den Text einer Anwendung nie an. Eine Anwendung kann nicht auf einen Menschen warten: Ein Text wird allein von den Regeln und dem lokalen Prüfer maskiert, auch wenn die Regeln des Schlüssels den Live-Prüfer einschalten.
  • Der Dump-Schutz gilt für data-Texte wie für die Ausgabe eines Werkzeugs: Einer, der mehr personenbezogene Werte enthält, als dump_guard erlaubt, erhält blocked.

Ein blockierter Text kommt als {"blocked": "<Grund>"} ohne Text zurück, und der Grund nennt keinen Wert. Wenn im Aufruf etwas eingeschränkt lief (ein Detektor, der einen Text nicht lesen konnte, eine Prüfung, die nicht fertig wurde), sagt die Antwort das in "degraded", mit den Namen dessen, was es betraf, etwa ner oder llm-review.

mask hält jeden Wert, den es findet, für echt, auch die eigenen Ersatzwerte des Schlüssels: Niemand kann einer Zeichenkette ansehen, ob sie ein Ersatzwert ist oder ein echter Wert, der wie einer aussieht. Ein zweimal maskierter Text enthält Ersatzwerte von Ersatzwerten, und eine Wiederherstellung führt ihn um einen Schritt zurück:

Schritt Text
Original Schreiben Sie an mario.rossi@acme.it
Maskiert Schreiben Sie an tommaso.gallo@margie-6e5a.example
Erneut maskiert Schreiben Sie an simone.desantis@humongous-0e54.example
Einmal wiederhergestellt Schreiben Sie an tommaso.gallo@margie-6e5a.example

Maskieren Sie einen Text einmal, ausgehend vom Original. Das Gateway folgt derselben Regel: Ein Text, der über die API maskiert und dann über das Gateway von Latet an ein Modell gesendet wird, wird dort erneut maskiert, und die Antwort des Modells kommt mit den Ersatzwerten der API zurück. Eine RAG-Anwendung sendet den maskierten Text deshalb direkt an das Modell und stellt die Antwort über die API wieder her (RAG).

Wiederherstellen liefert die echten Werte der Ersatzwerte des Schlüssels, gleich welcher Aufruf oder welche Unterhaltung des Schlüssels sie erzeugt hat, auch die vom letzten Jahr. Es erreicht keine Ersatzwerte eines anderen Schlüssels und keine einer Person: Text, der kein Ersatzwert des Schlüssels ist, bleibt, wie er ist. Der Name einer Person kommt nur als Ganzes zurück: Ein Teil davon allein (ein Vorname) kann ein gewöhnliches Wort oder der Name einer anderen Person sein und bleibt, wie er ist. Jede Wiederherstellung wird im Administrationsprotokoll festgehalten, bevor ihre Antwort gesendet wird (apps.unmasked, mit dem Token, wie viele Texte und wie viele Werte; nie ein Text oder ein Wert), und ein Protokoll, das sie nicht aufnehmen kann, weist den Aufruf ab (503). Der erste Token des Schlüssels, der analysieren, maskieren und wiederherstellen darf, bedient auch Agenten über das Gateway: Ihre Antworten werden wie die jedes Agenten wiederhergestellt, ohne einen Eintrag im Administrationsprotokoll für jede Wiederherstellung. Ein Token, der mit keys add-token erstellt wurde, tut das nie, daher ist jede seiner Wiederherstellungen ein Eintrag apps.unmasked. Geben Sie unmask den Werkzeugen der Personen, die die echten Daten sehen dürfen, und lesen Sie das Protokoll mit latet server log --action apps.unmasked.

Auch Maskieren verrät etwas. Mit einem Token, der maskieren darf, und Zugriff auf die maskierten Logs kann jemand eine Vermutung bestätigen: mario.rossi@acme.it maskieren und in den Logs nach dem Ersatzwert suchen. Ist er da, ist Mario Rossi ein Kunde. Geben Sie mask deshalb nur den Programmen, die es brauchen, bewahren Sie den Token dort auf, wo sie ihre Geheimnisse aufbewahren, und behandeln Sie die maskierten Logs als pseudonyme Daten, nicht als anonyme.

Auch Analysieren verrät etwas. Es erzeugt keinen Ersatzwert und ändert nichts, meldet aber die Werte, die der Tresor des Schlüssels schon kennt, wo immer sie stehen. Ein Token, der nur analysieren darf, kann also bestätigen, dass ein Wert, den kein Detektor von sich aus finden würde (ein Name in einem data-Text, ein Wert, den eine Regel des Schlüssels fand, bevor sie entfernt wurde), einer des Schlüssels ist. Vergeben Sie scan mit derselben Sorgfalt wie mask.

Auch Vergessen verrät etwas. Seine Antwort sagt, wie viele der Werte der Tresor des Schlüssels enthielt; ein Token, der vergessen darf, kann also bestätigen, dass ein Wert einer des Schlüssels ist, wie einer, der analysieren darf. Geben Sie forget nur dem Vorgang, der Löschanfragen bearbeitet; jedes Vergessen steht im Administrationsprotokoll (apps.forgotten, mit dem Token und den Zahlen, nie mit einem Wert). Keiner der Token, die keys add erzeugt, darf vergessen: forget erhält ein eigener Token (keys add-token "Shop" erasure --forget).

Der Tresor eines Schlüssels behält jeden Wert, den er maskiert hat, mit seinem Ersatzwert, solange der Schlüssel besteht: Eine Wiederherstellung funktioniert mit einer Logzeile vom letzten Jahr, und ein Kunde behält jahrelang denselben Ersatzwert. Werte laufen nicht ab: Ein Wert verlässt den Tresor, wenn er vergessen wird (Werte vergessen), und alle Werte gehen mit dem Schlüssel, denn latet server keys remove löscht seinen Bereich und damit seinen Tresor. Ein einmal gefundener Wert wird von da an überall maskiert, wo er auftaucht, auch dort, wo kein Detektor ihn wieder finden würde: Die Adresse eines Kunden, die eine inzwischen entfernte Regel des Schlüssels einmal gefunden hat, bleibt maskiert.

Ein Log-Bereich kann Millionen von Werten enthalten, deshalb hält eine Instanz den Tresor eines Schlüssels nicht im Speicher. Sie behält die Werte, die der Schlüssel zuletzt verwendet hat (key_working_values, standardmäßig 5.000, etwa 10 MiB pro Schlüssel), und die Fingerabdrücke des ganzen Tresors: einen 64-Bit-Fingerabdruck jedes Wortes seiner Werte und Ersatzwerte, mit einem geheimen Schlüssel gebildet, etwa 15 MB für eine Million Werte. Die Wörter eines Aufrufs werden zuerst unter den Fingerabdrücken gesucht: Ein Wort ohne Fingerabdruck gehört zu keinem Wert und keinem Ersatzwert, und PostgreSQL wird nicht danach gefragt. Die Werte und Ersatzwerte, deren Wörter einen haben, werden aus PostgreSQL gelesen, alle des Aufrufs auf einmal, und jeder wird entschlüsselt und mit dem Text verglichen, bevor er etwas maskiert oder wiederherstellt, denn zwei Wörter können denselben Fingerabdruck haben. Weder die Fingerabdrücke noch der Index der Wörter in PostgreSQL enthalten ein Wort im Klartext. Vor jedem Aufruf liest die Instanz in PostgreSQL den Änderungszähler des Schlüssels und übernimmt, was die anderen Instanzen seither gespeichert oder vergessen haben; ein Wert, der auf einer Instanz gespeichert wurde, bevor ein Aufruf beginnt, wird also von diesem Aufruf auf jeder anderen gefunden, auch wenn die Nachricht darüber verloren ging. Ein Schlüssel mit wenigen Werten arbeitet genauso: Nur der Tresor einer Person mit 35.000 Werten oder weniger wird ganz im Speicher gehalten (Serverbetrieb).

Geheimnisse, an die andere Buchstaben grenzen. In PostgreSQL wird ein Geheimnis, das als eine einzige Folge von Buchstaben und Ziffern ohne Leerzeichen oder Satzzeichen geschrieben ist, über sein Wort gefunden, und über seine ersten und letzten sechs Zeichen, wenn andere Buchstaben oder Ziffern an es grenzen: auf einer Seite am Rand der Folge des Textes, die es enthält, und auf beiden Seiten (xhunter2y) in dieser Folge, die ein Aufruf sechs Zeichen auf einmal liest. Ein Aufruf liest höchstens eine Million solcher Positionen: darüber hinaus werden seine späteren Folgen nur über ihre Wörter und Ränder gelesen. Ein Geheimnis von sechs Zeichen oder weniger, oder eines, dessen Wort an einem angrenzenden Rand sechs Zeichen oder weniger hat, findet kein Wort des Textes, wenn etwas an es grenzt. Darum hält eine Instanz die kurzen Geheimnisse eines Schlüssels dauerhaft im Speicher, die aus einer einzigen Folge von 16 Zeichen oder weniger oder mit einem so kurzen Wort am Rand: die neuesten key_held_secrets davon (1.000, sofern die Einstellungen des Servers nichts anderes sagen), gefunden, wie auch immer etwas an sie grenzt, auch jenseits der Million Positionen eines Aufrufs. Ein älteres oder ein längeres wird gefunden, wo nichts an es grenzt, mit etwas auf einer Seite, wenn das Wort an diesem Rand sieben Zeichen oder mehr hat, und mit etwas auf beiden Seiten, wenn es eine einzige Folge von sieben Zeichen oder mehr ist, innerhalb der Million Positionen des Aufrufs. Erhöhen Sie key_held_secrets für einen Schlüssel, der viele kurze Geheimnisse sieht, etwa ein Sitzungstoken in jeder Logzeile: Jedes gehaltene belegt etwa 23 KB auf jeder Instanz, die den Schlüssel bedient, das meiste davon im Detektor, der es findet, und jeder Text, den der Schlüssel maskiert oder wiederherstellt, wird danach durchsucht, was seine Aufrufe etwas verlangsamt. Gemessen mit Latet 0.10.0 auf dem Rechner aus Wie schnell es ist, mit einem eigenen Sitzungstoken in jeder Zeile (ein Wert mehr, der in jeder aufzubewahren ist), maskierte ein neuer Schlüssel mit 1.000 gehaltenen solchen Geheimnissen 73 Zeilen pro Sekunde und stellte 1.935 wieder her, gegenüber 75 und 2.103 ohne gehaltene. Ein Ersatzwert wird gefunden, wo etwas an ihn grenzt, wie sein Geheimnis: Er behält dessen Sonderzeichen an ihrer Stelle, sodass ein Ersatzwert, an den so etwas grenzt, dass keines seiner Wörter für sich steht, der eines im Speicher gehaltenen Geheimnisses ist, und gefunden wird, solange das Geheimnis gehalten wird. Wie überall in Latet wird ein Wert ab vier Zeichen wiedererkannt, der Name einer Person ab drei (bekannte Werte).

Kurze Geheimnisse. Ein Geheimnis mit weniger als zwölf Zeichen (password=1234, ein kurzes Token) erhält einen längeren Ersatzwert derselben Arten, seine Sonderzeichen an ihrer Stelle: fünfzehn Ziffern für eine Zahl (mit ihrem Vorzeichen und ihrem Dezimalpunkt, damit eine JSON-Zahl eine Zahl bleibt), zwölf Hexadezimalziffern für einen hexadezimalen Wert, sonst zwölf Buchstaben und Ziffern. Ein Ersatzwert, so kurz wie sein Wert, käme zufällig in den IDs und Zahlen anderer Logzeilen vor, und eine Wiederherstellung würde Zeilen verändern, die das Geheimnis nie enthielten.

Ein kurzer Ersatzwert, den der Tresor eines Schlüssels vor Latet 0.10.2 vergeben hat (acht Zeichen oder weniger), bleibt, wie jeder Ersatzwert. unmask, das jeden Ersatzwert des Tresors des Schlüssels liest, setzt nur dort den Wert für ihn ein, wo er als ganzes Wort steht, ohne Buchstabe, Ziffer oder Unterstrich daneben. Der Preis: Wo der Wert eines solchen Geheimnisses in einem längeren Wort oder einer längeren Zahl maskiert wurde (postgres in postgresql://, 1234 in order_id=9912343), lässt unmask dort seinen Ersatzwert stehen. Eine Unterhaltung des ersten Tokens des Schlüssels, über das Gateway oder das Plugin, stellt ihn wie bisher wieder her, auch innerhalb eines Wortes, da sie nur die Ersatzwerte wiederherstellt, die ihr gezeigt wurden. Solange eine Instanz des Servers noch 0.10.0 ausführt, gibt sie neuen kurzen Geheimnissen kurze Ersatzwerte, und auch diese bleiben: Aktualisieren Sie jede Instanz.

Was ein gescheiterter Aufruf behält. Ein mask-Aufruf, der beim Maskieren scheitert (ein 507, ein Commit, der PostgreSQL nicht erreicht), behält keinen seiner neuen Werte, wenn er dafür eine der Transaktionen der Instanz hielt, von denen es mit dem standardmäßigen database_pool drei gleichzeitig gibt: Der Bereich des Schlüssels vergisst sie. Waren diese belegt, hat der Aufruf jeden neuen Wert sofort gespeichert, und diese bleiben im Tresor des Schlüssels. Ein Fehler nach dem Maskieren, in der lokalen Prüfung oder beim Erstellen der Antwort, kommt nach dem Commit; auch dann bleiben die neuen Werte des Aufrufs. Die Texte erneut zu maskieren ist in jedem Fall sicher: Ein Wert, den der Tresor behalten hat, hat seinen Ersatzwert, und einer, den er vergessen hat, erhält wieder einen. Ein Agent desselben Schlüssels, der einen vergessenen Wert inzwischen über das Gateway verwendet hat, hat dessen Ersatzwert bereits gesendet, den keine spätere Wiederherstellung zurückverwandelt.

  • Der Tresor. Der Tresor eines Schlüssels fasst die vault_limit Werte des Servers (200.000, wenn die Einstellungen des Servers nichts anderes sagen), oder eine eigene Grenze: latet server keys limit "Shop" 5000000 (von 1.000 bis 100.000.000; default gibt ihm wieder die des Servers). Ein voller Tresor weist den mask-Aufruf, der einen Wert hinzufügen würde, mit 507 ab: Vom Aufruf kommt nichts zurück, und nichts verlässt den Server im Klartext. Der Status des Servers listet die Identitäten bei 90 % ihrer Grenze auf, und die Metrik latet_vault_near_limit zählt sie; heben Sie eine Grenze also an, bevor sie erreicht ist. Jede Instanz zählt die neuen Werte eines Aufrufs, bevor sie sie bestätigt: Zwei Instanzen, oder ein Aufruf und ein Agent desselben Schlüssels, können die Grenze um die neuen Werte je eines Aufrufs überschreiten, bevor der nächste Wert abgewiesen wird. Ein Index der Fingerabdrücke eines Tresors wird bis zu 16.777.216 Fingerabdrücken aufgebaut, etwa acht Millionen Werten der Art, die Wie schnell es ist misst: darüber wird kein Index aufgebaut, solange der Bereich des Schlüssels besteht, und seine Aufrufe suchen ihre Wörter allein in PostgreSQL, langsamer.
  • Ein Aufruf nimmt höchstens 1.000 Texte und das apps_request_mb des Servers (8 MiB, wenn seine Einstellungen nichts anderes sagen, nie mehr als sein max_request_mb): Ein Aufruf wartet im Speicher auf den Thread des Schlüssels, und latet-client teilt eine längere Liste in Aufrufe von höchstens 8 MiB auf. Texte, deren Wörter mehr als 100.000 Werte oder Ersatzwerte des Tresors nennen, werden ganz mit 422 abgewiesen, in festen Worten, die keinen Wert nennen: Vom Aufruf wird nichts maskiert oder wiederhergestellt, senden Sie also weniger Texte pro Aufruf. Ein 503 ist etwas anderes: Ein Speicher des Servers (PostgreSQL, Valkey) hat nicht geantwortet, und derselbe Aufruf kann später erneut gesendet werden (HTTP-Referenz).
  • Gleichzeitige Aufrufe. Ein Schlüssel hat höchstens requests_in_flight Anwendungsanfragen auf einer Instanz (16, wenn die Einstellungen nichts anderes sagen); darüber hinaus 429 mit Retry-After.

Die Aufrufe eines Schlüssels laufen auf einer Instanz nacheinander, im Thread seines Bereichs; Aufrufe verschiedener Schlüssel und Aufrufe, die verschiedene Instanzen erreichen, laufen nebeneinander. Um mit einem Schlüssel schneller zu werden, betreiben Sie mehr Instanzen hinter dem Lastverteiler, oder mehrere Worker auf einer Linux-Maschine (mehrere Instanzen auf einer Maschine).

Gemessen mit Latet 0.11 auf einer Instanz (Intel i9-10850K, 64 GB, Windows 10, PostgreSQL 18 in Docker Desktop auf demselben Rechner, die Standardeinstellungen des Servers): 40.000 Logzeilen von etwa 200 Zeichen mit je einer E-Mail-Adresse und einer öffentlichen IPv4-Adresse, von denen die Hälfte einen bereits gesehenen Kunden nennt, in Aufrufen zu 500 als data; danach 50 Dokumente von etwa 20 KB Prosa mit je zehn Werten, eines pro Aufruf. Zeilen pro Sekunde sind die Zeilen geteilt durch die Zeit, die ihre Aufrufe brauchten; die Zeit eines Dokuments ist der Median seiner 50 Aufrufe:

Der Tresor des Schlüssels Maskieren, Zeilen pro Sekunde Wiederherstellen, Zeilen pro Sekunde Ein Dokument, maskieren / wiederherstellen
Neu, lernt seine Werte nach und nach 191 2.894 126 / 31 ms
200.000 Werte 871 2.960 117 / 32 ms
1.000.000 Werte 773 2.967 124 / 32 ms

Jedes Dokument bringt Werte mit, die für den Schlüssel neu sind und die er beim Maskieren in PostgreSQL schreibt: Dieselben 50 Dokumente, erneut maskiert, mit bekannten Werten, brauchten 16,6 ms bei 200.000 Werten und 16,4 ms bei 1.000.000 (Wiederherstellen jeweils 23 ms). Auch ein neuer Schlüssel schreibt die meisten Werte seiner Zeilen, darum maskiert er weniger.

Latet 0.10.0, auf dieselbe Weise auf demselben Rechner gemessen, maskierte 350 und 304 Zeilen pro Sekunde bei 200.000 und 1.000.000 Werten und 153 für einen neuen Schlüssel, stellte 2.114 und 2.014 wieder her und maskierte ein Dokument in 163 bis 182 ms. Latet 0.11 hatte sich vorgenommen, auf einer Instanz mindestens 1.000 solcher Zeilen pro Sekunde bei 200.000 und bei 1.000.000 Werten zu maskieren, und ein Dokument in 30 ms oder weniger: Es maskiert 871 und 773, und ein Dokument in 30 ms nur dann, wenn der Schlüssel seine Werte schon kennt. Der größte Teil der Zeit eines Maskierungsaufrufs geht an die Detektoren, ein Teil an das Warten auf PostgreSQL.

Eine Instanz findet die Werte eines Schlüssels über die Fingerabdrücke der Wörter seines Tresors (der Tresor eines Schlüssels), die etwa 15 MB für eine Million Werte belegen (14,77 MB bei 1.000.000) und im Hintergrund aufgebaut werden, während sich der Bereich des Schlüssels öffnet: in 1,5 Sekunden bei 1.000.000 Werten (der Median von drei Öffnungen, 1,42 bis 1,62 s). Bis sie bereit sind, suchen die Aufrufe des Schlüssels ihre Wörter allein in PostgreSQL: In einer früheren Messung mit Latet 0.11, mit abgeschalteten Fingerabdrücken, maskierte der Schlüssel mit 200.000 Werten 494 Zeilen pro Sekunde und stellte 1.382 wieder her. Jeder Aufruf liest zuerst den Änderungszähler des Schlüssels, 0,51 ms im Median, und übernimmt, was sich seither geändert hat: 1,87 ms für einen neuen Wert und 2,11 ms für 100, ebenfalls im Median.

In einem Aufruf bereits maskierter Texte werden nur die neuen erneut gelesen, aber jeder der anderen wird trotzdem nachgeschlagen und mit dem verglichen, was der Schlüssel maskiert hat: 500 bereits maskierte Texte und ein neuer brauchten 168 ms, der neue allein 6,1 ms (Mediane von drei Läufen).

Auf einer Linux-Maschine addieren sich die Worker. Gemessen in Docker auf demselben Rechner (eine virtuelle Linux-Maschine mit 20 Prozessoren und 16 GB, PostgreSQL 18.6 und Valkey 9.1.2 in Containern daneben), mit latet server run --workers und einem API-Schlüssel mit 200.000 Werten: 200.000 verschiedene Zeilen der oben beschriebenen Art in Aufrufen zu 62, vier Verbindungen pro Worker, jede Anzahl 20 Sekunden lang, der Median von drei Runden; der Speicher ist der residente Speicher jedes Workers nach den Runden:

Worker Maskieren, Zeilen pro Sekunde Wiederherstellen, Zeilen pro Sekunde Speicher pro 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

Acht Worker maskierten das 6,28-Fache dessen, was einer maskierte, und stellten das 5,21-Fache wieder her, wo sich Latet 0.11 mindestens das 5-Fache für beides vorgenommen hatte; keine Runde mit acht Workern stellte weniger als das 5,07-Fache wieder her. Der Kernel teilt die Verbindungen den Workern nach einem Hash zu, nicht der Reihe nach, sodass manche Worker mehr davon bekommen als andere: 1 bis 10 der 32 in einer Runde mit acht Workern. Der Prozess, der die Worker startet, belegte daneben 47 bis 48 MB. Diese Aufrufe kommen von mehreren Verbindungen zugleich; vergleichen Sie die Zeilen dieser Tabelle also miteinander, nicht mit der ersten Tabelle.

Neue Werte zu schreiben braucht Verbindungen zu PostgreSQL: Eine Instanz, und jeder Worker, hält höchstens database_pool davon (10, sofern die Einstellungen des Servers nichts anderes sagen) für die Arbeit aller Identitäten und lässt drei Zehntel davon (drei von zehn) von Aufrufen halten, die ihre neuen Werte in je einer Transaktion speichern; die übrigen speichern jeden Wert einzeln, was langsamer ist. Erhöhen Sie database_pool für eine Instanz, die für viele neue Schlüssel gleichzeitig maskiert, und mit ihm max_connections von PostgreSQL (Serverbetrieb). scripts/bench_apps.py im Repository misst einen Server von Ihnen so, wie die erste Tabelle gemessen wurde.

Latet 0.11 ändert die Tabellen. Stoppen Sie jede Instanz und jeden Worker auf jeder Maschine, führen Sie latet server migrate einmal mit 0.11 aus und starten Sie dann alle damit (Aktualisieren). Eine Instanz startet nicht auf Tabellen einer anderen Version, daher wird zwischen dem Stopp und dem Start kein Aufruf beantwortet, und latet server migrate ändert nichts, solange eine Instanz läuft, eine von 0.10 noch 30 Sekunden nach ihrem Stopp. Die Migration gibt jeder Wortzeile jedes Tresors einen leeren Platz für ihren Fingerabdruck und für die Zählung der Änderung, die sie zuletzt geschrieben hat, mit ihren Indizes: Auf einer Datenbank mit großen Tresoren dauert sie länger als frühere Migrationen, und die Fingerabdrücke selbst werden später gebildet, im Hintergrund.

Ein Tresor, den Latet 0.10 gespeichert hat, hat noch keine Fingerabdrücke. Nach dem Neustart beantwortet die erste Instanz, die den Bereich eines Schlüssels aufbaut, dessen Aufrufe sofort, indem sie ihre Wörter in PostgreSQL sucht, wie 0.10 es tat, und bildet die Fingerabdrücke des Tresors im Hintergrund, einmal, eine Instanz nach der anderen; jede Instanz baut dann ihren eigenen Index daraus auf und nutzt ihn. Bis dahin maskieren die Aufrufe des Schlüssels dieselben Werte, langsamer (Wie schnell es ist: 494 Zeilen pro Sekunde bei 200.000 Werten mit abgeschalteten Fingerabdrücken, in einer früheren Messung), und keiner wird deswegen abgewiesen. Während eine Instanz die Fingerabdrücke eines Tresors bildet, können auch die anderen Schlüssel und Personen, deren Bereiche sie inzwischen aufbaut, über die Suche in PostgreSQL bedient werden, bis ihr Index aufgebaut ist. Wenn die Fingerabdrücke noch nicht alle gebildet sind oder ein Index nicht aufgebaut werden kann (PostgreSQL oder Valkey antworten nicht, ein fehlgeschlagenes Nachführen des Index), bleiben die Aufrufe bei der Suche in PostgreSQL, bis der Index erneut aufgebaut ist: sofort, wenn eine andere Instanz die Fingerabdrücke gebildet hat, sonst 10, 20 und 30 Sekunden nach jedem Versuch und dann einmal pro Minute. Das Log sagt einmal, wenn ein Index nicht mehr genutzt wird, und einmal, wenn er wieder genutzt wird. Ein Tresor über der Größe, für die ein Index aufgebaut wird (Grenzen), bleibt bei der Suche in PostgreSQL, solange sein Bereich besteht. Die Wörter selbst bleiben, wie 0.10 sie gebildet hat: 0.11 bildet sie nicht neu.

Eine Version, die ändert, wie die Wörter eines Tresors gebildet werden (das Changelog sagt es), bildet die Wörter eines Schlüssels in der ersten Instanz neu, die seinen Bereich aufbaut, bevor dieser Bereich den ersten Aufruf des Schlüssels beantwortet: Latet 0.10.0 brauchte dafür etwa 40 Sekunden für 200.000 Werte und viereinhalb Minuten für 1.000.000 auf dem oben genannten Rechner. Ein Aufruf, der in der Zwischenzeit eintrifft, wartet, und eine Instanz baut vier Bereiche gleichzeitig auf; einige große Schlüssel verzögern also die ersten Aufrufe der anderen Schlüssel auf ihr: Senden Sie nach einer solchen Version jedem großen Schlüssel einen Aufruf mit ausreichend langem Timeout, bevor seine Programme wieder starten.

Solange Instanzen zweier Versionen nebeneinander laufen, was eine Version erlaubt, die die Tabellen gleich lässt, wird ein Wert, den eine ältere Instanz schreibt, von den neueren gefunden, solange sie ihn im Speicher halten, aber nicht in PostgreSQL, bis der Bereich des Schlüssels neu aufgebaut wird. Wenn die API für Anwendungen genutzt wird, stoppen Sie jede ältere Instanz, bevor die neuen starten, auch wenn die Tabellen gleich bleiben.