C++ AWS SDK 완벽 가이드 | S3·DynamoDB·Lambda 연동 및 프로덕션 패턴 [#52-7]

C++ AWS SDK 완벽 가이드 | S3·DynamoDB·Lambda 연동 및 프로덕션 패턴 [#52-7]

이 글의 핵심

AWS 서비스 C++ 통합: S3 파일 저장·멀티파트 업로드, DynamoDB NoSQL CRUD·Query, Lambda 함수 호출, IAM 인증. 실무 문제 시나리오, 완전한 예제, 흔한 에러 해결, 성능 최적화, 프로덕션 패턴까지.

들어가며: “C++에서 AWS 서비스를 어떻게 연동하지?”

실제 겪는 문제 시나리오

C++로 서버·데스크톱 앱을 개발하다 보면, 클라우드 스토리지·DB·서버리스 함수를 연동해야 하는 순간이 옵니다. REST API를 직접 호출할 수도 있지만, AWS SDK for C++를 쓰면 인증·재시도·에러 처리를 SDK가 담당해 줍니다.

시나리오 1: 대용량 로그 파일을 S3에 업로드하다 메모리 부족

상황: 500MB 로그 파일을 PutObject로 한 번에 업로드하려다 메모리 할당 실패
문제: 단일 PutObject는 5GB 제한이 있지만, 전체를 메모리에 올리면 OOM 발생
결과: 멀티파트 업로드로 청크 단위(5MB~) 스트리밍 업로드

시나리오 2: DynamoDB 조회 시 “ValidationException: One or more parameter values were invalid”

상황: GetItem/Query 호출 시 파티션 키·정렬 키 형식이 테이블 스키마와 맞지 않음
문제: 문자열인데 숫자로 보냈거나, AttributeValue 타입 불일치
결과: SetS/SetN 등 올바른 AttributeValue 생성자 사용, 스키마 확인

시나리오 3: Lambda 호출 시 “ResourceNotFoundException” 또는 타임아웃

상황: C++ 앱에서 Lambda 함수를 Invoke했는데 404 또는 15초 후 타임아웃
문제: 함수명 오타, 리전 불일치, Lambda 실행 권한(IAM) 부족, 페이로드 크기 초과
결과: 함수명·리전·IAM 정책 확인, InvocationType::RequestResponse 시 타임아웃 설정

시나리오 4: S3 접근 시 “Access Denied” 또는 “SignatureDoesNotMatch”

상황: 로컬 개발 시 credentials 정상인데 EC2/ECS에서만 실패
문제: IAM Role 미할당, 환경 변수(AWS_ACCESS_KEY_ID 등) 누락, 리전·엔드포인트 오류
결과: EC2 Instance Profile 또는 ECS Task Role 설정, credentials 체인 확인

시나리오 5: 여러 AWS 서비스를 한 앱에서 사용할 때 클라이언트 관리

상황: S3·DynamoDB·Lambda를 동시에 쓰는데, 매번 새 클라이언트 생성하면 리소스 낭비
문제: 클라이언트는 스레드 세이프하지만, 불필요한 생성·소멸은 오버헤드
결과: 싱글톤 또는 의존성 주입으로 클라이언트 재사용, ClientConfiguration 공유
flowchart TB
    subgraph 문제[실무 문제]
        P1[대용량 S3 업로드] --> S1[멀티파트 업로드]
        P2[DynamoDB 타입 에러] --> S2[AttributeValue 올바른 사용]
        P3[Lambda 404/타임아웃] --> S3[리전·IAM·함수명 확인]
        P4[Access Denied] --> S4[IAM Role·Credentials]
        P5[클라이언트 관리] --> S5[재사용·싱글톤]
    end

이 글에서 다루는 것:

  • S3: PutObject, GetObject, 멀티파트 업로드, ListObjectsV2
  • DynamoDB: PutItem, GetItem, UpdateItem, Query
  • Lambda: Invoke (동기·비동기)
  • IAM 인증: 환경 변수, Instance Profile, credentials 체인
  • 자주 발생하는 에러와 해결법
  • 성능 최적화 팁
  • 프로덕션 배포 패턴

요구 환경: C++17 이상, AWS SDK for C++ 1.x


실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  1. 환경 설정 및 SDK 초기화
  2. S3 파일 저장·다운로드
  3. DynamoDB NoSQL 연동
  4. Lambda 함수 호출
  5. IAM 인증 및 Credentials
  6. 자주 발생하는 에러와 해결법
  7. 성능 최적화 팁
  8. 프로덕션 패턴
  9. 구현 체크리스트
  10. 정리

1. 환경 설정 및 SDK 초기화

1.1 CMake로 AWS SDK 빌드

vcpkg 또는 시스템 패키지로 AWS SDK for C++를 설치합니다.

# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(aws_sdk_demo)

set(CMAKE_CXX_STANDARD 17)

# vcpkg 사용 시
find_package(aws-sdk-cpp REQUIRED COMPONENTS s3 dynamodb lambda core)

add_executable(aws_demo
    main.cpp
)

target_link_libraries(aws_demo
    PRIVATE
    aws-cpp-sdk-s3
    aws-cpp-sdk-dynamodb
    aws-cpp-sdk-lambda
    aws-cpp-sdk-core
)

vcpkg 설치 예시:

vcpkg install aws-sdk-cpp[s3,dynamodb,lambda]:x64-linux

1.2 SDK 초기화 및 ClientConfiguration

모든 AWS API 호출 전에 Aws::InitAPI를 호출하고, 종료 시 Aws::ShutdownAPI를 호출해야 합니다.

#include <aws/core/Aws.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/s3/S3Client.h>
#include <iostream>

int main() {
    Aws::SDKOptions options;
    Aws::InitAPI(options);

    // 리전·엔드포인트·재시도 설정
    Aws::Client::ClientConfiguration config;
    config.region = "ap-northeast-2";  // 서울 리전
    config.connectTimeoutMs = 3000;
    config.requestTimeoutMs = 30000;
    config.maxConnections = 50;

    Aws::S3::S3Client s3Client(config);

    // ... API 호출 ...

    Aws::ShutdownAPI(options);
    return 0;
}

ClientConfiguration 주요 옵션:

  • region: 리전 (예: ap-northeast-2, us-east-1)
  • endpointOverride: 커스텀 엔드포인트 (로컬스택 등)
  • requestTimeoutMs: 요청 타임아웃
  • maxConnections: 연결 풀 크기

2. S3 파일 저장·다운로드

2.1 PutObject: 파일 업로드

#include <aws/core/Aws.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/PutObjectRequest.h>
#include <fstream>
#include <iostream>

bool uploadFileToS3(const std::string& bucket, const std::string& key,
                   const std::string& filePath,
                   const Aws::Client::ClientConfiguration& config) {
    Aws::S3::S3Client client(config);

    auto inputStream = Aws::MakeShared<Aws::FStream>(
        "PutObjectStream", filePath.c_str(),
        std::ios_base::in | std::ios_base::binary);

    if (!*inputStream) {
        std::cerr << "파일 열기 실패: " << filePath << std::endl;
        return false;
    }

    Aws::S3::Model::PutObjectRequest request;
    request.SetBucket(bucket);
    request.SetKey(key);
    request.SetBody(inputStream);

    auto outcome = client.PutObject(request);

    if (!outcome.IsSuccess()) {
        std::cerr << "PutObject 실패: " << outcome.GetError().GetMessage()
                  << std::endl;
        return false;
    }

    std::cout << "업로드 완료: s3://" << bucket << "/" << key << std::endl;
    return true;
}

코드 설명:

  • Aws::FStream: 파일을 스트림으로 열어 메모리 효율적으로 전송
  • SetBucket/SetKey: 버킷명과 객체 키(경로)
  • PutObject는 동기 호출; 비동기는 PutObjectAsync 사용

2.2 PutObject: 문자열(버퍼) 업로드

bool uploadStringToS3(const std::string& bucket, const std::string& key,
                     const std::string& content,
                     const Aws::Client::ClientConfiguration& config) {
    Aws::S3::S3Client client(config);

    auto stream = Aws::MakeShared<Aws::StringStream>("");
    *stream << content;

    Aws::S3::Model::PutObjectRequest request;
    request.SetBucket(bucket);
    request.SetKey(key);
    request.SetBody(stream);

    auto outcome = client.PutObject(request);
    return outcome.IsSuccess();
}

2.3 GetObject: 파일 다운로드

#include <aws/s3/model/GetObjectRequest.h>

bool downloadFromS3(const std::string& bucket, const std::string& key,
                   const std::string& localPath,
                   const Aws::Client::ClientConfiguration& config) {
    Aws::S3::S3Client client(config);

    Aws::S3::Model::GetObjectRequest request;
    request.SetBucket(bucket);
    request.SetKey(key);

    auto outcome = client.GetObject(request);

    if (!outcome.IsSuccess()) {
        std::cerr << "GetObject 실패: " << outcome.GetError().GetMessage()
                  << std::endl;
        return false;
    }

    auto& result = outcome.GetResult();
    auto& body = result.GetBody();

    std::ofstream ofs(localPath, std::ios::binary);
    ofs << body.rdbuf();
    ofs.close();

    std::cout << "다운로드 완료: " << localPath << std::endl;
    return true;
}

2.4 멀티파트 업로드 (5MB 이상 권장)

5MB 이상 파일은 멀티파트 업로드로 청크 단위 전송하면 메모리·재시도에 유리합니다.

#include <aws/s3/model/CreateMultipartUploadRequest.h>
#include <aws/s3/model/UploadPartRequest.h>
#include <aws/s3/model/CompleteMultipartUploadRequest.h>
#include <aws/s3/model/AbortMultipartUploadRequest.h>
#include <fstream>
#include <vector>

struct PartETag {
    int partNumber;
    Aws::String eTag;
};

bool multipartUpload(const std::string& bucket, const std::string& key,
                    const std::string& filePath,
                    const Aws::Client::ClientConfiguration& config,
                    size_t partSize = 5 * 1024 * 1024) {  // 5MB
    Aws::S3::S3Client client(config);

    std::ifstream file(filePath, std::ios::binary | std::ios::ate);
    if (!file) return false;
    size_t fileSize = file.tellg();
    file.seekg(0);

    // 1. 멀티파트 업로드 시작
    Aws::S3::Model::CreateMultipartUploadRequest createReq;
    createReq.SetBucket(bucket);
    createReq.SetKey(key);
    auto createOutcome = client.CreateMultipartUpload(createReq);
    if (!createOutcome.IsSuccess()) {
        std::cerr << "CreateMultipartUpload 실패" << std::endl;
        return false;
    }
    auto uploadId = createOutcome.GetResult().GetUploadId();

    // 2. 각 파트 업로드
    std::vector<PartETag> parts;
    int partNumber = 1;
    std::vector<char> buffer(partSize);

    while (file.read(buffer.data(), partSize) || file.gcount() > 0) {
        size_t bytesRead = file.gcount();
        if (bytesRead == 0) break;

        auto partStream = Aws::MakeShared<Aws::StringStream>("");
        partStream->write(buffer.data(), bytesRead);
        partStream->seekg(0);

        Aws::S3::Model::UploadPartRequest partReq;
        partReq.SetBucket(bucket);
        partReq.SetKey(key);
        partReq.SetUploadId(uploadId);
        partReq.SetPartNumber(partNumber);
        partReq.SetBody(partStream);

        auto partOutcome = client.UploadPart(partReq);
        if (!partOutcome.IsSuccess()) {
            client.AbortMultipartUpload(
                Aws::S3::Model::AbortMultipartUploadRequest()
                    .WithBucket(bucket).WithKey(key).WithUploadId(uploadId));
            return false;
        }

        parts.push_back({partNumber, partOutcome.GetResult().GetETag()});
        partNumber++;
    }

    // 3. 멀티파트 업로드 완료
    Aws::S3::Model::CompletedMultipartUpload completed;
    for (const auto& p : parts) {
        Aws::S3::Model::CompletedPart cp;
        cp.SetETag(p.eTag);
        cp.SetPartNumber(p.partNumber);
        completed.AddParts(cp);
    }

    Aws::S3::Model::CompleteMultipartUploadRequest completeReq;
    completeReq.SetBucket(bucket);
    completeReq.SetKey(key);
    completeReq.SetUploadId(uploadId);
    completeReq.WithMultipartUpload(completed);

    auto completeOutcome = client.CompleteMultipartUpload(completeReq);
    if (!completeOutcome.IsSuccess()) {
        std::cerr << "CompleteMultipartUpload 실패" << std::endl;
        return false;
    }

    std::cout << "멀티파트 업로드 완료: " << key << std::endl;
    return true;
}

멀티파트 업로드 포인트:

  • 최소 파트 크기 5MB (마지막 파트 제외)
  • 최대 10,000 파트
  • 실패 시 AbortMultipartUpload로 정리

2.5 ListObjectsV2: 객체 목록 조회

#include <aws/s3/model/ListObjectsV2Request.h>

std::vector<Aws::String> listS3Objects(const std::string& bucket,
                                       const std::string& prefix,
                                       const Aws::Client::ClientConfiguration& config) {
    Aws::S3::S3Client client(config);
    std::vector<Aws::String> keys;
    Aws::String continuationToken;

    do {
        Aws::S3::Model::ListObjectsV2Request request;
        request.SetBucket(bucket);
        if (!prefix.empty()) request.SetPrefix(prefix);
        if (!continuationToken.empty()) request.SetContinuationToken(continuationToken);

        auto outcome = client.ListObjectsV2(request);
        if (!outcome.IsSuccess()) break;

        for (const auto& obj : outcome.GetResult().GetContents()) {
            keys.push_back(obj.GetKey());
        }
        continuationToken = outcome.GetResult().GetNextContinuationToken();
    } while (!continuationToken.empty());

    return keys;
}

3. DynamoDB NoSQL 연동

3.1 PutItem: 아이템 저장

#include <aws/dynamodb/DynamoDBClient.h>
#include <aws/dynamodb/model/PutItemRequest.h>
#include <aws/dynamodb/model/AttributeValue.h>

bool putDynamoItem(const std::string& tableName,
                  const std::string& pkName, const std::string& pkValue,
                  const std::string& skName, const std::string& skValue,
                  const Aws::Client::ClientConfiguration& config) {
    Aws::DynamoDB::DynamoDBClient client(config);

    Aws::DynamoDB::Model::PutItemRequest request;
    request.SetTableName(tableName);
    request.AddItem(pkName, Aws::DynamoDB::Model::AttributeValue().SetS(pkValue));
    request.AddItem(skName, Aws::DynamoDB::Model::AttributeValue().SetS(skValue));
    // 추가 속성
    request.AddItem("createdAt", Aws::DynamoDB::Model::AttributeValue().SetS(
        std::to_string(std::chrono::system_clock::now().time_since_epoch().count())));

    auto outcome = client.PutItem(request);

    if (!outcome.IsSuccess()) {
        std::cerr << "PutItem 실패: " << outcome.GetError().GetMessage() << std::endl;
        return false;
    }
    return true;
}

AttributeValue 타입:

  • SetS(s): 문자열
  • SetN(n): 숫자 (문자열로 전달, 예: "123")
  • SetB(data): 바이너리
  • SetBool(b): 불리언
  • SetSS(set): 문자열 집합
  • SetM(map): 맵(중첩 문서)

3.2 GetItem: 단일 아이템 조회

#include <aws/dynamodb/model/GetItemRequest.h>

std::optional<Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue>>
getDynamoItem(const std::string& tableName,
              const std::string& pkName, const std::string& pkValue,
              const std::string& skName, const std::string& skValue,
              const Aws::Client::ClientConfiguration& config) {
    Aws::DynamoDB::DynamoDBClient client(config);

    Aws::DynamoDB::Model::GetItemRequest request;
    request.SetTableName(tableName);
    request.AddKey(pkName, Aws::DynamoDB::Model::AttributeValue().SetS(pkValue));
    request.AddKey(skName, Aws::DynamoDB::Model::AttributeValue().SetS(skValue));

    auto outcome = client.GetItem(request);

    if (!outcome.IsSuccess()) {
        std::cerr << "GetItem 실패: " << outcome.GetError().GetMessage() << std::endl;
        return std::nullopt;
    }

    auto item = outcome.GetResult().GetItem();
    if (item.empty()) return std::nullopt;

    return item;
}

3.3 UpdateItem: 속성 업데이트

#include <aws/dynamodb/model/UpdateItemRequest.h>

bool updateDynamoItem(const std::string& tableName,
                     const std::string& pkName, const std::string& pkValue,
                     const std::string& attrName, const std::string& attrValue,
                     const Aws::Client::ClientConfiguration& config) {
    Aws::DynamoDB::DynamoDBClient client(config);

    Aws::DynamoDB::Model::UpdateItemRequest request;
    request.SetTableName(tableName);
    request.AddKey(pkName, Aws::DynamoDB::Model::AttributeValue().SetS(pkValue));
    request.SetUpdateExpression("SET #a = :v");

    Aws::Map<Aws::String, Aws::String> exprNames;
    exprNames[#a] = attrName;
    request.SetExpressionAttributeNames(exprNames);

    Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> exprValues;
    exprValues[:v] = Aws::DynamoDB::Model::AttributeValue().SetS(attrValue);
    request.SetExpressionAttributeValues(exprValues);

    auto outcome = client.UpdateItem(request);
    return outcome.IsSuccess();
}

3.4 Query: 파티션 키 기준 조회

#include <aws/dynamodb/model/QueryRequest.h>

std::vector<Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue>>
queryDynamoByPartitionKey(const std::string& tableName,
                         const std::string& pkName, const std::string& pkValue,
                         const Aws::Client::ClientConfiguration& config) {
    Aws::DynamoDB::DynamoDBClient client(config);
    std::vector<Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue>> items;

    Aws::DynamoDB::Model::QueryRequest request;
    request.SetTableName(tableName);
    request.SetKeyConditionExpression("#pk = :pkval");
    request.AddExpressionAttributeNames("#pk", pkName);
    request.AddExpressionAttributeValues(":pkval",
        Aws::DynamoDB::Model::AttributeValue().SetS(pkValue));

    Aws::Map<Aws::String, Aws::DynamoDB::Model::AttributeValue> lastEvaluatedKey;
    do {
        if (!lastEvaluatedKey.empty()) {
            request.SetExclusiveStartKey(lastEvaluatedKey);
        }

        auto outcome = client.Query(request);
        if (!outcome.IsSuccess()) break;

        for (const auto& item : outcome.GetResult().GetItems()) {
            items.push_back(item);
        }
        lastEvaluatedKey = outcome.GetResult().GetLastEvaluatedKey();
    } while (!lastEvaluatedKey.empty());

    return items;
}

4. Lambda 함수 호출

4.1 Invoke (동기 호출)

#include <aws/lambda/LambdaClient.h>
#include <aws/lambda/model/InvokeRequest.h>
#include <aws/core/utils/json/JsonSerializer.h>

struct LambdaResponse {
    bool success;
    std::string payload;
    std::string errorMessage;
};

LambdaResponse invokeLambdaSync(const std::string& functionName,
                                const std::string& payloadJson,
                                const Aws::Client::ClientConfiguration& config) {
    Aws::Lambda::LambdaClient client(config);

    Aws::Lambda::Model::InvokeRequest request;
    request.SetFunctionName(functionName);
    request.SetInvocationType(Aws::Lambda::Model::InvocationType::RequestResponse);

    auto payloadStream = Aws::MakeShared<Aws::StringStream>("");
    *payloadStream << payloadJson;
    request.SetBody(payloadStream);

    auto outcome = client.Invoke(request);

    LambdaResponse result{};
    if (!outcome.IsSuccess()) {
        result.success = false;
        result.errorMessage = outcome.GetError().GetMessage();
        return result;
    }

    auto& invokeResult = outcome.GetResult();
    auto statusCode = invokeResult.GetStatusCode();

    if (statusCode >= 200 && statusCode < 300) {
        result.success = true;
        auto& body = invokeResult.GetPayload();
        std::string payloadStr((std::istreambuf_iterator<char>(body)),
                              std::istreambuf_iterator<char>());
        result.payload = payloadStr;
    } else {
        result.success = false;
        result.errorMessage = "Lambda returned status " + std::to_string(statusCode);
    }
    return result;
}

InvocationType:

  • RequestResponse: 동기, 응답 대기 (최대 15분, 클라이언트 타임아웃 별도)
  • Event: 비동기, 즉시 반환
  • DryRun: 검증만 수행

4.2 Invoke (비동기 Event)

LambdaResponse invokeLambdaAsync(const std::string& functionName,
                                 const std::string& payloadJson,
                                 const Aws::Client::ClientConfiguration& config) {
    Aws::Lambda::LambdaClient client(config);

    Aws::Lambda::Model::InvokeRequest request;
    request.SetFunctionName(functionName);
    request.SetInvocationType(Aws::Lambda::Model::InvocationType::Event);

    auto payloadStream = Aws::MakeShared<Aws::StringStream>("");
    *payloadStream << payloadJson;
    request.SetBody(payloadStream);

    auto outcome = client.Invoke(request);

    LambdaResponse result{};
    result.success = outcome.IsSuccess();
    if (!outcome.IsSuccess()) {
        result.errorMessage = outcome.GetError().GetMessage();
    }
    return result;
}

5. IAM 인증 및 Credentials

5.1 Credentials 체인 (기본 순서)

AWS SDK는 다음 순서로 자격 증명을 찾습니다:

  1. 환경 변수: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN(선택)
  2. ~/.aws/credentials 파일
  3. EC2 Instance Profile (EC2 메타데이터)
  4. ECS Task Role
  5. 기타 프로파일
# 환경 변수 설정 예시
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_DEFAULT_REGION=ap-northeast-2

5.2 프로파일 지정

Aws::Client::ClientConfiguration config;
config.region = "ap-northeast-2";

// 특정 프로파일 사용 (예: ~/.aws/credentials의 [my-profile])
// 환경 변수 AWS_PROFILE=my-profile 로도 가능

5.3 EC2/ECS에서 IAM Role

EC2에 Instance Profile을, ECS Task에 Task Role을 붙이면 별도 키 없이 SDK가 자동으로 메타데이터 서비스에서 임시 자격 증명을 가져옵니다. 프로덕션에서는 이 방식을 권장합니다.


6. 자주 발생하는 에러와 해결법

6.1 S3: “NoSuchBucket” / “Access Denied”

원인: 버킷 없음, 버킷명 오타, IAM 권한 부족

해결법:

// 버킷명·리전 확인
config.region = "ap-northeast-2";  // 버킷이 생성된 리전과 일치
// IAM 정책에 s3:PutObject, s3:GetObject, s3:ListBucket 필요

필요 IAM 권한 예시:

{
  "Effect": "Allow",
  "Action": [
    "s3:PutObject",
    "s3:GetObject",
    "s3:DeleteObject",
    "s3:ListBucket"
  ],
  "Resource": [
    "arn:aws:s3:::my-bucket",
    "arn:aws:s3:::my-bucket/*"
  ]
}

6.2 S3: “SignatureDoesNotMatch”

원인: 시크릿 키 오타, 시스템 시계 불일치, 리전·서비스명 불일치

해결법:

  • 시크릿 키 재확인
  • ntpdate 등으로 서버 시간 동기화
  • endpointOverride 사용 시 서비스명·리전 일치 확인

6.3 DynamoDB: “ValidationException: One or more parameter values were invalid”

원인: AttributeValue 타입이 테이블 스키마와 다름

해결법:

// 잘못된 예: 파티션 키가 문자열인데 숫자로 전달
request.AddKey("userId", Aws::DynamoDB::Model::AttributeValue().SetN("123"));  // 스키마가 S면 SetS

// 올바른 예: 스키마에 맞게
request.AddKey("userId", Aws::DynamoDB::Model::AttributeValue().SetS("user-123"));
request.AddKey("timestamp", Aws::DynamoDB::Model::AttributeValue().SetN("1700000000"));

6.4 DynamoDB: “ResourceNotFoundException”

원인: 테이블명 오타, 리전 불일치, 테이블이 아직 ACTIVE가 아님

해결법:

  • 테이블명·리전 확인
  • 테이블 생성 직후 DescribeTableTableStatus == ACTIVE 확인 후 사용

6.5 Lambda: “ResourceNotFoundException”

원인: 함수명 오타, 리전 불일치, 함수가 다른 리전에 있음

해결법:

config.region = "ap-northeast-2";  // Lambda 함수가 있는 리전
// 함수명: my-function 또는 my-function:alias

6.6 Lambda: 타임아웃

원인: Lambda 자체 타임아웃(최대 15분), 클라이언트 requestTimeoutMs 부족

해결법:

config.requestTimeoutMs = 60000;  // 60초 (Lambda 실행 시간 + 네트워크)
// Lambda 콘솔에서 함수 타임아웃도 충분히 설정

6.7 공통: “RequestTimeout” / “Connection timeout”

원인: 네트워크 지연, 방화벽, DNS 문제, requestTimeoutMs 짧음

해결법:

config.connectTimeoutMs = 5000;
config.requestTimeoutMs = 30000;
// 재시도: RetryStrategy 설정 (기본적으로 SDK가 재시도함)

7. 성능 최적화 팁

7.1 클라이언트 재사용

클라이언트는 스레드 세이프합니다. 앱 전체에서 싱글톤으로 재사용하세요.

class AwsServiceHolder {
public:
    static Aws::S3::S3Client& getS3() {
        static Aws::S3::S3Client client(getConfig());
        return client;
    }
    static Aws::DynamoDB::DynamoDBClient& getDynamo() {
        static Aws::DynamoDB::DynamoDBClient client(getConfig());
        return client;
    }
private:
    static Aws::Client::ClientConfiguration getConfig() {
        Aws::Client::ClientConfiguration c;
        c.region = "ap-northeast-2";
        c.maxConnections = 100;
        return c;
    }
};

7.2 S3 멀티파트 업로드 병렬화

대용량 파일은 파트별로 std::async 등으로 병렬 업로드할 수 있습니다.

// 파트 업로드를 std::async로 병렬 실행
std::vector<std::future<PartETag>> futures;
for (size_t i = 0; i < numParts; ++i) {
    futures.push_back(std::async(std::launch::async, [&, i]() {
        return uploadPart(uploadId, i + 1, ...);
    }));
}
for (auto& f : futures) {
    parts.push_back(f.get());
}

7.3 DynamoDB BatchGetItem / BatchWriteItem

여러 아이템을 한 번에 조회·쓰기하면 RTT를 줄일 수 있습니다.

#include <aws/dynamodb/model/BatchGetItemRequest.h>

// BatchGetItem: 최대 100개 키, 16MB 응답 제한
Aws::DynamoDB::Model::BatchGetItemRequest batchReq;
// KeysAndAttributes에 테이블별 키 목록 추가

7.4 Connection Pool 설정

config.maxConnections = 100;  // 동시 연결 수
config.connectTimeoutMs = 3000;
config.requestTimeoutMs = 30000;

7.5 S3 TransferManager (고수준 API)

AWS SDK는 TransferManager를 제공해 멀티파트·병렬 업로드/다운로드를 자동화합니다. 공식 예제를 참고하세요.


8. 프로덕션 패턴

8.1 에러 처리 및 로깅

bool safePutObject(Aws::S3::S3Client& client,
                  const std::string& bucket, const std::string& key,
                  std::shared_ptr<Aws::IOStream> body) {
    try {
        Aws::S3::Model::PutObjectRequest request;
        request.SetBucket(bucket);
        request.SetKey(key);
        request.SetBody(body);

        auto outcome = client.PutObject(request);

        if (!outcome.IsSuccess()) {
            // 구조화된 로깅 (CloudWatch Logs 등)
            std::cerr << "[S3_ERROR] " << outcome.GetError().GetExceptionName()
                      << ": " << outcome.GetError().GetMessage()
                      << " (bucket=" << bucket << ", key=" << key << ")"
                      << std::endl;
            return false;
        }
        return true;
    } catch (const std::exception& e) {
        std::cerr << "[S3_EXCEPTION] " << e.what() << std::endl;
        return false;
    }
}

8.2 재시도 정책

SDK 기본 재시도는 지수 백오프를 사용합니다. RetryStrategy를 커스터마이즈할 수 있습니다.

#include <aws/core/client/DefaultRetryStrategy.h>

Aws::Client::ClientConfiguration config;
config.retryStrategy = Aws::MakeShared<Aws::Client::DefaultRetryStrategy>(
    "custom", 3, 1000);  // 최대 3회 재시도, 초기 딜레이 1초

8.3 환경별 설정

std::string getAwsRegion() {
    const char* env = std::getenv("AWS_REGION");
    if (env && strlen(env) > 0) return env;
    return "ap-northeast-2";  // 기본값
}

// 로컬스택 등 커스텀 엔드포인트
void setupLocalStack(Aws::Client::ClientConfiguration& config) {
    const char* endpoint = std::getenv("AWS_ENDPOINT_URL");
    if (endpoint) {
        config.endpointOverride = endpoint;
    }
}

8.4 헬스체크

bool checkS3Connectivity(const Aws::Client::ClientConfiguration& config) {
    Aws::S3::S3Client client(config);
    Aws::S3::Model::ListBucketsRequest request;
    auto outcome = client.ListBuckets(request);
    return outcome.IsSuccess();
}

8.5 메트릭·모니터링

  • CloudWatch Metrics: SDK가 자동으로 요청 수·에러 수 등을 메트릭으로 보낼 수 있음
  • X-Ray: 분산 추적 활성화 시 세그먼트 기록
  • 로그: 구조화된 JSON 로그로 CloudWatch Logs에 전송

9. 구현 체크리스트

환경 설정

  • AWS SDK for C++ 설치 (vcpkg 또는 시스템 패키지)
  • CMake에 s3, dynamodb, lambda, core 컴포넌트 링크
  • Aws::InitAPI / ShutdownAPI 호출

Credentials

  • 로컬: ~/.aws/credentials 또는 환경 변수 설정
  • EC2/ECS: Instance Profile / Task Role 할당
  • IAM 정책: S3, DynamoDB, Lambda 필요한 권한 포함

S3

  • 버킷 생성 및 리전 확인
  • 5MB 이상 파일은 멀티파트 업로드 사용
  • 에러 시 AbortMultipartUpload 호출

DynamoDB

  • 테이블 스키마 확인 (파티션 키·정렬 키 타입)
  • AttributeValue 타입 일치 (SetS vs SetN)
  • Query 시 KeyConditionExpression 올바른 사용

Lambda

  • 함수명·리전 일치
  • InvocationType에 맞는 타임아웃 설정
  • IAM에 lambda:InvokeFunction 권한

프로덕션

  • 클라이언트 재사용 (싱글톤 등)
  • 에러 로깅·모니터링
  • 재시도 정책 검토
  • 환경별 설정 분리 (dev/staging/prod)

10. 정리

항목설명
S3PutObject, GetObject, 멀티파트 업로드, ListObjectsV2
DynamoDBPutItem, GetItem, UpdateItem, Query, BatchGetItem
LambdaInvoke (RequestResponse / Event)
IAM환경 변수, Instance Profile, credentials 체인
에러NoSuchBucket, ValidationException, ResourceNotFoundException, 타임아웃
성능클라이언트 재사용, 멀티파트 병렬화, BatchGetItem
프로덕션로깅, 재시도, 환경별 설정, 헬스체크

핵심 원칙:

  1. SDK 초기화·종료를 반드시 수행
  2. AttributeValue·스키마 타입 일치
  3. 리전·엔드포인트·함수명 확인
  4. 프로덕션에서는 IAM Role 사용
  5. 클라이언트 재사용으로 성능 확보

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. 클라우드 네이티브 애플리케이션, 서버리스 아키텍처, 스케일러블 스토리지, 이벤트 기반 처리 등에 활용합니다. 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. AWS SDK for C++ 공식 문서aws-doc-sdk-examples GitHub를 참고하세요.

한 줄 요약: C++에서 S3·DynamoDB·Lambda를 연동하고, 실무 에러·성능·프로덕션 패턴까지 마스터할 수 있습니다.


관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3