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++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.
목차
- 환경 설정 및 SDK 초기화
- S3 파일 저장·다운로드
- DynamoDB NoSQL 연동
- Lambda 함수 호출
- IAM 인증 및 Credentials
- 자주 발생하는 에러와 해결법
- 성능 최적화 팁
- 프로덕션 패턴
- 구현 체크리스트
- 정리
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는 다음 순서로 자격 증명을 찾습니다:
- 환경 변수:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN(선택) ~/.aws/credentials파일- EC2 Instance Profile (EC2 메타데이터)
- ECS Task Role
- 기타 프로파일
# 환경 변수 설정 예시
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가 아님
해결법:
- 테이블명·리전 확인
- 테이블 생성 직후
DescribeTable로TableStatus == 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. 정리
| 항목 | 설명 |
|---|---|
| S3 | PutObject, GetObject, 멀티파트 업로드, ListObjectsV2 |
| DynamoDB | PutItem, GetItem, UpdateItem, Query, BatchGetItem |
| Lambda | Invoke (RequestResponse / Event) |
| IAM | 환경 변수, Instance Profile, credentials 체인 |
| 에러 | NoSuchBucket, ValidationException, ResourceNotFoundException, 타임아웃 |
| 성능 | 클라이언트 재사용, 멀티파트 병렬화, BatchGetItem |
| 프로덕션 | 로깅, 재시도, 환경별 설정, 헬스체크 |
핵심 원칙:
- SDK 초기화·종료를 반드시 수행
- AttributeValue·스키마 타입 일치
- 리전·엔드포인트·함수명 확인
- 프로덕션에서는 IAM Role 사용
- 클라이언트 재사용으로 성능 확보
자주 묻는 질문 (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 |