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

[2026] 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 공유

아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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++를 설치합니다. 다음은 cmake를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 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를 호출해야 합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 파일 업로드

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 문자열(버퍼) 업로드

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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: 파일 다운로드

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 이상 파일은 멀티파트 업로드로 청크 단위 전송하면 메모리·재시도에 유리합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 객체 목록 조회

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 아이템 저장

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 단일 아이템 조회

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 속성 업데이트

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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: 파티션 키 기준 조회

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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 (동기 호출)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

다음은 cpp를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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. 기타 프로파일 다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 환경 변수 설정 예시
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_DEFAULT_REGION=ap-northeast-2

5.2 프로파일 지정

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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 권한 예시: 아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

{
  "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 타입이 테이블 스키마와 다름 해결법: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 잘못된 예: 파티션 키가 문자열인데 숫자로 전달
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 클라이언트 재사용

클라이언트는 스레드 세이프합니다. 앱 전체에서 싱글톤으로 재사용하세요. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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 등으로 병렬 업로드할 수 있습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 파트 업로드를 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를 줄일 수 있습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

#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 에러 처리 및 로깅

다음은 cpp를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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를 커스터마이즈할 수 있습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

8.3 환경별 설정

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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 헬스체크

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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를 연동하고, 실무 에러·성능·프로덕션 패턴까지 마스터할 수 있습니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3