본문 바로가기
datasource/DyanmoDB

[DynamoDB] 그거 알아요 ? DynamoDB는 HTTP로 통신한다는거 (RDB 운영 이슈, Transaction, Conflict)

by 사바라다 2025. 2. 9.
반응형

개요

DynamoDB의 Java SDK 코드중 AmazonDynamoDBClient 코드를 따라가다보면 재밌는것을 확인할 수 있습니다. 아래를 보도로 하겠습니다.

/**
 * Invoke the request using the http client. Assumes credentials (or lack thereof) have been configured in the
 * ExecutionContext beforehand.
 **/
private <X, Y extends AmazonWebServiceRequest> Response<X> doInvoke(Request<Y> request, HttpResponseHandler<AmazonWebServiceResponse<X>> responseHandler,
        ExecutionContext executionContext, URI discoveredEndpoint, URI uriFromEndpointTrait) {

    if (discoveredEndpoint != null) {
        request.setEndpoint(discoveredEndpoint);
        request.getOriginalRequest().getRequestClientOptions().appendUserAgent("endpoint-discovery");
    } else if (uriFromEndpointTrait != null) {
        request.setEndpoint(uriFromEndpointTrait);
    } else {
        request.setEndpoint(endpoint);
    }

    request.setTimeOffset(timeOffset);

    HttpResponseHandler<AmazonServiceException> errorResponseHandler = protocolFactory.createErrorResponseHandler(new JsonErrorResponseMetadata());

    return client.execute(request, responseHandler, errorResponseHandler, executionContext);
}

바로 DynamoDB와 DB 서버가 통신을 할 때 HTTP로 한다는 것입니다. 일반적으로 사용하는 MySQL과 같은 RDB는 TCP 연결을 사용한다는 것과 다른 흥미로운 부분입니다. 오늘은 이 부분에 대해서 디깅해보면서 HTTP를 사용하기 때문에 얻는 장점과 단점을 알아보도록 하겠습니다.

RDB의 경우 TCP로 인해서 발생할 수 있는 이슈

RDB의 경우 TCP로 연결합니다. 이렇게 연결할 때 잘못된 설정을 해두면 운영중 이슈가 발생할 수 있습니다. 먼저 그 케이스를 보고 넘어가도록 하겠습니다. RDB는 일반적으로 max_connections 의 설정으로 최대 연결될 수 있는 Connection의 수를 제한해 둡니다. 리소스 소비를 억제하기 위한 장치로 max_connections 가 넘어가면 connection을 더이상 새로 맺지 않고 이전 connection이 release 될때까지 기다리게 됩니다.

어플리케이션에서는 RDB와 연결을 맺기위해서 connector를 사용합니다. java에서는 hikariCP를 많이 사용합니다. hikariCP를 예를 들어 설명해보면 Connection Pool을 통해서 Connection을 재활용할 수 있게 관리합니다. maximum-pool-size 설정으로 어플리케이션 1대가 맺을 수 있는 Connection 최대 숫자를 관리할 수 있습니다.

이때 트래픽이 많이 들어와서 스케일 아웃 된다고 가정해보겠습니다.

  • RDB의 max_connection = 200
  • hikariCP의 maximum-pool-size = 20

이라면 트래픽이 많이 몰려서 CPU가 오르고 각 서버가 maximum-pool-size도 오르고 있습니다. 이때 10대가 넘어가고 11대째로 스케일아웃이 된다고하면 hikariCP의 amximum-pool-size * 11 해서 220으로 RDB의 max_connection 갯수를 넘게됩니다.

결과적으로 20개의 Connection은 DB 연결이 불가능해지고 DB 연결 불가능 에러가 발생하게됩니다.

이러한 과정을 통해서 알 수 있는 사실은 RDB를 사용할 때, RDB는 동시 연결 수가 제한적이므로, 서버를 무한정 늘릴 수 없다는 사실입니다. 서버가 많아질수록 Connection Pool을 과도하게 사용하게 되고, 결국 DB 성능 저하나 장애를 유발할 수 있습니다.

HTTP라 생기는 RDB와의 차이점

DynamoDB는 HTTP로 stateless 하게 통신하기 때문에 위와 같은 상황이 발생하기 어렵습니다. 확장성적으로 상대적으로 자유롭다는 의미가 됩니다. (HTTP도 TCP 세션을 맺었다 끊기 때문에 완전히 자유로울 순 없습니다.) 위 이미지에서 나타나는 바와 같이 클라이언트는 HTTP로 요청을 전달하기 때문입니다.HTTP는 요청에 대해서 순간적인 Sessiopn을 맺은 뒤 통신이 마무리되면 연결이 종료되기 때문입니다. 이러한 기조에 맞춰 Transaction, 충돌 처리 등도 RDB와는 다릅니다. 이러한 내용에 대해서 추가로 알아보도록 하겠습니다.

설명에 앞서서 먼저 HTTP라 생기는 단점으로는 TCP 연결 기반 DB보다 HTTP 통신으로 인한 응답 시간이 길어질 수 있습니다. 왜냐하면 매번 연결을 맺어야하기 때문입니다. 이것이 문제를 일으키고 보완이 필요하다면 DAX (DynamoDB Accelerator)를 사용가능합니다. DAX를 사용하면 읽기성능을 향상시킬 수 있습니다.

그렇다면 HTTP 이기 때문에 RDB와 다른점을 살펴보도록 하겠습니다.

Transaction

RDB에서의 Transaction과 DynamoDB에서의 Transaction을 비교 해보도록 하겠습니다. DataBase에서 Transaction이라는 것은 기본적으로 아래 4가지를 보장합니다.

ACID

  • Atomicity (원자성): 모든 작업이 성공하거나, 모두 롤백됨.
  • Consistency (일관성): 트랜잭션 전후 데이터가 무결성을 유지.
  • Isolation (격리성): 동시에 실행되는 트랜잭션이 서로 간섭하지 않음.
  • Durability (지속성): 커밋된 데이터는 영구적으로 저장됨.

RDB

TCP를 이용한 커넥션을 만들고 세션을 기반으로 트랜잭션 상태를 유지합니다. 즉, 처음 세션을 만들때 Tranasction을 시작하고 여러 연산을 수행합니다. 그리고 마지막에 COMMIT 또는 ROLLBACK을 통해서 database에 반영할지 말지 여부를 정합니다. TCP를 이용한 연결지향이기때문에 Transaction 중간에 여러 연산을 수행할 수 있고 하물며 RDB와 연관없는 Exception이 발생하더라도 그것을 기반으로 Rollback을 처리할 수도 있습니다.

ex)

  1. Begin Transaction
  2. RDB insert 요청 1
  3. RDB update 요청 2
  4. API 호출 하려고 했으나 실패
  5. RDB Rollback

이때 API 호출 시 실패하였기 때문에 Exception으로 인해서 RDB를 롤백 시킬 수 있고 이것이 TCP 기반 연결 지향이기 때문에 이룰 수 있는 강력한 일관성 유지 방식입니다. 하지만 단점으로는 트랜잭션이 오래 지속되면 history list length가 지속적으로 늘어날 수 있습니다. 또한 Locking이 발생하여 성능 저하 가능성이 높아지기 때문에 유의해야합니다.

DynamoDB

DynamoDB는 HTTP 기반으로 요청을 수행하며, 요청마다 독립적인 HTTP 호출을 수행합니다. 즉, DynamoDB는 stateless를 기반으로 구성되어있기 때문에 HTTP 통신과 또 다른 HTTP 통신을 Transaction으로 묶어주지는 못합니다. 대신 상대적으로 제한되어있지만 한번의 HTTP 통신에서 여러 요청을 보낼 수 있고 이러한 요청에 대해서 Transaction을 유지할 수 있습니다.

DynamoDB는 트랜잭션을 사용하기 위해서는 TransactionWriteItems와 TransactionGetItems를 사용할 수 있습니다. 사용방법은 아래와 같습니다. TransactionWriteItems 만 한번 보도록 하겠습니다.

{
    "TransactItems": [
        { // Request 1
            "Put": {
                "TableName": "Users",
                "Item": {
                    "UserId": { "S": "12345" },
                    "Name": { "S": "John Doe" },
                    "Age": { "N": "30" }
                }
            }
        },
        { // Request 2
            "Update": {
                "TableName": "Orders",
                "Key": {
                    "OrderId": { "S": "98765" }
                },
                "UpdateExpression": "SET OrderStatus = :status",
                "ExpressionAttributeValues": {
                    ":status": { "S": "Shipped" }
                }
            }
        }
    ]
}

설명

  • Put 작업: Users 테이블에 새로운 항목 추가 (UserId가 "12345"인 사용자 생성)
  • Update 작업: Orders 테이블에서 특정 주문(OrderId: "98765")의 OrderStatus 필드를 "Shipped"으로 변경

위 HTTP 통신을 통해서 우리는 함께 요청된 2가지의 쓰기가 Transaction으로 묶이는것을 기대할 수 있습니다. 하지만 RDB 처럼 긴 세션을 유지하는 트랜잭션은 아니기 때문에 타 로직과 트랜잭션을 엮는것은 어렵다는 것을 알 수 있습니다.

충돌 처리 (Conflict)

두번째로 충돌 처리에 대해서 알아보도록 하겠습니다. 동일한 리소스가 동시에 업데이트 될 수 있고 그것을 어떻게 처리하는지에 대한문제입니다. RDB의 경우 조회 시 선 잠금을해서 타 Session이 업데이트 하지 못하도록하는 비관적락(Pessimistic Lock)과 버전 컨트롤을 통해서 먼저 업데이트 되는 경우에 사용하는 락관적락(Optimistic Lock)을 모두 사용할 수 있습니다. 두 매커니즘을 잘 모르시는 분은 제가 4년전에 포스팅했던 [database] 낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)를 참고해주시면 쉽게 이해하시리라 믿습니다.

하지만 DynamoDB에서는 비관적락을 제공하지 않고 사용할 수 없습니다. 그 이유는 비관적락은 사용하기 위해서는 리소스에 대해서 먼저 선점하는 것이 필요하고 이는 TCP 등의 지속적인 연결이 필요하기 때문입니다. 하지만 DynamoDB는 HTTP로만 통신하기 때문에 매커니즘적으로 제공이 불가능한 개념입니다.

따라서 DynamoDB에서는 락관적락 매커니즘만 제공이 됩니다. DynamoDB에서는 낙관적 락을 적용할 때 버전 번호 (Version Number) 또는 타임스탬프 (LastUpdatedTime)를 활용합니다. 그리고 실제 Update 명령을 전송할 때 업데이트 조건으로 사용합니다. 만약 조회시 확인했던 버전 또는 타임스탬프가 다르다면 이미 업데이트가 된 것으로 간주하는것입니다.

아래는 업데이트하는 HTTP 요청이며 ConditionExpression을 보시면 버전 체크하시는 것을 확인하실 수 있습니다.

{
    "TableName": "Users",
    "Key": {
        "UserId": { "S": "12345" }
    },
    "UpdateExpression": "SET Balance = :newBalance, Version = Version + 1",
    "ConditionExpression": "Version = :currentVersion", // do Optimistic Lock Mechanism
    "ExpressionAttributeValues": {
        ":newBalance": { "N": "120" },
        ":currentVersion": { "N": "3" }
    }
}
  • ConditionExpression: Version = :currentVersion (현재 Version이 예상한 값이면 업데이트 수행)
  • Version이 다르면 충돌이 발생하며 업데이트가 실패 → 다시 데이터를 가져와 업데이트해야 함.

마무리

오늘은 이렇게 DynamoDB의 http 통신과 그로 인해서 파생되는 점에 대해서 알아보았습니다.

다음 시간에는 DynamoDB의 용어 및 개념 정리와 비용 계산하는 방법에 대해서 알아보도록 하겠습니다.

감사합니다.

참조

[1] The DynamoDB Book by Alex DeBrie

[2] https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/http/AmazonHttpClient.java#L1312

[3] https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-dynamodb/src/main/java/com/amazonaws/services/dynamodbv2/AmazonDynamoDBClient.java

반응형

댓글