Amazon QLDB-Treiberempfehlungen - Amazon Quantum Ledger Database (Amazon QLDB)

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Amazon QLDB-Treiberempfehlungen

In diesem Abschnitt werden bewährte Methoden zum Konfigurieren und Verwenden des Amazon QLDB-Treibers für jede unterstützte Sprache beschrieben. Bei den Codebeispielen handelt es sich um Java-Beispiele.

Diese Empfehlungen gelten für die meisten typischen Anwendungsfälle, aber nicht für alle. Verwenden Sie die folgenden Empfehlungen, wenn Sie glauben, dass sie für Ihre Anwendung geeignet sind.

Konfigurieren von QldbDriver Objekt

DieQldbDriverobject verwaltet Verbindungen zu Ihrem Ledger, indem ein Pool vonSitzungendie über Transaktionen hinweg wiederverwendet werden. EINSitzungstellt eine einzelne Verbindung zum Ledger dar. QLDB unterstützt eine aktiv laufende Transaktion pro Sitzung.

Wichtig

Bei älteren Treiberversionen befindet sich die Funktion zum Session-Pooling immer noch imPooledQldbDriverObjekt anstelle vonQldbDriveraus. Wenn Sie eine der folgenden Versionen verwenden, ersetzen Sie alle Erwähnungen vonQldbDrivermitPooledQldbDriverfür den Rest dieses Themas.

Treiber Version
Java 1.1.0oder früher
.NET 0.1.0-beta
Node.js 1.0.0-rc.1oder früher
Python 2.0.2oder früher

DiePooledQldbDriver-Objekt ist in der neuesten Version der Treiber veraltet. Es wird empfohlen, ein Upgrade auf die neueste Version durchzuführen und alle Instanzen vonPooledQldbDriverzuQldbDriveraus.

Konfiguration QldbDriver als globales Objekt

Um die Verwendung von Treibern und Sitzungen zu optimieren, müssen Sie sicherstellen, dass nur eine globale Instance des Treibers in Ihrer Anwendungs-Instance vorhanden ist. In Java können Sie beispielsweise Abhängigkeits-Injection-Frameworks wie Spring, Google Guice oder Dagger verwenden. Das folgende Codebeispiel zeigt, wie Sie QldbDriver als Singleton konfigurieren.

@Singleton public QldbDriver qldbDriver (AWSCredentialsProvider credentialsProvider, @Named(LEDGER_NAME_CONFIG_PARAM) String ledgerName) { QldbSessionClientBuilder builder = QldbSessionClient.builder(); if (null != credentialsProvider) { builder.credentialsProvider(credentialsProvider); } return QldbDriver.builder() .ledger(ledgerName) .transactionRetryPolicy(RetryPolicy .builder() .maxRetries(3) .build()) .sessionClientBuilder(builder) .build(); }

Konfigurieren der Wiederholungsversuche

Der Treiber wiederholt Transaktionen automatisch, wenn häufige vorübergehende Ausnahmen (z. B.SocketTimeoutExceptionoderNoHttpResponseException) treten auf. Um die maximale Anzahl an Wiederholungsversuchen festzulegen, können Sie dasmaxRetries-Parameter destransactionRetryPolicyKonfigurationsobjekt beim Erstellen einer Instanz vonQldbDriveraus. (Verwenden Sie für ältere Treiberversionen, wie im vorherigen Abschnitt aufgeführt,retryLimit-Parameter vonPooledQldbDriver.)

Der Standardwert von maxRetries ist 4.

Bei clientseitigen Fehlern wie InvalidParameterException sind keine wiederholten Versuche möglich. Wenn sie auftreten, wird die Transaktion abgebrochen, die Sitzung wird an den Pool zurückgegeben, und eine Ausnahme wird für den Client des Treibers ausgelöst.

Konfigurieren der maximalen Anzahl gleichzeitiger Sitzungen und Transaktionen

Die maximale Anzahl an Ledger-Sitzungen, die von einer Instance vonQldbDriverTransaktionen auszuführen, wird durch seinemaxConcurrentTransactions-Parameter. (Für ältere Treiberversionen, wie im vorherigen Abschnitt aufgeführt, wird dies durch diepoolLimit-Parameter vonPooledQldbDriver.)

Dieser Grenzwert muss größer als Null und kleiner oder gleich der maximalen Anzahl offener HTTP-Verbindungen sein, die der Sitzungs-Client zulässt (definiert nach Maßgabe des jeweiligenAWS-SDKS. In Java wird die maximale Anzahl an Verbindungen beispielsweise im Objekt ClientConfiguration festgelegt.

Der Standardwert vonmaxConcurrentTransactionsist die maximale Verbindungseinstellung IhresAWS-SDKS.

Wenn Sie QldbDriver in der Anwendung konfigurieren, beachten Sie die folgenden Aspekte hinsichtlich der Skalierung:

  • Der Pool sollte immer mindestens so viele Sitzungen enthalten, wie gleichzeitig ausgeführte Transaktionen geplant sind.

  • In einem Multi-Thread-Modell, in dem ein Supervisor-Thread an Worker-Threads delegiert, sollte der Treiber mindestens so viele Sitzungen enthalten, wie Worker-Threads vorliegen. Andernfalls warten Threads bei Spitzenlast nacheinander auf eine verfügbare Sitzung.

  • Das Service-Limit für gleichzeitige aktive Sitzungen pro Ledger ist in Kontingente und Limits in Amazon QLDB definiert. Stellen Sie sicher, dass Sie nicht mehr als dieses Limit an gleichzeitigen Sitzungen für einen einzelnen Ledger über alle Clients konfigurieren.

Erneuter Versuch bei Ausnahmen

Beachten Sie beim erneuten Versuch bei Ausnahmen, die in QLDB auftreten, die folgenden Empfehlungen.

Wiederholen von OccConflictException

Optimistic Concurrency Control(OCC) Konflikte treten auf, wenn sich die Daten, auf die Transaktion zugreift, seit dem Beginn der Transaktion geändert haben. QLDB löst diese Ausnahme aus, während sie versucht, die Transaktion zu übernehmen. Der Fahrer versucht die Transaktion bis zu so oft wiemaxRetriesist konfiguriert.

Weitere Informationen zu OCC und Best Practices zur Verwendung von Indizes zur Begrenzung von OCC-Konflikten finden Sie unterAmazon QLDB-Nebenläufigkeit von Amazon QLDB-Nebenläufigkeitaus.

Andere Ausnahmen außerhalb von QlDbDriver erneut versuchen

Um eine Transaktion außerhalb des Treibers zu wiederholen, wenn benutzerdefinierte, anwendungsdefinierte Ausnahmen während der Laufzeit ausgelöst werden, müssen Sie die Transaktion umbrechen. Der folgende Code zeigt beispielsweise, wie Sie dasReslience4J-Bibliothek, um eine Transaktion in QLDB erneut zu versuchen.

private final RetryConfig retryConfig = RetryConfig.custom() .maxAttempts(MAX_RETRIES) .intervalFunction(IntervalFunction.ofExponentialRandomBackoff()) // Retry this exception .retryExceptions(InvalidSessionException.class, MyRetryableException.class) // But fail for any other type of exception extended from RuntimeException .ignoreExceptions(RuntimeException.class) .build(); // Method callable by a client public void myTransactionWithRetries(Params params) { Retry retry = Retry.of("registerDriver", retryConfig); Function<Params, Void> transactionFunction = Retry.decorateFunction( retry, parameters -> transactionNoReturn(params)); transactionFunction.apply(params); } private Void transactionNoReturn(Params params) { try (driver.execute(txn -> { // Transaction code }); } return null; }
Anmerkung

Das erneute Versuch einer Transaktion außerhalb des QLDB-Treibers hat einen Multiplikatoreffekt. Zum Beispiel, wennQldbDriverist so konfiguriert, dass drei Wiederholungen versucht werden, und die benutzerdefinierte Wiederholungslogik wiederholt sich ebenfalls drei Wiederholungen, dieselbe Transaktion kann bis zu neun Mal wiederholt werden.

Transaktionen idempotent machen

Als bewährte Methode sollten Sie Ihre Schreibtransaktionen idempotent machen, um unerwartete Nebenwirkungen im Falle von Wiederholungen zu vermeiden. Eine Transaktion istidempotentwenn es mehrmals laufen kann und jedes Mal identische Ergebnisse erzielen kann.

Weitere Informationen hierzu finden Sie unter Amazon QLDB-Nebenläufigkeit von Amazon QLDB-Nebenläufigkeit.

Optimierung der Leistung

Um die Leistung beim Ausführen von Transaktionen mit dem Treiber zu optimieren, sollten Sie Folgendes berücksichtigen:

  • DieexecuteBetrieb macht immer mindestens dreiSendCommand-Aufrufe der API an QLDB, die folgende Befehle enthalten:

    1. StartTransaction

    2. ExecuteStatement

      Dieser Befehl wird für jede PartiQL-Anweisung aufgerufen, die Sie imexecuteblockieren.

    3. CommitTransaction

    Berücksichtigen Sie die Gesamtzahl der auszuführenden API-Aufrufe, wenn Sie die Gesamt-Workload der Anwendung kalkulieren.

  • Grundsätzlich empfehlen wir, mit einem Single-Thread-Writer zu beginnen und Transaktionen zu optimieren, indem mehrere Anweisungen in einer Transaktion zusammengefasst werden. Maximieren Sie die Kontingente für Transaktionsgröße, Dokumentgröße und Anzahl der Dokumente pro Transaktion gemäß der Definition in Kontingente und Limits in Amazon QLDB.

  • Wenn das Batching für große Transaktionslasten nicht ausreicht, können Sie es mit Multi-Threading versuchen, indem Sie weitere Writer hinzufügen. Sie sollten jedoch sorgfältig die Anwendungsanforderungen an die Dokument- und Transaktionssequenzierung im Hinblick auf die damit einhergehende Komplexität abwägen.

Ausführen mehrerer Anweisungen pro Transaktion

Wie in dervorheriger Abschnitt, können Sie mehrere Anweisungen pro Transaktion ausführen, um die Leistung Ihrer Anwendung zu optimieren. Im folgenden Codebeispiel fragen Sie eine Tabelle ab und aktualisieren dann ein Dokument in dieser Tabelle innerhalb einer Transaktion. Hierzu übergeben Sie einen Lambda-Ausdruck anexecuteverwenden.

Java
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. public static boolean InsureCar(QldbDriver qldbDriver, final String vin) { final IonSystem ionSystem = IonSystemBuilder.standard().build(); final IonString ionVin = ionSystem.newString(vin); return qldbDriver.execute(txn -> { Result result = txn.execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", ionVin); if (!result.isEmpty()) { txn.execute("UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin); return true; } return false; }); }
.NET
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. public static async Task<bool> InsureVehicle(IAsyncQldbDriver driver, string vin) { ValueFactory valueFactory = new ValueFactory(); IIonValue ionVin = valueFactory.NewString(vin); return await driver.Execute(async txn => { // Check if the vehicle is insured. Amazon.QLDB.Driver.IAsyncResult result = await txn.Execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", ionVin); if (await result.CountAsync() > 0) { // If the vehicle is not insured, insure it. await txn.Execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin); return true; } return false; }); }
Go
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. func InsureCar(driver *qldbdriver.QLDBDriver, vin string) (bool, error) { insured, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin) if err != nil { return false, err } hasNext := result.Next(txn) if !hasNext && result.Err() != nil { return false, result.Err() } if hasNext { _, err = txn.Execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin) if err != nil { return false, err } return true, nil } return false, nil }) if err != nil { panic(err) } return insured.(bool), err }
Node.js
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. async function insureCar(driver: QldbDriver, vin: string): Promise<boolean> { return await driver.executeLambda(async (txn: TransactionExecutor) => { const results: dom.Value[] = (await txn.execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin)).getResultList(); if (results.length > 0) { await txn.execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin); return true; } return false; }); };
Python
# This code snippet is intentionally trivial. In reality you wouldn't do this because you'd # set your UPDATE to filter on vin and insured, and check if you updated something or not. def do_insure_car(transaction_executor, vin): cursor = transaction_executor.execute_statement( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin) first_record = next(cursor, None) if first_record: transaction_executor.execute_statement( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin) return True else: return False def insure_car(qldb_driver, vin_to_insure): return qldb_driver.execute_lambda( lambda executor: do_insure_car(executor, vin_to_insure))

Die des Fahrersexecute-Operation startet implizit eine Sitzung und eine Transaktion in dieser Sitzung. Jede Anweisung, die Sie im Lambda-Ausdruck ausführen, wird in die Transaktion eingeschlossen. Nachdem alle Anweisungen ausgeführt wurden, schreibt der Treiber die Transaktion automatisch fest. Wenn eine Anweisung fehlschlägt, nachdem das automatische Wiederholungslimit erschöpft ist, wird die Transaktion abgebrochen.

Verbreiten von Ausnahmeregelungen in einer Transaktion

Wenn Sie mehrere Anweisungen pro Transaktion ausführen, empfehlen wir im Allgemeinen nicht, dass Sie Ausnahmen innerhalb der Transaktion abfangen und übergeben.

Beispielsweise fängt das folgende Programm in Java jede Instance von RuntimeException ab, protokolliert den Fehler und fährt fort. Dieses Codebeispiel wird als schlechte Praxis angesehen, da die Transaktion sogar dann erfolgreich ist, wenn die UPDATE-Anweisung fehlschlägt. Daher geht der Client möglicherweise davon aus, dass das Update erfolgreich war, auch dies nicht der Fall war.

Warnung

Verwenden Sie nicht dieses Codebeispiel. Er wird bereitgestellt, um ein Anti-Pattern-Beispiel zu zeigen, das als schlechte Praxis gilt.

// DO NOT USE this code example because it is considered bad practice public static void main(final String... args) { ConnectToLedger.getDriver().execute(txn -> { final Result selectTableResult = txn.execute("SELECT * FROM Vehicle WHERE VIN ='123456789'"); // Catching an error inside the transaction is an anti-pattern because the operation might // not succeed. // In this example, the transaction succeeds even when the update statement fails. // So, the client might assume that the update succeeded when it didn't. try { processResults(selectTableResult); String model = // some code that extracts the model final Result updateResult = txn.execute("UPDATE Vehicle SET model = ? WHERE VIN = '123456789'", Constants.MAPPER.writeValueAsIonValue(model)); } catch (RuntimeException e) { log.error("Exception when updating the Vehicle table {}", e.getMessage()); } }); log.info("Vehicle table updated successfully."); }

Propagiere stattdessen die Ausnahme (sprenge nach oben). Wenn ein Teil der Transaktion fehlschlägt, lassen Sieexecutebricht die Transaktion ab, damit der Kunde die Ausnahme entsprechend behandeln kann.