Empfehlungen Amazon QLDB Amazon-Fahrer - Amazon Quantum Ledger-Datenbank (AmazonQLDB)

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.

Empfehlungen Amazon QLDB Amazon-Fahrer

Wichtig

Hinweis zum Ende des Supports: Bestandskunden können Amazon QLDB bis zum Ende des Supports am 31.07.2025 nutzen. Weitere Informationen finden Sie unter Migrieren eines Amazon QLDB Ledgers zu Amazon Aurora SQL Postgre.

In diesem Abschnitt werden bewährte Methoden für die Konfiguration und Verwendung des QLDB Amazon-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.

Das QldbDriver Objekt konfigurieren

Das QldbDriver Objekt verwaltet Verbindungen zu Ihrem Ledger, indem es einen Pool von Sitzungen verwaltet, die transaktionsübergreifend wiederverwendet werden. Eine Sitzung stellt eine einzelne Verbindung zum Ledger dar. QLDBunterstützt eine aktiv laufende Transaktion pro Sitzung.

Wichtig

Bei älteren Treiberversionen befindet sich die Session-Pooling-Funktionalität immer noch im PooledQldbDriver Objekt stattQldbDriver. Wenn Sie eine der folgenden Versionen verwenden, ersetzen Sie alle Erwähnungen von QldbDriver durch PooledQldbDriver fü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

Das PooledQldbDriver Objekt ist in der neuesten Version der Treiber veraltet. Wir empfehlen, auf die neueste Version zu aktualisieren und alle Instanzen von PooledQldbDriver to zu zu zu konvertieren. QldbDriver

QldbDriver Als globales Objekt konfigurieren

Um die Verwendung von Treibern und Sitzungen zu optimieren, stellen Sie sicher, dass in Ihrer Anwendungsinstanz nur eine globale Instanz des Treibers 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 versucht automatisch, Transaktionen zu wiederholen, wenn häufig auftretende vorübergehende Ausnahmen (wie SocketTimeoutException oderNoHttpResponseException) auftreten. Um die maximale Anzahl von Wiederholungsversuchen festzulegen, können Sie den maxRetries Parameter des transactionRetryPolicy Konfigurationsobjekts verwenden, wenn Sie eine Instanz von erstellen. QldbDriver (Verwenden Sie für ältere Treiberversionen, wie im vorherigen Abschnitt aufgeführt, den 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 Sie die maximale Anzahl gleichzeitiger Sitzungen und Transaktionen

Die maximale Anzahl von Ledger-Sitzungen, die von einer Instanz von QldbDriver zur Ausführung von Transaktionen verwendet werden, wird durch ihren maxConcurrentTransactions Parameter definiert. (Für ältere Treiberversionen, wie im vorherigen Abschnitt aufgeführt, wird dies durch den poolLimit Parameter von definiertPooledQldbDriver.)

Dieser Grenzwert muss größer als Null und kleiner oder gleich der maximalen Anzahl offener HTTP Verbindungen sein, die der Sitzungsclient gemäß der spezifischen Definition zulässt AWS SDK. In Java ist beispielsweise die maximale Anzahl von Verbindungen im ClientConfigurationObjekt festgelegt.

Der Standardwert von maxConcurrentTransactions ist die maximale Verbindungseinstellung Ihres AWS SDK.

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

  • Ihr Pool sollte immer mindestens so viele Sitzungen haben wie die Anzahl der gleichzeitig laufenden Transaktionen, die Sie planen.

  • In einem Multithread-Modell, bei dem ein Supervisor-Thread an Worker-Threads delegiert, sollte der Treiber mindestens so viele Sitzungen haben wie die Anzahl der Worker-Threads. 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 bei 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.

Wiederholter Versuch bei Ausnahmen

Beachten Sie beim erneuten Versuch mit Ausnahmen, die in auftretenQLDB, die folgenden Empfehlungen.

Versuchen Sie es erneut mit OccConflictException

Konfliktausnahmen bei Optimistic Concurrency Control (OCC) treten auf, wenn sich die Daten, auf die die Transaktion zugreift, seit Beginn der Transaktion geändert haben. QLDBlöst diese Ausnahme aus, während versucht wird, die Transaktion zu bestätigen. Der Treiber wiederholt die Transaktion bis zu so oft, wie konfiguriert maxRetries ist.

Weitere Informationen OCC und bewährte Methoden zur Verwendung von Indizes zur Begrenzung von OCC Konflikten finden Sie unter. QLDBAmazon-Parallelitätsmodell

Wiederholter Versuch bei anderen Ausnahmen außerhalb von QldbDriver

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

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

Der Wiederholungsversuch einer Transaktion außerhalb des Treibers hat einen MultiplikatoreffektQLDB. Wenn beispielsweise die Konfiguration so konfiguriert QldbDriver ist, dass sie dreimal wiederholt wird, und die benutzerdefinierte Wiederholungslogik ebenfalls dreimal versucht, kann dieselbe Transaktion bis zu neun Mal wiederholt werden.

Dadurch werden Transaktionen idempotent

Als bewährte Methode sollten Sie Ihre Schreibtransaktionen idempotent machen, um unerwartete Nebenwirkungen im Falle von Wiederholungen zu vermeiden. Eine Transaktion ist idempotent, wenn sie mehrfach ausgeführt werden kann und jedes Mal zu identischen Ergebnissen führt.

Weitere Informationen hierzu finden Sie unter QLDBAmazon-Parallelitätsmodell.

Optimierung der Leistung

Um die Leistung zu optimieren, wenn Sie Transaktionen mit dem Treiber ausführen, sollten Sie die folgenden Überlegungen berücksichtigen:

  • Der execute Vorgang führt immer mindestens drei SendCommand API Aufrufe durchQLDB, einschließlich der folgenden Befehle:

    1. StartTransaction

    2. ExecuteStatement

      Dieser Befehl wird für jede PartiQL-Anweisung aufgerufen, die Sie in dem execute Block ausführen.

    3. CommitTransaction

    Berücksichtigen Sie bei der Berechnung der Gesamtauslastung Ihrer Anwendung die Gesamtzahl der API Aufrufe.

  • 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 bei Amazon QLDB.

  • Wenn Batching für große Transaktionslasten nicht ausreicht, können Sie Multithreading ausprobieren, indem Sie zusätzliche 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.

Mehrere Anweisungen pro Transaktion ausführen

Wie im vorherigen Abschnitt beschrieben, 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. Dazu übergeben Sie der execute Operation einen Lambda-Ausdruck.

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 execute Operation des Treibers startet implizit eine Sitzung und eine Transaktion in dieser Sitzung. Jede Anweisung, die Sie im Lambda-Ausdruck ausführen, ist 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 generell nicht, Ausnahmen innerhalb der Transaktion abzufangen und zu schlucken.

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 dieses Codebeispiel nicht. 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."); }

Propagieren Sie stattdessen die Ausnahme (lassen Sie sie auftauchen). Wenn ein Teil der Transaktion fehlschlägt, lassen Sie den execute Vorgang die Transaktion abbrechen, damit der Client die Ausnahme entsprechend behandeln kann.