• Zuhause
  • Artikel
  • Wie stellen wir eine schnelle Antwortzeit für die Kartenautorisierung sicher?
Veröffentlicht am 26-02-2019

Wie stellen wir eine schnelle Antwortzeit für die Kartenautorisierung sicher?

Service Design bei Qonto

Bei Qonto stellen wir KMU und Unternehmern ein Bankkonto und Kreditkarten zur Verfügung. Eine unserer größten Herausforderungen besteht darin, in weniger als 10 Millisekunden auf Kartenautorisierungsanfragen zu antworten. Wenn Sie so schnell gehen, erhalten Sie schnellere Antworten, wenn Sie mit der Qonto-Karte bezahlen. Dies bedeutet auch geringere Infrastrukturkosten für die Skalierung des Dienstes.

In diesem Artikel wird beschrieben, wie wir diese Antwortzeiten erzielen und gleichzeitig eine saubere und wartbare Codebasis beibehalten. Wir laden Sie auf eine Reise durch die Entwurfsphase des Services bis zur konkreten Umsetzung mit zwei konkreten Beispielen ein.

TL; DR

Ihre Qonto-Karte ist verrückt, schnell und absolut stabil.

Das Rezept: von muss planen

Die Zutaten

Ausgehend von einer leeren Seite. Was brauchen wir? Was sind die Probleme zu lösen? Wo könnte unser neues Core Banking System (CBS) etwas Besseres einbringen?

  1. Anwendungsfälle

Sie würden denken, dass Kartenzahlungen unkompliziert sind: Legen Sie Ihre Karte in ein Zahlungsterminal ein, geben Sie Ihren PIN-Code ein und klicken Sie auf "voilà"! Sie würden staunen, wie vielfältig die Abfolge von Ereignissen ist, die wir tatsächlich erhalten können. Wir haben das schon früh entdeckt, als wir die verschiedenen Anwendungsfälle herausgearbeitet haben.

Hier ist ein Beispiel für einen solchen Anwendungsfall:

2. Technischer Fluss

Gleichzeitig zeichnen wir mit der technischen Dokumentation des Kartennetzwerks ein Flussdiagramm, welche Ein- und Ausgänge wir empfangen und senden müssten. Mal sehen, mit einigen gemeinsamen Aktivitäten.

Stellen Sie sich vor, Sie haben in Ihrem Lieblingsrestaurant den falschen PIN-Code eingegeben. Die folgende Sequenz wird generiert:

Sie besuchen dann eine Website, um ein Produkt zu kaufen. Das ist, was passiert:

Nachdem Sie ein Paar Flugtickets gekauft haben, entscheiden Sie sich, eines davon zu stornieren:

Ein Bauunternehmen hat versehentlich das Kabel zwischen dem Kartennetz und Qonto durchtrennt (ein hoffentlich seltenes Ereignis!):

3. Ziele

An dieser Stelle haben wir einen klaren Überblick über das, was funktional von uns erwartet wird. Wir müssen klare Ziele setzen, bevor wir uns mit den technischen Details der Implementierung beschäftigen.

Wenn wir unsere Computer fallen lassen, treffen wir uns mit allen Mitgliedern des Entwicklungsteams um ein Blatt Papier. Wir schreiben eine Liste von Zielen:

  • Seien Sie schnell: Sie bietet unseren Kunden die bestmögliche Benutzererfahrung. Dadurch werden die Kosten für die Skalierung verringert. Ganz zu schweigen davon, die Widerstandsfähigkeit des Systems zu verbessern.
  • Seien Sie genau: Wir dürfen keine falsch positiven oder falsch negativen Genehmigungen zulassen. Wenn Sie Geld haben, können Sie es verwenden. Wenn Sie keine haben, können Sie nicht. Dies mag offensichtlich erscheinen, aber besser gesagt als nicht.
  • Seien Sie von Anfang an wartbar: Was wir bauen, muss lange dauern und leicht wiederverwendbar sein.
  • Schiff pünktlich. Wenn wir dies berücksichtigen, können wir die Dinge einfacher machen.

Das Rezept

Wir haben diese Startelemente, die Zutaten für unseren CBS-Autorisierungsservice. Planen wir das Rezept aus, um alles zusammenzusetzen.

  1. Trennung von Bedenken

Haben Sie vom Prinzip der Trennung von Bedenken gehört? Diese Entwurfsregel, die angeblich von Dijkstra geprägt wurde, erklärt, dass jeder Aspekt eines Programms separat behandelt werden sollte. Dies verbessert die Lesbarkeit und Wartbarkeit des Programms. In welche Aspekte wird der Autorisierungsdienst eingegrenzt?

  • Ein Protokoll (ISO8583) Decoder und Encoder: Für die Kommunikation mit dem Kartennetzwerk.
  • Ein Autorisierungshandler: Wir müssen jede Autorisierungsanfrage beantworten können.

Beide werden als Microservices implementiert und kommunizieren über eine REST-API.

2. Definieren des inneren Dienstleistungsflusses

Als Nächstes beginnen wir, die Arbeit unter jedem Microservice zu unterteilen: Der Code sollte lesbar und wiederverwendbar sein. Jeder Teil sollte nicht wissen, was die anderen Teile tun.

Wir teilen den Protokoll-Handler in drei Teile auf:

  1. Ein TCP-Client / Server zum Senden und Empfangen von ISO8583-Frames aus dem Kartennetzwerk.
  2. Ein ISO8583-Decoder und -codierer
  3. Ein Client für den Autorisierungsdienst

Wir unterteilen den Berechtigungsservice auch in drei Teile:

  1. Ein API-Server für die Kommunikation mit dem Protokollhandler
  2. Ein Datenbank- (DB-) Client, der DB-Abfragen enthalten und ausführen wird.
  3. Ein Fehlerbehandlungsprogramm, das Rückmeldungen zu den Autorisierungsoptionen liefert.

Bei der Fehlerbehandlung erwarten wir, dass Fehler (oder Ablehnungen) auf mehreren Ebenen auftreten können. Da wir sachkundig sein wollen, möchten wir den normalen Weg fortsetzen, um alle möglichen Fehler und Ablehnungsgründe zu sehen. Der Fehlerbehandler hat die Aufgabe, alle Fehler zu zentralisieren und die endgültige Antwort zu liefern.

Starter kochen: ISO8583 analysieren

Ein komplexes Format

Stellen Sie sich vor, Sie sind im Restaurant und die folgende Abfolge von Ereignissen passiert.

Wenn die Kreditanfrage bei Qonto eintrifft, erhalten wir einen ISO8583-TCP-Frame, der sehr ähnlich aussieht:

Die ersten vier Oktette geben die Länge des TCP-Pakets an. Die beiden folgenden (0400) geben die Art der Nachricht an; hier eine stornierungsanfrage. Danach erhalten Sie ein oder zwei Bitmaps, die angeben, welche Felder vorhanden sind, und dann alle diese Felder in der Reihenfolge.

Jedes Feld hat eine andere Kodierung. Einige haben eine variable Länge, einige enthalten optionale Unterfelder, andere Dezimalwerte, die als Hexadezimalwerte codiert sind. (Sie haben das richtig gelesen: siehe die 0978-Zeichenfolge im Beispiel? Sie liest wirklich 978, selbst wenn der Hexadezimalwert 2424 ist - und den ISO4217-Code für die Euro-Währung darstellt.)

Je nach Feldtyp werden die Daten nach Oktett (dies gilt für Text) oder nach Quartett (für Ziffern) gruppiert. Selbst in einem Bereich kann sich diese Gruppierung ändern. Beispielsweise wird die Länge eines Felds mit variabler Länge als normales Hexadezimal pro Oktett codiert, und seine Daten können numerisch sein und nach Quartetten gruppiert sein.

Schnell sein

Hier ist die eigentliche Herausforderung des Protokoll-Handlers: Analysieren Sie dieses unordentliche Format und verwandeln Sie es in verständliche JSON. Und wenn möglich, lesen Sie es nur einmal: Da wir alle Informationen aus dem Frame benötigen, ist O (n) die optimale Analysekomplexität.

Das Wichtigste zuerst: Wir erstellen ein Array, das alle möglichen Felder in der Reihenfolge des Erscheinens beschreibt (einige davon sind möglicherweise an zwei Positionen, wir legen sie zweimal in das Array). Da wir beim Lesen des Frames weiterkommen, werden wir dieses Array weiter vorantreiben.

Hier ist ein Beispiel davon:

Wir benötigen auch eine Empfangsstruktur mit Methoden zur Eingabe von Feldern:

Dann ist der Parser unkompliziert:

Dieser Pseudo-Code verwirft natürlich jede Fehlerüberprüfung oder Ausfallsicherheit. Dies impliziert, dass ein Quartetpuffer-Typ sowie ein Quartet-Typ implementiert werden muss, da Go keine Typen unterstützt, deren Länge weniger als ein Byte beträgt.

Handhabung von Quartetten

Da die Ausgabe des Protokoll-Handlers reines JSON (eine Zeichenfolge) ist, entscheiden wir uns dafür, das Quartett in eine Tupel-Konvertierung zu überführen (wir hätten es mit einigen bitweisen Operationen gemacht). Stattdessen erstellen wir drei Funktionen:

  • Ein Konverter von einem Byte mit ISO8583-Ziffern in zwei ASCII-Ziffern.
  • Eine von einem Byte Text (in EBCDIC) zu einem ASCII-Zeichen;
  • Ein Nop-Konverter, der nur das Byte kopiert, wie es ist.

Da es sich bei den ersten beiden nur um eine Alphabetumwandlung handelt, verwenden wir nur eine Karte:

Daher können wir einen einfachen Byte-Puffer verwenden, um das Lesen des Frames zu handhaben.

Lassen Sie uns nach all diesen Verbesserungen den Protokoll-Handler bewerten. Es dauert vom Ende bis zum Ende durchschnittlich 150 µs, um zu empfangen und zu analysieren, und 150 µs, um ein vollständiges Frame zu schreiben und zu senden. Der Code ist sauber mit wenig überprüfbaren Funktionen und großer Trennung der Bedenken.

Wir hätten uns mehr verbessern können. Beispielsweise hätten wir Arrays anstelle von Karten zur Konvertierung verwenden können. Das ist aber nicht nötig: Wir sind schnell genug und der Code würde dadurch weniger lesbar. Wir können zum Autorisierungsdienst übergehen.

Das Hauptgericht: Wie wir Postgres optimal nutzen

Die Bearbeitung Ihrer Kartenzahlung endet nicht mit der Analyse der Kartennetzanforderung! Schauen wir uns den Autorisierungsdienst an: Wie wir uns entscheiden, Ihre Zahlungen anzunehmen?

Der Autorisierungsdienst kann recht komplex sein. Wenn wir es streng implementieren, wie es der naive Fluss beschreibt, wird die Verarbeitung einer Nutzlast sehr lange dauern.

Die Frage ist: Was ist die wichtigste Ursache für die Latenzzeit? Netzwerk! Wo generiert dieser Fluss die meisten Netzwerkaufrufe: Datenbankaufrufe. Wir müssen es vereinfachen. Im Idealfall sollte für eine Autorisierung nur ein Aufruf der Datenbank erforderlich sein.

Entwerfen der Datenbank

Schreiben wir alle Informationen auf, die wir speichern müssen:

  • Kontoinformationen
  • Karteninformationen und Optionen
  • Balance
  • Angeforderter Betrag und Saldelta (da möglicherweise Teilberechtigungen zulässig sind)
  • Einfügung, Gültigkeit und Berechtigungsdatum
  • Vorgangstyp

Diese Liste ist kurz genug, um nur in eine Tabelle aufgenommen zu werden. Mit einer intelligenten Auswahl an Indizes können wir sicherstellen, dass jede Suche in dieser Tabelle schnell erfolgt. Wir entscheiden uns für einen Primärschlüssel aus Konto-ID, Kartentoken, Datum und ID. Auf diese Weise stellen wir sicher, dass Postgres Daten in dieser Reihenfolge speichert. Und die Tabelle ist partitionfähig, entweder nach Konto oder nach Datum.

Die nächste Frage lautet: Wie lassen sich Transaktionen nacheinander identifizieren (etwa nach einer Autorisierung löschen)? Dazu fügen wir ein Feld hinzu: Transaktions-ID.

Jede Zeile mit einem Null-Autorisierungsdatum wird in Berechnungen ignoriert. Auf diese Weise verfolgen wir abgelehnte Berechtigungen und behalten gleichzeitig die Lesbarkeit der Daten bei.

Der Finaltisch ist bereit, entworfen zu werden:

Wenn wir den Fluss beschreiben würden, könnten wir die Analogie einer Kassette annehmen. Für jede Abfrage wird so lange gewürfelt, bis wir zu der Zeile gelangen, die dem Schlüssel entspricht. Mit der Wahl des Primärschlüssels stellen wir jedoch sicher, dass die Daten einer Karte und eines Kontos gruppiert werden. Das bedeutet, dass wir einfache Abfragen machen können, die auf einen Punkt rollen und dann das Band nur für begrenzte Entfernungen vor- und zurückbewegen.

Schnelle Abfragen

Bei dieser einen Tabelle liegt der nächste Fokus für jede vom Autorisierungsdienst empfangene Nutzlast darin, nur eine Abfrageausführung auszulösen. Diese Abfrage muss:

  1. Kontostand abrufen (ein Konto kann mehrere Karten haben)
  2. Erhalte den Kontostatus,
  3. Holen Sie sich die rollenden Grenzen der Karte im letzten Monat,
  4. Den letzten Status der Karte sowie ihre Optionen abrufen,
  5. Die mögliche vorherige Transaktion abrufen (bei mehreren verknüpften Transaktionen, z. B. im ersten Fall bei einer Tankstelle),
  6. Berechnen Sie den zulässigen Betrag (das Saldelta).
  7. Entscheiden, ob die Transaktion genehmigt, teilweise genehmigt oder abgelehnt werden soll
  8. Fügen Sie eine neue Transaktion ein oder geben Sie im Falle eines Idempotenzkonflikts die vorherige Transaktion zurück.

Acht Abfragen, um nur eine zu gebrauchen! Zum Glück bietet SQL viele Möglichkeiten, um dies zu erreichen. Wir verwenden Common Table Expressions (CTE), um die erforderlichen Daten abzurufen:

  • Einen für Kontostand und Status,
  • Eine für den letzten Betriebsmonat der Karte (einschließlich ihres letzten Status),
  • Zwei für Idempotenz, Suche nach dem letzten Monat CTE.

Wir verwenden dann eine Unterabfrage in der Von-Klausel für die Berechnung des zulässigen Betrags. Um die vorherigen Transaktionen einfügen oder zurückgeben zu können, verwenden wir eine Upsert-Konstruktion.

Die Implementierung des Entscheidungsbaums ist nur eine Frage sorgfältig entworfener CASE-Ausdrücke.

Nach einigen Feinabstimmungen antworten wir hier in weniger als 8 Millisekunden von Ende zu Ende des Autorisierungsdienstes.

Der Nachtisch: was wir gelernt haben, wie wir uns verbessern können

Sei schnell: Es muss nicht alles verbessert werden

Es gibt einige Indikatoren, die überwacht werden müssen, um schnell und rechtzeitig zu sein:

  • Wie schnell läuft der Code?
  • Wie viel Ressource verbraucht es?
  • Wie lange brauchen wir, um es zu schreiben?
  • Mit welchen Kosten halten wir es in Zukunft aufrecht?

Softwaremetriken

Die beiden ersten sind einfach: Mit den go-Tools ist das Ausführen eines Benchmarks und das Profilieren einer Anwendung einfach.

Hier ein Beispiel, wie wir sie einsetzen: Nachdem die Entwicklung des Protokoll-Handlers fast abgeschlossen ist, beginnen wir mit dem Benchmarking und Profilieren. Es dauert ca. 300 µs vom Eingang zum Ausgang (also insgesamt 600 µs). Das scheint in Ordnung zu sein. Aber was für eine Überraschung, die Analyse des Profiling-Ergebnisses zu analysieren, um zu sehen, dass die meiste Zeit in Sprintf-Aufrufen und Reflexionen verbracht wird!

Es stellt sich heraus, dass die von uns gewählte Logger-Bibliothek diese stark nutzt und den Prozess ständig verlangsamt. Das hat uns entschieden, von dort zu Uber's zap logger zu wechseln. Mit dem Fokus auf die Leistung haben wir es geschafft, auf die Zeit von 150 µs zurückzuschalten, die wir Ihnen zuvor vorgestellt haben.

Wir verwenden die gleichen Metriken, um unsere Implementierung zu analysieren: Für eine rechtzeitige Auslieferung haben wir keine Zeit, um jeden Teil der Anwendung zu optimieren. Wir arbeiten eher an einem Teil, profilieren ihn und konzentrieren uns auf den kritischen Pfad. Warum verlieren Sie 10 Stunden beim Schreiben einer brandneuen Funktion, wenn sie nur 1% der Zeit ausgeführt wird? Diese Zeit sollte besser für die Verbesserung der einen Funktion verwendet werden, die die meiste Zeit läuft.

Teamkennzahlen

Es ist leicht, sich beim Entwickeln in kleinen Details zu verlieren. Um auf unsere Ziele konzentriert zu bleiben, verwenden wir mehrere bewährte Verfahren:

  • Teilen Sie die Arbeit in kleine Einheiten auf: So haben wir immer ein klares und erreichbares Ziel. Außerdem sind Patches klein und leicht zu überprüfen.
  • Verwenden Sie visuelles Feedback: Mit Kanban (看板) behalten wir ständig den Überblick über die Aktivitäten, die Überprüfung, der Fortschritt oder der Rückstand. Die tägliche Diskussion hilft uns bei der Auswahl der richtigen Prioritäten.
  • Kontinuierliches Peer-Review: Nachdem wir die Aufgaben in kleine Einheiten aufgeteilt haben, können wir mehrmals am Tag versenden. Jedes Mal, wenn ein Entwickler eine Zusammenführungsanforderung erstellt, betrachtet er die noch offenen und überprüft, was die anderen getan haben.

Wir haben uns an diese Prinzipien gehalten und konnten innerhalb der erwarteten Verzögerung versenden. Wir begannen im April 2018 mit der Konzeption, und der Autorisierungsdienst hat alle QS bis Ende Dezember bestanden. Es ist eine sehr schnelle Implementierung und sehr wartungsfähig.

Fertig zum Servieren? Es gibt immer Raum für Verbesserungen

Können wir sagen "Ende"? Das System wird bereits von Betatestern verwendet. Es behandelt alle unsere erwarteten Anwendungsfälle. Es ist nahtlos mit dem Kartennetzwerk verbunden. Es ist schnell. Es ist genau. Es ist belastbar. Es ist ein starker Boden für alle Zahlungsmethoden, die Sie darauf werfen.

Wir können noch weiter gehen: Wir haben vorhin gesagt, dass die Anfragen sehr groß sind. Wir können sie aufteilen und mithilfe gespeicherter Prozeduren eine schöne Beschleunigung erzielen, indem auf die SQL-Kompilierung verzichtet wird.

Wir haben sehr darauf geachtet, Ihnen das robusteste Zahlungserlebnis zu bieten. Nutzen Sie Ihre Qonto-Karte überall!

Siehe auch

Was ich aus der Erstellung eines Videospiels mit etwas mehr als einem Jahr Erfahrung mit dem Programmieren gelernt habeSo richten Sie die NSFW-Inhaltserkennung mit Machine Learning einAmerikanische Frucht gegen chinesische Frucht?