Consigli per i QLDB driver Amazon - Database Amazon Quantum Ledger (Amazon) QLDB

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Consigli per i QLDB driver Amazon

Importante

Avviso di fine del supporto: i clienti esistenti potranno utilizzare Amazon QLDB fino alla fine del supporto il 31/07/2025. Per ulteriori dettagli, consulta Migrare un Amazon QLDB Ledger ad Amazon Aurora Postgre. SQL

Questa sezione descrive le migliori pratiche per configurare e utilizzare il QLDB driver Amazon per qualsiasi lingua supportata. Gli esempi di codice forniti sono specifici per Java.

Questi consigli si applicano alla maggior parte dei casi d'uso comuni, ma un'unica soluzione non è adatta a tutti. Utilizza i seguenti consigli come ritieni opportuno per la tua applicazione.

Configurazione dell'oggetto QldbDriver

L'QldbDriveroggetto gestisce le connessioni al registro gestendo un pool di sessioni che vengono riutilizzate tra le transazioni. Una sessione rappresenta una singola connessione al registro. QLDBsupporta una transazione in esecuzione attiva per sessione.

Importante

Per le versioni precedenti dei driver, la funzionalità di pool delle sessioni è ancora presente nell'PooledQldbDriveroggetto anzichéQldbDriver. Se utilizzi una delle seguenti versioni, sostituisci le eventuali menzioni di QldbDriver con PooledQldbDriver per il resto di questo argomento.

Driver Versione
Java 1.1.0o precedente
.NET 0.1.0-beta
Node.js 1.0.0-rc.1o prima
Python 2.0.2o prima

L'PooledQldbDriveroggetto è obsoleto nella versione più recente dei driver. Si consiglia di eseguire l'aggiornamento alla versione più recente e di convertire tutte le istanze di to. PooledQldbDriver QldbDriver

Configura QldbDriver come oggetto globale

Per ottimizzare l'uso di driver e sessioni, assicuratevi che nell'istanza dell'applicazione esista solo un'istanza globale del driver. Ad esempio, in Java, potete utilizzare framework di iniezione delle dipendenze come Spring, Google Guice o Dagger. Il seguente esempio di codice mostra come configurare come singleton. QldbDriver

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

Configura i tentativi di riprova

Il driver ritenta automaticamente le transazioni quando si verificano eccezioni transitorie comuni (come o). SocketTimeoutException NoHttpResponseException Per impostare il numero massimo di tentativi, è possibile utilizzare il maxRetries parametro dell'oggetto di transactionRetryPolicy configurazione durante la creazione di un'istanza di. QldbDriver (Per le versioni precedenti dei driver elencate nella sezione precedente, utilizzate il retryLimit parametro diPooledQldbDriver.)

Il valore predefinito di maxRetries è 4.

Errori sul lato client, ad esempio l'InvalidParameterExceptionimpossibilità di riprovare. Quando si verificano, la transazione viene interrotta, la sessione viene restituita al pool e l'eccezione viene generata al client del driver.

Configura il numero massimo di sessioni e transazioni simultanee

Il numero massimo di sessioni contabili utilizzate da un'istanza di QldbDriver per eseguire transazioni è definito dal relativo maxConcurrentTransactions parametro. (Per le versioni precedenti dei driver elencate nella sezione precedente, questo valore è definito dal poolLimit parametro diPooledQldbDriver.)

Questo limite deve essere maggiore di zero e minore o uguale al numero massimo di HTTP connessioni aperte consentite dal client di sessione, come definito dallo specifico AWS SDK. Ad esempio, in Java, il numero massimo di connessioni è impostato nell'ClientConfigurationoggetto.

Il valore predefinito di maxConcurrentTransactions è l'impostazione di connessione massima del tuo AWS SDK.

Quando lo configuri QldbDriver nella tua applicazione, prendi in considerazione le seguenti considerazioni sulla scalabilità:

  • Il pool deve avere sempre almeno lo stesso numero di sessioni quante sono le transazioni in esecuzione simultanea che si prevede di effettuare.

  • In un modello multithread in cui un thread supervisore delega ai thread di lavoro, il driver dovrebbe avere un numero di sessioni almeno pari al numero di thread di lavoro. Altrimenti, al picco di carico, i thread aspetteranno in fila una sessione disponibile.

  • Il limite di servizio delle sessioni attive simultanee per registro è definito in. Quote e limiti in Amazon QLDB Assicurati di non configurare più di questo limite di sessioni simultanee da utilizzare per un singolo registro tra tutti i client.

Riprovare con le eccezioni

Quando riprovi a risolvere le eccezioni che si verificano inQLDB, prendi in considerazione i seguenti consigli.

Riprovare OccConflictException

Le eccezioni al conflitto Optimistic Concurrency Control (OCC) si verificano quando i dati a cui accede la transazione sono cambiati dall'inizio della transazione. QLDBgenera questa eccezione durante il tentativo di confermare la transazione. Il driver ritenta la transazione fino a un numero massimo di volte pari a maxRetries quello configurato.

Per ulteriori informazioni OCC e procedure consigliate per l'utilizzo degli indici per limitare i OCC conflitti, vedere. Modello di QLDB concorrenza Amazon

Riprovare con altre eccezioni diverse da QldbDriver

Per riprovare una transazione all'esterno del driver quando durante l'esecuzione vengono generate eccezioni personalizzate e definite dall'applicazione, è necessario eseguire il wrapping della transazione. Ad esempio, in Java, il codice seguente mostra come utilizzare la libreria Reslience4J per riprovare una transazione. 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; }
Nota

Riprovare una transazione al di fuori del driver ha un effetto moltiplicatoreQLDB. Ad esempio, se QldbDriver è configurato per riprovare tre volte e anche la logica di ripetizione personalizzata riprova tre volte, la stessa transazione può essere ritentata fino a nove volte.

Rendere le transazioni idempotenti

È buona norma rendere le transazioni di scrittura idempotenti per evitare effetti collaterali imprevisti in caso di nuovi tentativi. Una transazione è idempotente se può essere eseguita più volte e produrre risultati identici ogni volta.

Per ulteriori informazioni, consulta Modello di QLDB concorrenza Amazon.

Ottimizzazione delle prestazioni

Per ottimizzare le prestazioni quando esegui transazioni utilizzando il driver, prendi le seguenti considerazioni:

  • L'executeoperazione effettua sempre un minimo di tre SendCommand API chiamate aQLDB, inclusi i seguenti comandi:

    1. StartTransaction

    2. ExecuteStatement

      Questo comando viene richiamato per ogni istruzione PartiQL eseguita nel execute blocco.

    3. CommitTransaction

    Considerate il numero totale di API chiamate effettuate quando calcolate il carico di lavoro complessivo dell'applicazione.

  • In generale, consigliamo di iniziare con uno scrittore a thread singolo e di ottimizzare le transazioni raggruppando più istruzioni all'interno di un'unica transazione. Massimizza le quote relative alle dimensioni delle transazioni, alle dimensioni dei documenti e al numero di documenti per transazione, come definito in. Quote e limiti in Amazon QLDB

  • Se il batching non è sufficiente per carichi di transazioni di grandi dimensioni, puoi provare il multithreading aggiungendo altri writer. Tuttavia, è necessario considerare attentamente i requisiti dell'applicazione per il sequenziamento di documenti e transazioni e la complessità aggiuntiva che ciò comporta.

Esecuzione di più rendiconti per transazione

Come descritto nella sezione precedente, è possibile eseguire più istruzioni per transazione per ottimizzare le prestazioni dell'applicazione. Nel seguente esempio di codice, si esegue una query su una tabella e quindi si aggiorna un documento in quella tabella all'interno di una transazione. A tale scopo, è necessario passare un'espressione lambda all'executeoperazione.

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'executeoperazione del driver avvia implicitamente una sessione e una transazione in quella sessione. Ogni istruzione eseguita nell'espressione lambda è inclusa nella transazione. Dopo l'esecuzione di tutte le istruzioni, il driver esegue automaticamente la transazione. Se un'istruzione ha esito negativo dopo l'esaurimento del limite di tentativi automatici, la transazione viene interrotta.

Propaga le eccezioni in una transazione

Quando si eseguono più rendiconti per transazione, in genere non consigliamo di catturare e ingoiare eccezioni all'interno della transazione.

Ad esempio, in Java, il seguente programma rileva qualsiasi istanza diRuntimeException, registra l'errore e continua. Questo esempio di codice è considerato una pratica scorretta perché la transazione ha esito positivo anche quando l'UPDATEistruzione ha esito negativo. Pertanto, il client potrebbe presumere che l'aggiornamento abbia avuto esito positivo quando non lo è stato.

avvertimento

Non utilizzare questo esempio di codice. Viene fornito per mostrare un esempio anti-pattern considerato una cattiva pratica.

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

Propaga invece (ribolle up) l'eccezione. Se una parte della transazione fallisce, lascia che l'executeoperazione interrompa la transazione in modo che il client possa gestire l'eccezione di conseguenza.