아마존 QLDB 드라이버 추천 - 아마존 퀀텀 레저 데이터베이스 (아마존QLDB)

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

아마존 QLDB 드라이버 추천

중요

지원 종료 알림: 기존 고객은 2025년 7월 31일 지원이 종료될 QLDB 때까지 Amazon을 사용할 수 있습니다. 자세한 내용은 아마존 QLDB 원장을 Amazon Aurora SQL Postgre로 마이그레이션을 참조하십시오.

이 섹션에서는 지원되는 모든 언어에 대해 Amazon QLDB 드라이버를 구성하고 사용하는 모범 사례를 설명합니다. 제공된 코드 예제는 Java 전용입니다.

이러한 권장 사항은 대부분의 일반적인 사용 사례에 적용되지만 한 가지 방법이 모든 사용 사례에 적합하지는 않습니다. 애플리케이션에 적합하다고 판단되는 대로 다음 권장 사항을 사용하세요.

QldbDriver 객체 구성

QldbDriver 객체는 트랜잭션 간에 재사용되는 세션 풀을 유지 관리하여 원장과의 연결을 관리합니다. 세션은 원장에 대한 단일 연결을 나타냅니다. QLDB세션당 하나의 활성 실행 트랜잭션을 지원합니다.

중요

이전 드라이버 버전의 경우 세션 풀링 기능은 QldbDriver 대신 여전히 PooledQldbDriver 객체에 있습니다. 다음 버전 중 하나를 사용하는 경우, 이 주제의 나머지에서는 QldbDriver에 대한 언급을 PooledQldbDriver로 바꿉니다.

드라이버 버전
Java 1.1.0 또는 이전
.NET 0.1.0-beta
Node.js 1.0.0-rc.1 또는 이전
Python 2.0.2 또는 이전

PooledQldbDriver 객체는 최신 버전의 드라이버에서 더 이상 사용되지 않습니다. 최신 버전으로 업그레이드하고 PooledQldbDriver의 모든 인스턴스를 QldbDriver로 변환하는 것이 좋습니다.

글로벌 QldbDriver 개체로 구성

드라이버 및 세션 사용을 최적화하려면 애플리케이션 인스턴스에 드라이버의 글로벌 인스턴스가 하나만 있어야 합니다. 예를 들어, Java에서는 Spring, Google Guice 또는 Dagger와 같은 종속성 주입 프레임워크를 사용할 수 있습니다. 다음 코드 예제는 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(); }

재시도 시도 구성

드라이버는 일반적인 일시적 예외(예: SocketTimeoutException 또는 NoHttpResponseException)가 발생할 경우 자동으로 트랜잭션을 재시도합니다. 최대 재시도 횟수를 설정하려면 QldbDriver의 인스턴스를 만들 때 transactionRetryPolicy 구성 객체의 maxRetries 파라미터를 사용할 수 있습니다. (이전 섹션에 나열된 이전 드라이버 버전의 경우 PooledQldbDriverretryLimit 파라미터를 사용하세요.)

maxRetries의 기본값은 4입니다.

InvalidParameterException와 같은 클라이언트 측 오류는 다시 시도할 수 없습니다. 오류가 발생하면 트랜잭션이 중단되고 세션이 풀로 반환되며 드라이버의 클라이언트에 예외가 발생합니다.

동시에 접속할 수 있는 최대 세션 및 트랜잭션 수 설정

QldbDriver의 인스턴스가 트랜잭션을 실행하는 데 사용하는 최대 원장 세션 수는 해당 maxConcurrentTransactions 파라미터에 의해 정의됩니다. (이전 섹션에 나열된 이전 드라이버 버전의 경우 이는 PooledQldbDriverpoolLimit 파라미터로 정의됩니다.)

이 제한은 0보다 크고 세션 클라이언트가 허용하는 최대 열린 HTTP 연결 수 (특정 기준에 정의된 대로) 보다 작거나 같아야 AWS SDK 합니다. 예를 들어 Java에서는 ClientConfiguration개체에 최대 연결 수가 설정됩니다.

기본값은 의 maxConcurrentTransactions 최대 연결 AWS SDK 설정입니다.

애플리케이션에서 QldbDriver를 구성할 때는 다음과 같은 크기 조정 고려 사항을 고려하세요.

  • 풀에는 항상 계획한 동시 실행 트랜잭션 수만큼 많은 세션이 있어야 합니다.

  • 감독자 스레드가 작업자 스레드에 위임하는 다중 스레드 모델에서는 드라이버에 최소한 작업자 스레드 수 만큼의 세션이 있어야 합니다. 그렇지 않으면 부하가 최대일 때 스레드가 사용 가능한 세션을 기다리게 됩니다.

  • 원장당 동시 활성 세션의 서비스 한도는 아마존의 할당량 및 한도 QLDB에 정의되어 있습니다. 모든 클라이언트의 단일 원장에 사용할 동시 세션 한도를 초과하여 구성하지 않도록 하세요.

예외에 대한 재시도

에서 QLDB 발생한 예외에 대해 재시도할 때는 다음 권장 사항을 고려하십시오.

재시도: OccConflictException

낙관적 동시성 제어 (OCC) 충돌 예외는 트랜잭션이 시작된 이후 트랜잭션에서 액세스하는 데이터가 변경된 경우 발생합니다. QLDB트랜잭션을 커밋하려고 시도하는 동안 이 예외가 발생합니다. 드라이버는 maxRetries이 구성된 횟수만큼 트랜잭션을 재시도합니다.

인덱스를 사용하여 OCC 충돌을 제한하는 방법에 대한 자세한 내용 OCC 및 모범 사례는 을 참조하십시오. 아마존 QLDB 동시성 모델

이외의 다른 예외에 대한 재시도 QldbDriver

런타임 중에 애플리케이션에서 정의한 사용자 지정 예외가 발생할 때 드라이버 외부에서 트랜잭션을 재시도하려면 트랜잭션을 래핑해야 합니다. 예를 들어 Java에서 다음 코드는 Reslience4J 라이브러리를 사용하여 트랜잭션을 재시도하는 방법을 보여줍니다. 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; }
참고

드라이버 외부에서 트랜잭션을 재시도하면 멀티플라이어 효과가 발생합니다. QLDB 예를 들어, QldbDriver가 세 번 재시도하도록 구성되어 있고, 사용자 지정 재시도 로직도 세 번 재시도하면 동일한 트랜잭션을 최대 9번까지 재시도할 수 있습니다.

트랜잭션에 멱등성 부여하기

재시도 시 예상치 못한 부작용이 발생하지 않도록 쓰기 트랜잭션에 멱등성을 부여하는 것이 가장 좋은 방법입니다. 여러 번 실행하여 매번 동일한 결과를 생성할 수 있는 트랜잭션은 멱등성을 가집니다.

자세한 내용은 아마존 QLDB 동시성 모델 섹션을 참조하세요.

성능 최적화

드라이버를 사용하여 트랜잭션을 실행할 때 성능을 최적화하려면 다음 사항을 고려하세요.

  • execute 작업에서는 항상 다음 명령을 QLDB 포함하여 최소 세 번의 SendCommand API 호출을 수행합니다.

    1. StartTransaction

    2. ExecuteStatement

      이 명령은 execute 블록에서 실행하는 각 PartiQL 명령문에 대해 호출됩니다.

    3. CommitTransaction

    애플리케이션의 전체 워크로드를 계산할 때 이루어진 총 API 호출 수를 고려하세요.

  • 일반적으로 단일 스레드 작성기로 시작하여 단일 트랜잭션 내에서 여러 명령문을 일괄 처리하여 트랜잭션을 최적화하는 것이 좋습니다. 아마존의 할당량 및 한도 QLDB에 정의된 대로 트랜잭션 크기, 문서 크기 및 트랜잭션당 문서 수의 할당량을 최대화합니다.

  • 대규모 트랜잭션 로드에 일괄 처리가 충분하지 않은 경우, 작성자를 추가하여 멀티스레딩을 시도할 수 있습니다. 그러나 문서 및 트랜잭션 시퀀싱에 대한 애플리케이션 요구 사항과 이로 인해 발생하는 추가적인 복잡성을 신중하게 고려해야 합니다.

트랜잭션당 여러 명령문 실행

이전 섹션에서 설명한 대로 트랜잭션당 여러 명령문을 실행하여 애플리케이션의 성능을 최적화할 수 있습니다. 다음 코드 예제에서는 테이블을 쿼리하고 트랜잭션 내에서 해당 테이블의 문서를 업데이트합니다. Lambda 표현식을 execute 작업에 전달하여 이를 수행할 수 있습니다.

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

드라이버의 execute 작업은 세션 및 해당 세션에서 트랜잭션을 암시적으로 시작합니다. Lambda 표현식에서 실행하는 각 명령문은 트랜잭션에 래핑됩니다. 모든 명령문이 실행된 후 드라이버는 트랜잭션을 자동 커밋합니다. 자동 재시도 한도를 모두 사용한 후 명령문이 하나라도 실패하면 트랜잭션이 중단됩니다.

트랜잭션에 예외 전파

트랜잭션당 여러 명령문을 실행하는 경우 일반적으로 트랜잭션 내에서 예외를 캐치하고 가리지 않는 것이 좋습니다.

예를 들어, Java에서 다음 프로그램은 RuntimeException의 모든 인스턴스를 캐치하고 오류를 기록하고 계속합니다. 이 코드 예제는 UPDATE 명령문이 실패하더라도 트랜잭션이 성공하기 때문에 잘못된 관행으로 간주됩니다. 따라서 클라이언트는 업데이트가 성공하지 못했지만 성공했다고 가정할 수 있습니다.

주의

이 코드 예제를 사용하지 마세요. 이 예제는 잘못된 관행으로 간주되는 안티 패턴 예제를 보여주기 위해 제공되었습니다.

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

대신 예외를 전파(버블 업)하세요. 트랜잭션의 일부가 실패하는 경우, 클라이언트가 그에 따라 예외를 처리할 수 있도록 execute 작업을 통해 트랜잭션을 중단합니다.