Recomendaciones de controladores de Amazon QLDB - Amazon Quantum Ledger Database (Amazon QLDB)

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Recomendaciones de controladores de Amazon QLDB

En esta sección se describen las prácticas recomendadas para configurar y utilizar el controlador Amazon QLDB para cualquier idioma compatible. Los ejemplos de código proporcionados son específicamente para Java.

Estas recomendaciones se aplican a la mayoría de los casos de uso típicos, pero la talla única no es adecuada para todos. Utilice las siguientes recomendaciones según lo considere adecuado para su aplicación.

Configuración de QldbDriver objeto

LaQldbDriverobjeto administra las conexiones a su libro mayor manteniendo un grupo deSesiones deque se reutilizan en todas las transacciones. UNAsesiónrepresenta una única conexión al libro mayor. QLDB admite una transacción en ejecución activa por sesión.

importante

Para versiones de controladores anteriores, la funcionalidad de agrupación de sesiones sigue en elPooledQldbDriverobjeto en lugar deQldbDriver. Si utilizas una de las siguientes versiones, reemplaza cualquier mención deQldbDriverconPooledQldbDriverpara el resto de este tema.

Controlador Versión
Java 1.1.0o anterior
.NET 0.1.0-beta
Node.js 1.0.0-rc.1o anterior
Python 2.0.2o anterior

LaPooledQldbDriverel objeto está obsoleto en la última versión de los controladores. Le recomendamos que actualice a la última versión y convierta cualquier instancia dePooledQldbDriveraQldbDriver.

Configurar QldbDriver como objeto global

Para optimizar el uso de controladores y sesiones, asegúrese de que solo exista una instancia global del controlador en la instancia de la aplicación. Por ejemplo, en Java, puede utilizarinyección de dependenciamarcos tales comoPrimavera,Guía de Google, o bienDaga. En el siguiente ejemplo de código, se muestra cómo configurarQldbDrivercomo 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(); }

Configurar los intentos de reintento

El conductor vuelve a intentar automáticamente las transacciones cuando hay excepciones transitorias comunes (comoSocketTimeoutExceptionoNoHttpResponseException) ocurren. Para establecer el número máximo de reintentos, puede utilizar elmaxRetriesparámetro deltransactionRetryPolicyobjeto de configuración al crear una instancia deQldbDriver. (Para versiones de controladores anteriores, como se indica en la sección anterior, utilice elretryLimitparámetro dePooledQldbDriver.)

El valor predeterminado de maxRetries es 4.

Errores del cliente, tales comoInvalidParameterExceptionno se puede volver a intentar. Cuando se producen, la transacción se cancela, la sesión se devuelve al grupo y la excepción se lanza al cliente del conductor.

Configure el número máximo de sesiones y transacciones simultáneas

El número máximo de sesiones de libro mayor que utiliza una instancia deQldbDriverejecutar transacciones se define por sumaxConcurrentTransactionsparámetro. (Para versiones de controladores anteriores, como se indica en la sección anterior, se define mediante lapoolLimitparámetro dePooledQldbDriver.)

Este límite debe ser igual o menor que el número máximo de conexiones HTTP abiertas que el cliente de sesión permite, tal y como se define enAWSSDK. Por ejemplo, en Java, el número máximo de conexiones se establece en elClientConfigurationobjeto.

El valor predeterminado demaxConcurrentTransactionses la configuración máxima de conexión de suAWSSDK.

Al configurar elQldbDriveren la aplicación, tome las siguientes consideraciones de escalado:

  • El grupo siempre debe tener al menos tantas sesiones como el número de transacciones en ejecución simultánea que planea tener.

  • En un modelo de subprocesos múltiples en el que un subproceso supervisor delega en subprocesos de trabajo, el controlador debe tener al menos tantas sesiones como el número de subprocesos de trabajo. De lo contrario, con la carga máxima, los subprocesos estarán esperando en fila una sesión disponible.

  • El límite de servicio de sesiones activas simultáneas por libro mayor se define enCuotas y límites de Amazon QLDB. Asegúrese de no configurar más de este límite de sesiones simultáneas para que se utilicen en un solo libro mayor en todos los clientes.

Volver a intentar excepciones

Al volver a intentar las excepciones que se producen en QLDB, tenga en cuenta las siguientes recomendaciones.

Reintentar en OccConflictException

Control de simultaneidad optimista(OCC) se producen excepciones de conflicto cuando los datos a los que accede la transacción han cambiado desde el inicio de la transacción. QLDB lanza esta excepción al intentar confirmar la transacción. El conductor vuelve a intentar la transacción hasta tantas veces comomaxRetriesestá configurado.

Para obtener más información sobre OCC y prácticas recomendadas para utilizar índices para limitar los conflictos de OCC, consulteModelo de simultaneidad de Amazon QLDB.

Volver a intentar otras excepciones fuera de QLDBDriver

Para volver a intentar una transacción fuera del controlador cuando se lanzan excepciones personalizadas definidas por la aplicación durante el tiempo de ejecución, debe envolver la transacción. Por ejemplo, en Java, el siguiente código muestra cómo se utiliza elResliencia 4Jbiblioteca para volver a intentar una transacción en 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

Volver a intentar una transacción fuera del controlador QLDB tiene un efecto multiplicador. Por ejemplo, siQldbDriverestá configurado para volver a intentarlo tres veces, y la lógica de reintentos personalizada también se vuelve a intentar tres veces; la misma transacción se puede volver a intentar hasta nueve veces.

Hacer que las transacciones sean idempotentes

Como práctica recomendada, haga que sus transacciones de escritura sean idempotentes para evitar efectos secundarios inesperados en caso de reintentos. Una transacción esidempotentesi puede ejecutarse varias veces y producir resultados idénticos cada vez.

Para obtener más información, consulte Modelo de simultaneidad de Amazon QLDB.

Optimización del rendimiento

Para optimizar el rendimiento cuando ejecuta transacciones con el controlador, tome las siguientes consideraciones:

  • Laexecuteoperación siempre hace un mínimo de tresSendCommandLlamadas de API a QLDB, incluidos los siguientes comandos:

    1. StartTransaction

    2. ExecuteStatement

      Este comando se invoca para cada instrucción PartiQL que ejecute en elexecutebloque.

    3. CommitTransaction

    Tenga en cuenta el número total de llamadas a la API que se realizan al calcular la carga de trabajo general de la aplicación.

  • En general, recomendamos comenzar con un escritor de subproceso único y optimizar las transacciones mediante la agrupación de varios estados de cuenta dentro de una sola transacción. Maximizar las cuotas sobre el tamaño de la transacción, el tamaño del documento y el número de documentos por transacción, tal como se definen enCuotas y límites de Amazon QLDB.

  • Si el procesamiento por lotes no es suficiente para cargas de transacciones grandes, puedes probar el subproceso múltiple añadiendo escritores adicionales. Sin embargo, debe tener en cuenta cuidadosamente los requisitos de su aplicación para la secuenciación de documentos y transacciones y la complejidad adicional que esto presenta.

Ejecución de varios estados de cuenta por transacción

Como se describe en elsección anterior, puede ejecutar varios estados de cuenta por transacción para optimizar el rendimiento de la aplicación. En el siguiente ejemplo de código, consulta una tabla y, a continuación, actualiza un documento de esa tabla dentro de una transacción. Para ello, transfiera una expresión lambda alexecute.

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

El conductorexecutela operación inicia implícitamente una sesión y una transacción en esa sesión. Cada instrucción que ejecuta en la expresión lambda se envuelve en la transacción. Después de ejecutar todas las declaraciones, el conductor confirma automáticamente la transacción. Si alguna sentencia falla después de que se haya agotado el límite de reintentos automáticos, la transacción se anulará.

Propagar excepciones en una transacción

Al ejecutar varios estados de cuenta por transacción, generalmente no recomendamos que detecte y trague excepciones dentro de la transacción.

Por ejemplo, en Java, el siguiente programa captura cualquier instancia deRuntimeException, registra el error y continúa. Este ejemplo de código se considera una mala práctica porque la transacción se realiza correctamente incluso cuando elUPDATEfalla. Por lo tanto, el cliente podría suponer que la actualización se realizó correctamente cuando no lo hizo.

aviso

No uses este ejemplo de código. Se proporciona para mostrar un ejemplo antipatrón que se considera una mala práctica.

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

Propague (burbujee) la excepción en su lugar. Si alguna parte de la transacción falla, deje que elexecuteanular la transacción para que el cliente pueda gestionar la excepción en consecuencia.