← Tots els articles

Núm. 10X by Nexow

Una preautorització per a cada prompt

A X by Nexow, un widget costa crèdits, i ningú no sap la factura fins que el model acaba d’escriure. Així que vam manllevar dels pagaments amb targeta: autoritzar abans de la crida, cobrar després, mai més del que s’ha autoritzat.

Vincent Porret

Cofundador i CEO · · 4 min de lectura

Estampa 10, Pedra. Gotes i pintes sobre un bany, treta en construir el web.Passa-hi el punter per deixar caure tinta.

En riscos i tresoreria, un pagament de què no se sap l’import per endavant és un problema antic. Un hotel o una benzinera no sap el compte final quan li dones la targeta, així que primer reté un import i cobra el real més tard. X by Nexow té el mateix problema cada vegada que descrius un widget: el model escriu fins que acaba, i només llavors el proveïdor diu quants tokens ha fet servir. Així que X fa el que fa una xarxa de targetes.

Autoritzar

Abans que una petició vagi al model, el servidor demana a la base de dades que retingui crèdits per a ella: una estimació de l’entrada que està a punt d’enviar, més el màxim de sortida que la crida pot escriure. La base de dades concedeix aquesta estimació o el que queda al teu saldo, el que sigui més petit. Si el que queda és per sota del mínim que necessita una crida per començar, la petició s’atura aquí, amb un HTTP 402, i no es crida mai el model. Si el que s’ha concedit és menor que l’estimació, el límit de sortida de la crida es rebaixa al que la retenció pot pagar.

Cobrar

Quan la crida acaba, l’informe d’ús del proveïdor fixa el càrrec real, amb preu per model i per tipus de token: entrada, sortida, lectures i escriptures de memòria cau. La liquidació pren el menor entre aquest càrrec i la retenció, i retorna la resta. Aquesta és la línia de la funció de la base de dades que ho fa:

v_charge := least(coalesce(v_hold.pending_actual, 0), v_hold.amount);   -- clamp to reserved

Aquest topall és expressament. Si l’estimació es va quedar curta, la diferència l’assumim nosaltres, i un comentari del codi ho diu: una estimació baixa només posa en risc l’operador, que s’empassa l’excés, mai l’usuari, que no paga de més. Una crida que no ha consumit res allibera tota la seva retenció. Un flux tallat abans que arribés l’informe d’ús es cobra amb un mínim prudent, calculat a partir del que es va enviar i del que ja havia tornat, perquè tampoc no es perdin els tokens que el proveïdor ja va servir.

Mai no se’t cobra més del que s’ha retingut. Si la nostra estimació es va quedar curta, la diferència la paguem nosaltres.

La base de dades diu que no

Els saldos viuen a Postgres, i les regles també, com a restriccions i no com a codi de l’aplicació:

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 saldo no pot baixar de zero, i els crèdits retinguts no el poden superar, ho intenti com ho intenti un error més amunt. Cada canvi passa per una funció de la base de dades que primer bloqueja la fila del compte, així que dues construccions llançades al mateix moment s’esperen en lloc de gastar totes dues els mateixos crèdits. Només el servidor pot cridar aquestes funcions, i un usuari només pot llegir les seves pròpies files.

Els crèdits arriben per lots, cadascun amb el seu origen i, alguns, amb caducitat. Un càrrec els consumeix en un ordre fix: per prioritat, els de subscripció abans que els comprats, després els que caduquen abans, després els més antics. Cada moviment s’escriu en un registre, amb el saldo resultant.

Un esdeveniment, un abonament

Els crèdits es compren a través d’Stripe, i Stripe pot lliurar el mateix esdeveniment més d’una vegada. L’identificador de cada esdeveniment entra en una taula on és la clau primària, abans d’abonar res, així que una repetició es confirma i s’ignora. Si el processament falla, la fila s’esborra i es demana a Stripe que ho torni a provar. Els crèdits abonats porten la seva pròpia clau única, com l’identificador de la factura, així que fins i tot un duplicat que s’escolés pel primer control no podria abonar dues vegades.

Les retencions no sobreviuen a la seva crida. Un escombratge passa cada cinc minuts: una retenció que ha superat els seus cinc minuts de vida es liquida si hi ha un càrrec registrat, i s’allibera si no. Una generació es talla als dos minuts, així que l’escombratge no toca mai una crida que encara estigui en curs. I si portes la teva pròpia clau del model, res d’això no s’aplica: les teves crides no gasten mai crèdits.

Vincent