N° 10X by Nexow
Une pré-autorisation pour chaque prompt
Dans X by Nexow, un widget coûte des crédits, et personne ne connaît la facture avant que le modèle ait fini d’écrire. On a donc emprunté au paiement par carte : autoriser avant l’appel, débiter après, jamais plus que ce qui a été autorisé.
Vincent Porret
Cofondateur et CEO · · 4 min de lecture
En risque et en trésorerie, un paiement dont on ne connaît pas le montant à l’avance est un vieux problème. Un hôtel ou une pompe à essence ne connaît pas la note finale quand vous tendez votre carte : il bloque d’abord un montant, puis prélève le vrai plus tard. X by Nexow a le même problème chaque fois que vous décrivez un widget : le modèle écrit jusqu’à ce qu’il ait fini, et c’est seulement alors que le fournisseur indique combien de tokens il a utilisés. X fait donc comme un réseau de cartes.
Autoriser
Avant qu’une requête parte vers le modèle, le serveur demande à la base de données de bloquer des crédits pour elle : une estimation de l’entrée qu’il s’apprête à envoyer, plus le maximum de sortie que l’appel peut écrire. La base accorde cette estimation ou ce qui reste sur votre solde, selon ce qui est le plus petit. Si ce qui reste est sous le minimum dont un appel a besoin pour démarrer, la requête s’arrête là, avec un HTTP 402, et le modèle n’est jamais appelé. Si le montant accordé est inférieur à l’estimation, la limite de sortie de l’appel est abaissée à ce que le blocage peut payer.
Débiter
Quand l’appel se termine, le relevé d’usage du fournisseur fixe le vrai montant, tarifé par modèle et par type de token : entrée, sortie, lectures et écritures de cache. Le règlement prend le plus petit entre ce montant et le blocage, et rend le reste. Voici la ligne de la fonction de la base de données qui s’en charge :
v_charge := least(coalesce(v_hold.pending_actual, 0), v_hold.amount); -- clamp to reservedCe plafond est voulu. Si l’estimation était trop basse, c’est à nous d’absorber la différence, et un commentaire du code le dit : une estimation basse ne fait jamais courir de risque qu’à l’opérateur, qui absorbe le dépassement, jamais à l’utilisateur, qui ne paie pas plus. Un appel qui n’a rien consommé libère tout son blocage. Un flux coupé avant l’arrivée du relevé d’usage est facturé à un plancher prudent, calculé à partir de ce qui a été envoyé et de ce qui était déjà revenu, pour que les tokens servis par le fournisseur ne soient pas perdus non plus.
On ne vous débite jamais plus que ce qui a été bloqué. Si notre estimation était trop courte, c’est nous qui payons la différence.
La base de données dit non
Les soldes vivent dans Postgres, et les règles aussi, sous forme de contraintes plutôt que de code applicatif :
balance bigint not null default 0 check (balance >= 0),
held bigint not null default 0 check (held >= 0),
…
constraint held_le_balance check (held <= balance)Un solde ne peut pas passer sous zéro, et les crédits bloqués ne peuvent pas le dépasser, quoi qu’essaie de faire un bug plus haut. Chaque modification passe par une fonction de la base qui verrouille d’abord la ligne du compte : deux constructions lancées au même instant s’attendent au lieu de dépenser toutes les deux les mêmes crédits. Seul le serveur peut appeler ces fonctions, et un utilisateur ne peut lire que ses propres lignes.
Les crédits arrivent par lots, chacun avec sa provenance et, pour certains, une date d’expiration. Un débit les consomme dans un ordre fixe : par priorité, les crédits d’abonnement avant les crédits achetés, puis ceux qui expirent le plus tôt, puis les plus anciens. Chaque mouvement est écrit dans un registre, avec le solde qui en résulte.
Un événement, un crédit
Les crédits s’achètent via Stripe, et Stripe peut livrer le même événement plus d’une fois. L’identifiant de chaque événement entre dans une table dont il est la clé primaire, avant que quoi que ce soit ne soit crédité : une répétition est acquittée et ignorée. Si le traitement échoue, la ligne est retirée et on demande à Stripe de réessayer. Les crédits accordés portent leur propre clé unique, comme l’identifiant de la facture : même un doublon qui aurait passé le premier contrôle ne pourrait pas créditer deux fois.
Les blocages ne survivent pas à leur appel. Un balayage passe toutes les cinq minutes : un blocage qui a dépassé ses cinq minutes de vie est réglé si un montant y a été enregistré, et libéré sinon. Une génération s’arrête au bout de deux minutes : le balayage ne touche donc jamais un appel encore en cours. Et si vous apportez votre propre clé de modèle, rien de tout cela ne s’applique : vos appels ne dépensent jamais de crédits.
Vincent

