Suggerimenti per i driver Amazon QLDB - 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à.

Suggerimenti per i driver Amazon QLDB

Questa sezione descrive le best practice per la configurazione e l'utilizzo del driver Amazon QLDB per qualsiasi lingua supportata. Gli esempi di codice forniti sono specificamente per Java.

Questi consigli si applicano alla maggior parte dei casi d'uso tipici, ma la taglia unica non è adatta a tutti. Utilizza i seguenti consigli come preferisci per la tua applicazione.

Configurazione della QldbDriver oggetto

LaQldbDriverobject gestisce le connessioni al libro mastro mantenendo un pool disessioniche vengono riutilizzati tra le transazioni. UNsessionerappresenta una singola connessione con il libro contabile. QLDB supporta una transazione attiva per sessione.

Importante

Per le versioni precedenti del driver, la funzionalità di pooling delle sessioni è ancora presentePooledQldbDriverl'oggetto anzichéQldbDriver. Se si utilizza una delle seguenti versioni, sostituire qualsiasi menzione diQldbDriverconPooledQldbDriverper il resto di questo argomento.

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

LaPooledQldbDriverl'oggetto è obsoleto nella versione più recente dei driver. Ti consigliamo di eseguire l'aggiornamento alla versione più recente e convertire qualsiasi istanza diPooledQldbDriveraQldbDriver.

Configura QldbDriver come oggetto globale

Per ottimizzare l'uso di driver e sessioni, assicurarsi che nell'istanza dell'applicazione esista solo un'istanza globale del driver. Ad esempio, in Java, è possibile utilizzareiniezione dipendenzaFramework comePrimavera,Guida Google, oppurePugnale. Il codice di esempio seguente mostra come configurareQldbDrivercome 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(); }

Configurare i tentativi di nuovo tentativo

Il conducente ritenta automaticamente le transazioni quando eccezioni transitorie comuni (come ad esempioSocketTimeoutExceptionoNoHttpResponseException) si verificano. Per impostare il numero massimo di tentativi, è possibile utilizzare ilmaxRetriesparametro deltransactionRetryPolicyoggetto di configurazione durante la creazione di un'istanza diQldbDriver. (Per le versioni precedenti del driver elencate nella sezione precedente, utilizzare ilretryLimitparametro diPooledQldbDriver.)

Il valore predefinito di maxRetries è 4.

Errori lato client comeInvalidParameterExceptionnon può essere riprovato. Quando si verificano, la transazione viene interrotta, la sessione viene restituita al pool e l'eccezione viene lanciata al client del conducente.

Configurare il numero massimo di sessioni e transazioni simultanee

Il numero massimo di sessioni contabili utilizzate da un'istanza diQldbDriverper eseguire transazioni è definito dal suomaxConcurrentTransactionsParametro . (Per le versioni precedenti del driver elencate nella sezione precedente, questo è definito dalpoolLimitparametro diPooledQldbDriver.)

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

Il valore predefinito dimaxConcurrentTransactionsè l'impostazione massima di connessione del tuoAWSSDK.

Quando si configura ilQldbDrivernella tua applicazione, prendi le seguenti considerazioni di scalabilità:

  • Il pool dovrebbe avere sempre almeno il numero di sessioni pari al numero di transazioni in esecuzione contemporanea che si prevede di avere.

  • In un modello multi-thread in cui un thread di supervisore delega ai thread di lavoro, il driver dovrebbe avere almeno il numero di sessioni del numero di thread di lavoro. Altrimenti, al picco di carico, i thread saranno in linea per una sessione disponibile.

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

Nuovi tentativi di eccezione

Quando si riprova le eccezioni che si verificano in QLDB, prendere in considerazione le seguenti raccomandazioni.

Nuovi tentativi su OCCConflictException

Controllo ottimistico della concorrenzaLe eccezioni di conflitto (OCC) si verificano quando i dati a cui si accede alla transazione sono cambiati dall'inizio della transazione. QLDB genera questa eccezione mentre si tenta di eseguire il commit della transazione. Il conducente ritira la transazione fino a quante voltemaxRetriesè configurato.

Per ulteriori informazioni sull'OCC e sulle best practice per l'utilizzo di indici per limitare i conflitti OCC, vedereModello di concorrenza Amazon QLDB.

Riprovare altre eccezioni al di fuori di QLDBDriver

Per riprovare una transazione al di fuori del driver quando personalizzate vengono generate eccezioni definite dall'applicazione durante il runtime, è necessario avvolgere la transazione. Ad esempio, in Java, il codice seguente mostra come utilizzare il codiceReslienza 4Jlibreria per riprovare una transazione in 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

Il ritentativo di una transazione al di fuori del driver QLDB ha un effetto moltiplicatore. Ad esempio, seQldbDriverè configurato per riprovare tre volte e anche la logica di riprova personalizzata torna tre volte, la stessa transazione può essere ripetuta fino a nove volte.

Rendere idempotenti le transazioni

Come best practice, rendi le tue transazioni di scrittura idempotenti per evitare effetti collaterali imprevisti in caso di tentativi. Una transazione è.idempotentese può essere eseguito più volte e produrre risultati identici ogni volta.

Per ulteriori informazioni, consulta Modello di concorrenza Amazon QLDB.

Ottimizzazione delle prestazioni

Per ottimizzare le prestazioni quando si eseguono transazioni utilizzando il driver, attenersi alle seguenti considerazioni:

  • Laexecutel'operazione fa sempre un minimo di treSendCommandChiamate API a QLDB, inclusi i seguenti comandi:

    1. StartTransaction

    2. ExecuteStatement

      Questo comando viene richiamato per ogni istruzione PartiQL eseguita nellaexecuteblocco.

    3. CommitTransaction

    Considera il numero totale di chiamate API effettuate quando si calcola il carico di lavoro complessivo dell'applicazione.

  • In generale, si consiglia di iniziare con uno scrittore a thread singolo e di ottimizzare le transazioni mediante il batch di più istruzioni all'interno di una singola transazione. Massimizzare le quote relative alle dimensioni della transazione, alla dimensione del documento e al numero di documenti per transazione, come definito nellaQuote e limiti in Amazon QLDB.

  • Se il batch non è sufficiente per carichi di transazioni di grandi dimensioni, puoi provare il multi-threading aggiungendo altri scrittori. Tuttavia, è necessario considerare attentamente i requisiti applicativi per il sequenziamento di documenti e transazioni e la complessità aggiuntiva che questo introduce.

Esecuzione di più rendiconti per

Come descritto nelsezione precedente, è possibile eseguire più rendiconti per transazione per ottimizzare le prestazioni dell'applicazione. Nell'esempio di codice seguente, si esegue una query su una tabella e quindi si aggiorna un documento in tale tabella all'interno di una transazione. Lo fai passando un'espressione lambda alexecuteoperazione.

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

Il conducenteexecuteoperazione avvia implicitamente una sessione e una transazione in quella sessione. Ogni istruzione eseguita nell'espressione lambda è racchiusa nella transazione. Dopo aver eseguito tutte le istruzioni, il conducente commette automaticamente la transazione. Se una dichiarazione non riesce dopo che il limite di tentativi automatici è stato esaurito, la transazione viene interrotta.

Propagare le eccezioni in una transazione

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

Ad esempio, in Java, il seguente programma cattura qualsiasi istanza diRuntimeException, registra l'errore e continua. Questo esempio di codice è considerato una cattiva pratica perché la transazione ha esito positivo anche quandoUPDATEl'istruzione fallisce. Quindi, il client potrebbe presumere che l'aggiornamento abbia avuto esito positivo quando non è stato eseguito.

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 (ribalta) invece l'eccezione. Se una qualsiasi parte della transazione fallisce, lascia cheexecuteoperazione interrompe la transazione in modo che il cliente possa gestire l'eccezione di conseguenza.