Ons laat 'n hele auth-stelsel loop op stores wat net get en put ken
'n Auth-stelsel lyk of dit 'n relasionele databasis wil hê. Gebruikers, rolle, sessies, OAuth grants, refresh tokens, alles wat na mekaar verwys. Ons s'n gebruik nie een nie. Dit loop op Azure Table Storage, 'n store wat jou 'n partition key, 'n row key, en amper niks anders gee nie. Geen joins nie, geen betekenisvolle secondary indexes nie, geen increment-operator nie, geen multi-row-transaksies in die vorm waaraan jy gewoond is nie. Dit was 'n keuse. Hier is wat die keuse koop, die patrone wat dit laat werk, en die deel waar SQL werklik makliker sou gewees het.
Die rede om dit te doen is koste en operasionele eenvoud. Geen connection pool om uit te put nie, geen databasis-instansie om te dimensioneer, te patch of te fail over nie, per-partition-skalering wat jy verniet kry. 'n Tabel kos amper niks om te hou nie en daar is niks om op te raak nie. Die prys daarvan is 'n store sonder query planner, wat net vrugte afwerp wanneer jou toegangspatrone net so voorspelbaar is soos die store dom is. Auth se patrone is presies dit, so die hele ontwerp leun swaar daarop.
Die sleutel doen die werk wat 'n tabeluitleg sou doen. Sonder joins ontwerp jy die sleutel sodat die navraag die sleutel is. Jy denormaliseer met opset, jy kies partitions sodat jou warm leeswerk point-gets is, en jy enkodeer saamgestelde identiteite direk in die row key, byvoorbeeld "{pk}|{rk}" vir inskrywings wat 'n saamgestelde identiteit nodig het in 'n store wat net een row-key-gleuf bied. Die vraag "hoe soek ek dit op" moet beantwoord word wanneer jy die sleutel ontwerp, nie tydens navraagtyd nie. Vir auth is dit reg, want die toegangspatrone is bekend en hulle dryf nie: kry 'n gebruiker volgens id, kry 'n gebruiker se grants, verbruik 'n kode. Jy verken nie die data nie. Jy ken elke vraag vooraf.
Jy hou jou eie change log. Elke ry dra 'n bediener-bestuurde Timestamp, en dit is aanloklik om inkrementele backup daarop te bou: trek alles waar Timestamp gt watermark. Dit werk in 'n demo en val plat in produksie, want die store indekseer die partition key en die row key en niks anders nie. Timestamp is in geen indeks nie, so om daarop te filter, inspekteer elke ry in die tabel. Dit is 'n volledige skandering wat as 'n navraag vermom is, en dit word stadiger elke enkele dag wat die tabel groei. So skryf die store eerder sy eie change log. Elke mutasie, elke upsert en elke delete, voeg 'n ry by 'n change-log-tabel wat volgens die logiese tabelnaam gesleutel is, met die saamgestelde "{pk}|{rk}" as die row key en 'n vlag vir of die ry geskryf of geskrap is. Nou is "wat het verander sedert die watermark" 'n lees van een klein, geïndekseerde partition in plaas van 'n skandering van die hele tabel, en die backup point-lees net die rye wat werklik beweeg het. Jy herbou change-data-capture uit gewone skryfwerk, en dit skaal met die veranderingstempo in plaas van met die grootte van die tabel.
Concurrency sonder transaksies. Hier is geen SELECT ... FOR UPDATE nie. Wat jy in plaas daarvan kry, is een atomiese primitief: die voorwaardelike skryf, aan jou oorhandig as 'n ETag. Jy skryf 'n ry net as die kopie wat jy gelees het nie onder jou verander het nie. Alles wat veiligheid onder gelyktydige toegang nodig het, word uitgedruk as optimistic concurrency plus 'n herprobeer by konflik. Die duidelikste voorbeeld is die rekening-uitsluitteller. AccessFailedCount moet atomies inkrementeer, maar die store het geen increment nie. So jy lees die ry, stoot die telling op, skryf dit terug voorwaardelik op die ETag wat jy gelees het, en probeer weer as iemand jou voorgespring het. Daardie lus is hoe jy 'n teller atomies maak op 'n store wat geen teller het nie. Skiet vyftig verkeerde wagwoorde gelyktydig af en elkeen van hulle land, want die konflik word bespeur en herprobeer eerder as om verlore te gaan.
'n Delete kan 'n slot wees. Enkelgebruik-verbruik, 'n authorization code of 'n eenmalige token wat presies een keer ingewissel moet word, is 'n voorwaardelike delete. Dit is 'n ETag voorwaardelike-delete: as die delete slaag, het jy die wedloop gewen en mag jy voortgaan; as dit misluk omdat die ry reeds weg was, het iemand anders dit eerste verbruik en jy stop. Die delete is die slot. Dieselfde idee doen leader election, gebou op 'n blob lease, 'n slot wat uit 'n atomiese skryf gemaak is eerder as 'n aparte koördinasiediens. Jy het amper nooit 'n slotdiens nodig wanneer die skryf self atomies is nie.
Deletes moet eersteklas wees, wat die helfte is van waarom die change log bestaan. 'n Key-value store het geen delete-merker nie: 'n geskrapte ry verdwyn eenvoudig, so enigiets wat vir veranderinge geskandeer het, kon nooit eens sien dat dit weg is nie. 'n Backup wat op ry-timestamps sleutel, sou die geskrapte gebruiker stilweg vir ewig vorentoe dra. Die change log teken die delete as 'n delete aan, so 'n herstel speel dit weer af in plaas van om dit op te wek. Daardie falingsmodus, 'n backup wat almal wat jy verwyder het getrou terugbring, is 'n hele storie op sy eie en verdien sy eie pos, maar die patroon hoort op hierdie lys: in 'n store sonder delete-log hou jy die delete-log self.
Nou die ander kant van die grootboek, wat jy prysgee. Jy gee ad-hoc-navrae prys: 'n werklik nuwe toegangspatroon kan 'n nuwe sleutelontwerp of 'n volledige skandering beteken, want daar is geen query planner om jou te red nie. Jy gee joins prys, so jy onderhou gedenormaliseerde kopieë met die hand en besit die konsekwentheid daarvan. Jy gee multi-row-transaksies prys, so jy ontwerp elke invariant om binne 'n enkele item te leef, want enkel-item-atomisiteit is al wat jou gegee word. As jou data diep relasioneel is, of jou toegangspatrone onbekend is en steeds beweeg, is dit die verkeerde gereedskap en dit gaan seermaak.
En dit is presies waarom dit by auth pas. 'n Relasionele databasis verdien sy onderhoud deur vrae te beantwoord waaraan jy nog nie gedink het nie. 'n Auth-stelsel het nie daardie vrae nie. Die stel dinge wat jy vra, kry hierdie gebruiker, verbruik hierdie kode, kies hierdie leier, rugsteun wat verander het, is klein, bekend en stabiel. Gee die query planner prys wat jy nooit sou gebruik het nie, en in ruil kry jy 'n stoorlaag wat amper niks kos om te bedryf nie en niks het om oor te fail over nie. Vir die meeste sagteware is dit 'n slegte ruil. Vir hierdie een is dit die regte een.
Hierdie patrone is die stoorenjin onder Authagonal: 'n key-value-ontwerp wat amper niks kos om te bedryf nie, wat 'n groot deel is van hoe ons elke funksie in elke plan insluit.