Recommandations de pilotes Amazon QLDB - Amazon Quantum Ledger Database (Amazon QLDB)

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 de pilotes Amazon QLDB

Cette section décrit les meilleures pratiques de configuration et d'utilisation du pilote Amazon QLDB pour n'importe quelle langue prise en charge. Les exemples de code fournis concernent spécifiquement Java.

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

Configuration de QldbDriver objet

LeQldbDrivergère les connexions à votre livre en maintenant un pool desessionsqui sont réutilisés dans toutes les transactions. UNsessionreprésente une connexion unique au grand livre. QLDB prend en charge une transaction active par session.

Important

Pour les anciennes versions de pilotes, la fonctionnalité de pool de sessions se trouve toujours dans lePooledQldbDriverobjet et non deQldbDriver. Si vous utilisez l'une des versions suivantes, remplacez toutes les mentions deQldbDriveravecPooledQldbDriverpour le reste de ce sujet.

Pilote Version
Java 1.1.0ou antérieures
.NET 0.1.0-beta
Node.js 1.0.0-rc.1ou antérieures
Python 2.0.2ou antérieures

LePooledQldbDriverl'objet est obsolète dans la dernière version des pilotes. Nous vous recommandons de procéder à une mise à niveau vers la dernière version et de convertir toutes les instances dePooledQldbDriverpourQldbDriver.

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 utiliserInjection de dépendanceframeworks tels quePrintemps,Google Guide, ouDague. L'exemple de code suivant montre comment configurer.QldbDriveren 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 pilote retente automatiquement les transactions lorsque des exceptions transitoires courantes (telles queSocketTimeoutExceptionouNoHttpResponseException) se produisent. Pour définir le nombre maximal de nouvelles tentatives, vous pouvez utiliser l'optionmaxRetriesparamètre de latransactionRetryPolicyobjet de configuration lors de la création d'une instance deQldbDriver. (Pour les anciennes versions de pilotes répertoriées dans la section précédente, utilisez leretryLimitparamètre dePooledQldbDriver.)

La valeur par défaut de maxRetries est 4.

Erreurs côté du client, telles queInvalidParameterExceptionne peut pas être réessayé. Lorsqu'elles se produisent, la transaction est interrompue, la session est renvoyée au pool et l'exception est renvoyée au client du pilote.

Configuration du nombre maximal de sessions et de transactions simultanées

Le nombre maximal de sessions de livres utilisées par une instance deQldbDriverpour exécuter des transactions est défini par sonmaxConcurrentTransactions  Paramètre . (Pour les anciennes versions de pilotes répertoriées dans la section précédente, cela est défini par lepoolLimitparamètre dePooledQldbDriver.)

Cette limite doit être supérieure à zéro et inférieure ou égale au nombre maximal de connexions HTTP ouvertes que le client de session autorise, tel que défini par le paramètre spécifique.AWSSDK. Par exemple, en Java, le nombre maximal de connexions est défini dans leClientConfigurationobjet.

La valeur par défaut demaxConcurrentTransactionsest le paramètre de connexion maximal de votreAWSSDK.

Lorsque vous configurez leQldbDriverdans votre application, prenez en compte les considérations de dimensionnement suivantes :

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

  • Dans un modèle multithread où un thread de superviseur délègue à des threads de travail, le pilote doit avoir au moins autant de sessions que le nombre de threads de travail. Sinon, à la charge maximale, les threads seront en attente d'une session disponible.

  • La limite de service des sessions actives simultanées par livre est définie dansQuotas et limites d'Amazon QLDB. Assurez-vous que vous ne configurez pas plus que cette limite de sessions simultanées pour être utilisées pour un seul livre sur tous les clients.

Nouvelles tentatives en cas d'exception

Lorsque vous tentez de nouveau les exceptions qui se produisent dans QLDB, tenez compte des recommandations suivantes.

Nouvelles tentatives d'exception OccConflictException

Contrôle optimiste de la concurrenceLes exceptions de conflit (OCC) se produisent lorsque les données auxquelles la transaction accède ont changé depuis le début de la transaction. QLDB lance cette exception en essayant de valider la transaction. Le conducteur retente la transaction jusqu'à autant de fois quemaxRetriesest configuré.

Pour plus d'informations sur OCC et sur les meilleures pratiques d'utilisation des index pour limiter les conflits OCC, voirModèle de mise QLDB simultansimultansimultansimultansimultansimultansimultansimultansimultanéité.

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 lancées pendant l'exécution, vous devez encapsuler la transaction. Par exemple, en Java, le code suivant montre comment utiliser l'optionRélience 4Jpour réessayer une transaction dans 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

La nouvelle tentative d'une transaction en dehors du pilote QLDB a un effet multiplicateur. Par exemple, siQldbDriverest configuré pour réessayer trois fois, et la logique de nouvelle tentative personnalisée à nouveau trois fois, la même transaction peut être retentée jusqu'à neuf fois.

Indempotence des transactions

Comme meilleure pratique, rendez vos transactions d'écriture idépotentes afin d'éviter tout effet secondaire inattendu en cas de nouvelles tentatives. Une transaction estidempotentss'il peut s'exécuter plusieurs fois et produire des résultats identiques à chaque fois.

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

Optimisation des performances

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

  • Leexecutel'opération fait toujours un minimum de troisSendCommandAppels API vers QLDB, y compris les commandes suivantes :

    1. StartTransaction

    2. ExecuteStatement

      Cette commande est appelée pour chaque instruction PartiQL que vous exécutez dans leexecuteblock.

    3. CommitTransaction

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

  • En général, nous recommandons de commencer par un graveur monothread et d'optimiser les transactions en regroupant plusieurs instructions au sein d'une seule transaction. Maximisez les quotas sur la taille des transactions, la taille du document et le nombre de documents par transaction, comme défini dansQuotas et limites d'Amazon QLDB.

  • Si le traitement par lots n'est pas suffisant pour les charges de transactions volumineuses, vous pouvez essayer le multithreading en ajoutant des rédacteurs supplémentaires. Toutefois, vous devez examiner attentivement les exigences de votre application pour le séquençage des documents et des transactions, ainsi que la complexité supplémentaire que cela introduit.

Exécution de plusieurs instructions par transaction

Comme décrit dans lasection précédente, vous pouvez exécuter plusieurs relevés par transaction pour optimiser les performances de votre application. Dans l'exemple de code suivant, vous interrogez une table, puis mettez à jour un document de cette table dans une transaction. Pour ce faire, vous transmettez une expression lambda à laexecute.

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

Le chauffeurexecutel'opération démarre implicitement une session et une transaction dans cette session. Chaque instruction que vous exécutez dans l'expression lambda est encapsulée dans la transaction. Une fois toutes les instructions exécutées, le pilote valide automatiquement la transaction. Si une instruction échoue après que la limite de nouvelle tentative automatique est épuisée, la transaction est interrompue.

Propager les exceptions dans une transaction

Lorsque vous exécutez plusieurs instructions par transaction, nous ne recommandons généralement pas d'catch et d'avaler des exceptions au sein de la transaction.

Par exemple, en Java, le programme suivant capture 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 lorsque leUPDATEl'instruction échoue. Par conséquent, le client peut 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 anti-pattern 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 leexecutel'opération interrompt la transaction afin que le client puisse gérer l'exception en conséquence.