Recommandations pour les QLDB chauffeurs Amazon - Base de données Amazon Quantum Ledger (AmazonQLDB)

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Recommandations pour les QLDB chauffeurs Amazon

Important

Avis de fin de support : les clients existants pourront utiliser Amazon QLDB jusqu'à la fin du support le 31 juillet 2025. Pour plus de détails, consultez Migrer un Amazon QLDB Ledger vers Amazon Aurora SQL Postgre.

Cette section décrit les meilleures pratiques en matière de configuration et d'utilisation du QLDB pilote Amazon pour toutes les langues prises en charge. Les exemples de code fournis sont spécifiques à Java.

Ces recommandations s'appliquent à la plupart des cas d'utilisation courants, mais une solution unique ne convient pas à tous. Utilisez les recommandations suivantes comme bon vous semble pour votre application.

Configuration de l' QldbDriver objet

L'QldbDriverobjet gère les connexions à votre registre en gérant un pool de sessions qui sont réutilisées dans les transactions. Une session représente une connexion unique au registre. QLDBprend en charge une transaction active par session.

Important

Pour les anciennes versions du pilote, la fonctionnalité de regroupement de sessions se trouve toujours dans l'PooledQldbDriverobjet au lieu deQldbDriver. Si vous utilisez l'une des versions suivantes, remplacez toute mention de QldbDriver par PooledQldbDriver dans le reste de cette rubrique.

Pilote Version
Java 1.1.0ou plus tôt
.NET 0.1.0-beta
Node.js 1.0.0-rc.1ou plus tôt
Python 2.0.2ou plus tôt

L'PooledQldbDriverobjet est obsolète dans la dernière version des pilotes. Nous vous recommandons de passer à la dernière version et de convertir toutes les instances de PooledQldbDriver versQldbDriver.

Configuration QldbDriver en tant qu'objet global

Pour optimiser l'utilisation des pilotes et des sessions, assurez-vous qu'une seule instance globale du pilote existe dans votre instance d'application. Par exemple, en Java, vous pouvez utiliser des frameworks d'injection de dépendances tels que Spring, Google Guice ou Dagger. L'exemple de code suivant montre comment configurer QldbDriver en tant que singleton.

@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(); }

Configurer les tentatives de nouvelle tentative

Le conducteur réessaie automatiquement les transactions lorsque des exceptions transitoires courantes (telles que SocketTimeoutException ouNoHttpResponseException) se produisent. Pour définir le nombre maximal de tentatives, vous pouvez utiliser le maxRetries paramètre de l'objet de transactionRetryPolicy configuration lors de la création d'une instance deQldbDriver. (Pour les anciennes versions du pilote répertoriées dans la section précédente, utilisez le retryLimit paramètre dePooledQldbDriver.)

La valeur par défaut de maxRetries est 4.

Erreurs côté client, telles que l'impossibilité InvalidParameterException de réessayer. Lorsqu'elles se produisent, la transaction est abandonnée, la session est renvoyée au pool et l'exception est renvoyée au client du pilote.

Configurer le nombre maximum de sessions et de transactions simultanées

Le nombre maximum de sessions de registre utilisées par une instance de QldbDriver pour exécuter des transactions est défini par son maxConcurrentTransactions paramètre. (Pour les anciennes versions du pilote répertoriées dans la section précédente, cela est défini par le poolLimit paramètre dePooledQldbDriver.)

Cette limite doit être supérieure à zéro et inférieure ou égale au nombre maximum de HTTP connexions ouvertes autorisé par le client de session, tel que défini par le spécifique AWS SDK. Par exemple, en Java, le nombre maximum de connexions est défini dans l'ClientConfigurationobjet.

La valeur par défaut de maxConcurrentTransactions est le paramètre de connexion maximal de votre AWS SDK.

Lorsque vous configurez le QldbDriver dans votre application, tenez compte des considérations de dimensionnement suivantes :

  • Votre pool doit toujours comporter au moins autant de sessions que le nombre de transactions simultanées que vous prévoyez d'avoir.

  • Dans un modèle multithread où un thread de supervision délègue aux threads de travail, le pilote doit avoir au moins autant de sessions que le nombre de threads de travail. Sinon, au pic de charge, les threads feront la queue pour une session disponible.

  • La limite de service des sessions actives simultanées par registre est définie dansQuotas et limites sur Amazon QLDB. Assurez-vous de ne pas configurer plus que cette limite de sessions simultanées à utiliser pour un seul registre pour tous les clients.

Réessayer des exceptions

Lorsque vous réessayez d'effectuer des exceptions survenant dansQLDB, tenez compte des recommandations suivantes.

Réessayer OccConflictException

Des exceptions de conflit liées au contrôle de simultanéité optimiste (OCC) se produisent lorsque les données auxquelles la transaction accède ont changé depuis le début de la transaction. QLDBlance cette exception lors de la tentative de validation de la transaction. Le pilote tente de nouveau la transaction autant de fois que cela maxRetries est configuré.

Pour plus d'informations OCC et les meilleures pratiques relatives à l'utilisation des index afin de limiter OCC les conflits, consultezModèle de QLDB simultanéité Amazon.

Réessayer d'autres exceptions en dehors de QldbDriver

Pour réessayer une transaction en dehors du pilote lorsque des exceptions personnalisées définies par l'application sont émises pendant l'exécution, vous devez encapsuler la transaction. Par exemple, en Java, le code suivant montre comment utiliser la bibliothèque Reslience4J pour réessayer une transaction. 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; }
Note

Réessayer une transaction en dehors du QLDB driver a un effet multiplicateur. Par exemple, si elle QldbDriver est configurée pour réessayer trois fois et que la logique de nouvelle tentative personnalisée tente également trois fois, la même transaction peut être réessayée jusqu'à neuf fois.

Rendre les transactions idempotentes

Il est recommandé de rendre vos transactions d'écriture idempotentes afin d'éviter tout effet secondaire inattendu en cas de nouvelle tentative. Une transaction est idempotente si elle peut être exécutée plusieurs fois et produire des résultats identiques à chaque fois.

Pour en savoir plus, veuillez consulter la section Modèle de QLDB simultanéité Amazon.

Optimisation des performances

Pour optimiser les performances lorsque vous exécutez des transactions à l'aide du pilote, tenez compte des points suivants :

  • L'executeopération effectue toujours au moins trois SendCommand API appels àQLDB, y compris les commandes suivantes :

    1. StartTransaction

    2. ExecuteStatement

      Cette commande est invoquée pour chaque instruction partiQL que vous exécutez dans le execute bloc.

    3. CommitTransaction

    Tenez compte du nombre total d'APIappels effectués lorsque vous calculez la charge de travail globale de votre application.

  • En général, nous recommandons de commencer par un rédacteur à fil unique et d'optimiser les transactions en regroupant plusieurs instructions au sein d'une seule transaction. Maximisez les quotas relatifs à la taille des transactions, à la taille des documents et au nombre de documents par transaction, comme défini dansQuotas et limites sur Amazon QLDB.

  • Si le traitement par lots n'est pas suffisant pour des charges de transactions importantes, vous pouvez essayer le multithreading en ajoutant des rédacteurs supplémentaires. Cependant, vous devez examiner attentivement les exigences de votre demande en matière de séquençage des documents et des transactions et la complexité supplémentaire que cela entraîne.

Exécution de plusieurs relevés par transaction

Comme décrit dans la section précédente, vous pouvez exécuter plusieurs instructions par transaction afin d'optimiser les performances de votre application. Dans l'exemple de code suivant, vous interrogez une table, puis vous mettez à jour un document de cette table dans le cadre d'une transaction. Pour ce faire, transmettez une expression lambda à l'executeopération.

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))

L'executeopération du pilote démarre implicitement une session et une transaction au cours de cette session. Chaque instruction que vous exécutez dans l'expression lambda est intégrée à la transaction. Une fois toutes les instructions exécutées, le pilote valide automatiquement la transaction. Si une instruction échoue une fois la limite de tentatives automatique épuisée, la transaction est abandonnée.

Propager les exceptions dans une transaction

Lorsque vous exécutez plusieurs relevés par transaction, nous vous déconseillons généralement de détecter et d'avaler les exceptions au sein de la transaction.

Par exemple, en Java, le programme suivant détecte n'importe quelle instance deRuntimeException, enregistre l'erreur et continue. Cet exemple de code est considéré comme une mauvaise pratique car la transaction réussit même en cas d'échec de l'UPDATEinstruction. Le client peut donc supposer que la mise à jour a réussi alors que ce n'est pas le cas.

Avertissement

N'utilisez pas cet exemple de code. Il est fourni pour montrer un exemple d'anti-modèle considéré comme une mauvaise pratique.

// 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."); }

Propagez plutôt l'exception (bulle vers le haut). Si une partie de la transaction échoue, laissez l'executeopération abandonner la transaction afin que le client puisse gérer l'exception en conséquence.