AWS SDK v2 vs v3
AWS SDK for JavaScript는 v3부터 완전히 재설계되었다. 가장 큰 변화는 모듈화다.
기본 차이점
| 항목 | SDK v2 | SDK v3 |
|---|
| 패키지 구조 | 단일 패키지 (aws-sdk) | 모듈화 (@aws-sdk/*) |
| 번들 크기 | 50-70MB (전체) | 100-500KB (필요한 것만) |
| Lambda Layer | 70MB+ | 5MB 이하 |
| Cold Start | 1-2초 | 300-500ms |
| TypeScript | 별도 타입 정의 필요 | 네이티브 지원 |
| Promise | .promise() 호출 필요 | 기본 Promise |
| Middleware | 제한적 | 완전 지원 |
실전 비교: Collector Lambda
SDK v2 (Before)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // ❌ 전체 SDK 로드 (50MB+)
const AWS = require('aws-sdk');
const s3 = new AWS.S3({ region: 'ap-northeast-2' });
const dynamodb = new AWS.DynamoDB({ region: 'ap-northeast-2' });
const cloudwatch = new AWS.CloudWatch({ region: 'ap-northeast-2' });
const sns = new AWS.SNS({ region: 'ap-northeast-2' });
// S3 업로드
const params = {
Bucket: 'my-bucket',
Key: 'data.json',
Body: JSON.stringify(data)
};
s3.putObject(params).promise() // 👈 .promise() 필수
.then(result => console.log(result))
.catch(err => console.error(err));
|
SDK v3 (After)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // ✅ 필요한 클라이언트만 임포트 (각 ~100KB)
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');
const { CloudWatchClient, PutMetricDataCommand } = require('@aws-sdk/client-cloudwatch');
const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
// 클라이언트 초기화 (한 번만)
const s3Client = new S3Client({ region: 'ap-northeast-2' });
const dynamoDBClient = new DynamoDBClient({ region: 'ap-northeast-2' });
const cloudwatchClient = new CloudWatchClient({ region: 'ap-northeast-2' });
const snsClient = new SNSClient({ region: 'ap-northeast-2' });
// S3 업로드 (Command 패턴)
const command = new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'data.json',
Body: JSON.stringify(data),
ContentType: 'application/json'
});
await s3Client.send(command); // 👈 기본 Promise, .promise() 불필요
|
Command 패턴 (Command Pattern)
SDK v3는 Command 패턴을 도입했다. 각 API 호출이 독립적인 Command 객체가 된다.
장점
- 명확한 의도: 코드만 봐도 어떤 API를 호출하는지 명확
- 타입 안정성: TypeScript에서 자동 완성 지원
- 재사용 가능: Command 객체를 여러 번 재사용 가능
- Middleware 지원: Command에 미들웨어 추가 가능
예시: S3 파일 업로드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3Client = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });
async function storeToS3(payload) {
const now = new Date();
const year = now.getUTCFullYear();
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
const day = String(now.getUTCDate()).padStart(2, '0');
// 데이터 타입 자동 감지
let dataType = 'general';
if (payload.performance && !payload.system_info) {
dataType = 'performance';
} else if (payload.system_info || payload.software || payload.patches) {
dataType = 'data';
}
const s3Key = `processed/${payload.agent_metadata.group_name}/${dataType}/${year}/${month}/${day}/${payload.agent_metadata.agent_id}-${Date.now()}.json`;
// Command 생성
const command = new PutObjectCommand({
Bucket: process.env.PROCESSED_BUCKET,
Key: s3Key,
Body: JSON.stringify(payload),
ContentType: 'application/json'
});
// 전송
await s3Client.send(command);
}
|
실전 사례: Processor Lambda
전체 코드 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
| const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');
const { CloudWatchClient, PutMetricDataCommand } = require('@aws-sdk/client-cloudwatch');
const { SNSClient, PublishCommand } = require('@aws-sdk/client-sns');
// 클라이언트는 Lambda 컨테이너 재사용을 위해 전역으로 선언
const s3Client = new S3Client({
region: process.env.AWS_REGION || 'us-east-1'
});
const dynamoDBClient = new DynamoDBClient({
region: process.env.AWS_REGION || 'us-east-1'
});
const cloudwatchClient = new CloudWatchClient({
region: process.env.AWS_REGION || 'us-east-1'
});
const snsClient = new SNSClient({
region: process.env.AWS_REGION || 'us-east-1'
});
exports.handler = async (event, context) => {
try {
const processingPromises = event.Records.map(async (record) => {
const payload = JSON.parse(record.body);
// 1. 보안 위협 탐지
if (payload.security_findings && Array.isArray(payload.security_findings) && payload.security_findings.length > 0) {
for (const finding of payload.security_findings) {
if (finding.severity === 'CRITICAL' || finding.severity === 'HIGH') {
await sendAlert(finding, payload.agent_metadata);
}
}
}
// 2. S3 저장
if (process.env.PROCESSED_BUCKET) {
await storeToS3(payload);
}
// 3. DynamoDB 메타데이터 저장
if (process.env.METADATA_TABLE) {
await storeMetadata(payload);
}
// 4. CloudWatch 메트릭
if (process.env.CLOUDWATCH_NAMESPACE) {
await recordMetric(payload);
}
});
await Promise.all(processingPromises);
return {
statusCode: 200,
body: JSON.stringify({
message: 'Processing completed',
recordsProcessed: event.Records.length
})
};
} catch (error) {
console.error('Error processing SQS messages:', error);
throw error;
}
};
|
DynamoDB 저장
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| async function storeMetadata(payload) {
const agentId = payload.agent_metadata?.agent_id || 'unknown';
const timestamp = payload.timestamp || payload.agent_metadata?.timestamp || new Date().toISOString();
const receivedAt = new Date().toISOString();
const sourceIp = payload.source_ip || 'N/A';
// DynamoDB Item 생성 - 빈 문자열은 제외
const item = {
agent_id: { S: String(agentId).trim() },
timestamp: { S: String(timestamp).trim() },
received_at: { S: String(receivedAt).trim() },
source_ip: { S: String(sourceIp).trim() },
data: { S: JSON.stringify(payload) }
};
const command = new PutItemCommand({
TableName: process.env.METADATA_TABLE,
Item: item
});
await dynamoDBClient.send(command);
}
|
CloudWatch 메트릭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| async function recordMetric(payload) {
// 데이터 타입 자동 감지
let dataType = 'general';
if (payload.performance && !payload.system_info) {
dataType = 'performance';
} else if (payload.system_info || payload.software || payload.patches) {
dataType = 'data';
}
const command = new PutMetricDataCommand({
Namespace: process.env.CLOUDWATCH_NAMESPACE || 'ITSM/Agents',
MetricData: [
{
MetricName: 'DataReceived',
Value: 1,
Unit: 'Count',
Timestamp: new Date(),
Dimensions: [
{
Name: 'AgentGroup',
Value: payload.agent_metadata.group_name
},
{
Name: 'AgentID',
Value: payload.agent_metadata.agent_id
},
{
Name: 'DataType',
Value: dataType // 'data' 또는 'performance'
}
]
}
]
});
await cloudwatchClient.send(command);
}
|
SNS 알림
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| async function sendAlert(finding, agentMetadata) {
if (!process.env.ALERT_TOPIC_ARN) {
console.log('ALERT:', {
severity: finding.severity,
finding: finding,
agent: agentMetadata
});
return;
}
try {
const command = new PublishCommand({
TopicArn: process.env.ALERT_TOPIC_ARN,
Message: JSON.stringify({
severity: finding.severity,
finding: finding,
agent: agentMetadata,
timestamp: new Date().toISOString()
}),
Subject: `Security Alert: ${finding.severity} - ${agentMetadata.agent_id}`
});
await snsClient.send(command);
} catch (error) {
console.error('Error sending SNS alert:', error);
}
}
|
번들 크기 비교
SDK v2
1
2
3
4
5
| {
"dependencies": {
"aws-sdk": "^2.1000.0"
}
}
|
1
2
3
4
| # Lambda 배포 패키지
Lambda function package size: 72.4 MB
Lambda Layer: Not used (too large)
Cold Start: 1,800ms
|
SDK v3
1
2
3
4
5
6
7
8
| {
"dependencies": {
"@aws-sdk/client-s3": "^3.400.0",
"@aws-sdk/client-dynamodb": "^3.400.0",
"@aws-sdk/client-cloudwatch": "^3.400.0",
"@aws-sdk/client-sns": "^3.400.0"
}
}
|
1
2
3
4
| # Lambda 배포 패키지
Lambda function package size: 4.8 MB
Lambda Layer: Can be used
Cold Start: 420ms
|
성능 향상
| 지표 | SDK v2 | SDK v3 | 개선율 |
|---|
| 번들 크기 | 72.4 MB | 4.8 MB | 93% 감소 |
| Cold Start | 1,800ms | 420ms | 77% 단축 |
| 메모리 사용 | 512 MB | 256 MB | 50% 감소 |
| 비용 (월간 1M 요청) | $8.33 | $4.17 | 50% 절감 |
마이그레이션 가이드
1. 패키지 교체
1
2
3
4
5
6
7
8
| # v2 제거
npm uninstall aws-sdk
# v3 설치 (필요한 것만)
npm install @aws-sdk/client-s3
npm install @aws-sdk/client-dynamodb
npm install @aws-sdk/client-cloudwatch
npm install @aws-sdk/client-sns
|
2. 코드 변환 패턴
S3
1
2
3
4
5
6
7
8
9
| // v2
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
await s3.putObject({ Bucket: 'bucket', Key: 'key', Body: 'data' }).promise();
// v3
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({});
await s3.send(new PutObjectCommand({ Bucket: 'bucket', Key: 'key', Body: 'data' }));
|
DynamoDB
1
2
3
4
5
6
7
8
9
| // v2
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB();
await dynamodb.putItem({ TableName: 'table', Item: { ... } }).promise();
// v3
const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb');
const dynamodb = new DynamoDBClient({});
await dynamodb.send(new PutItemCommand({ TableName: 'table', Item: { ... } }));
|
CloudWatch
1
2
3
4
5
6
7
8
9
| // v2
const AWS = require('aws-sdk');
const cloudwatch = new AWS.CloudWatch();
await cloudwatch.putMetricData({ Namespace: 'NS', MetricData: [...] }).promise();
// v3
const { CloudWatchClient, PutMetricDataCommand } = require('@aws-sdk/client-cloudwatch');
const cloudwatch = new CloudWatchClient({});
await cloudwatch.send(new PutMetricDataCommand({ Namespace: 'NS', MetricData: [...] }));
|
Lambda 최적화 팁
1. 클라이언트를 전역으로 선언
1
2
3
4
5
6
| // ✅ 좋은 예: Lambda 컨테이너 재사용
const s3Client = new S3Client({ region: 'ap-northeast-2' });
exports.handler = async (event) => {
await s3Client.send(new PutObjectCommand({ ... }));
};
|
1
2
3
4
5
| // ❌ 나쁜 예: 매번 새로 생성
exports.handler = async (event) => {
const s3Client = new S3Client({ region: 'ap-northeast-2' }); // 비효율
await s3Client.send(new PutObjectCommand({ ... }));
};
|
2. 리전 명시
1
2
3
4
5
| // ✅ 좋은 예: 리전 명시
const s3Client = new S3Client({ region: 'ap-northeast-2' });
// ❌ 나쁜 예: 리전 누락 (환경 변수 의존)
const s3Client = new S3Client({});
|
리전을 명시하지 않으면 SDK가 자동으로 찾는 과정에서 시간이 소요된다.
3. 환경 변수 활용
1
2
3
| const s3Client = new S3Client({
region: process.env.AWS_REGION || 'us-east-1'
});
|
4. 병렬 처리
1
2
3
4
5
6
7
8
9
10
11
| // ✅ 좋은 예: 병렬 처리
await Promise.all([
storeToS3(payload),
storeMetadata(payload),
recordMetric(payload)
]);
// ❌ 나쁜 예: 순차 처리
await storeToS3(payload);
await storeMetadata(payload);
await recordMetric(payload);
|
TypeScript 지원
SDK v3는 TypeScript를 네이티브로 지원한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import { S3Client, PutObjectCommand, PutObjectCommandInput } from '@aws-sdk/client-s3';
const s3Client = new S3Client({ region: 'ap-northeast-2' });
async function uploadFile(bucket: string, key: string, body: string): Promise<void> {
const input: PutObjectCommandInput = {
Bucket: bucket,
Key: key,
Body: body,
ContentType: 'application/json'
};
const command = new PutObjectCommand(input);
await s3Client.send(command);
}
|
자동 완성
1
2
3
4
5
6
7
8
9
| const command = new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'file.json',
// 👇 IDE가 자동으로 사용 가능한 파라미터 제안
Body: '...',
ContentType: 'application/json',
ACL: 'private',
Metadata: { ... }
});
|
주의사항 (Gotchas)
1. .promise() 제거
1
2
3
4
5
6
| // v2
await s3.putObject(params).promise(); // ✅
// v3
await s3Client.send(command).promise(); // ❌ .promise() 없음
await s3Client.send(command); // ✅
|
2. 에러 타입 변경
1
2
3
4
5
6
7
8
9
10
11
12
13
| // v2
catch (err) {
if (err.code === 'NoSuchKey') { ... }
}
// v3
import { NoSuchKey } from '@aws-sdk/client-s3';
catch (err) {
if (err instanceof NoSuchKey) { ... }
// 또는
if (err.name === 'NoSuchKey') { ... }
}
|
3. 응답 형태 동일
1
2
3
4
| // v2, v3 모두 동일
const response = await s3.send(new GetObjectCommand({ ... }));
console.log(response.Body); // Stream
console.log(response.ContentType);
|
실전 팁 (Best Practices)
1. 필요한 클라이언트만 임포트
1
2
3
4
5
| // ✅ 좋은 예
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
// ❌ 나쁜 예
const { S3Client, ...rest } = require('@aws-sdk/client-s3'); // 모든 것 임포트
|
2. Command 재사용
1
2
3
4
5
6
7
8
9
10
11
| // 동일한 Command를 여러 번 재사용 가능
const command = new PutObjectCommand({
Bucket: 'my-bucket',
ContentType: 'application/json'
});
for (const data of dataList) {
command.input.Key = `${data.id}.json`;
command.input.Body = JSON.stringify(data);
await s3Client.send(command);
}
|
3. 에러 핸들링
1
2
3
4
5
6
7
8
9
10
| try {
await s3Client.send(new PutObjectCommand({ ... }));
} catch (error) {
console.error('S3 Error:', {
name: error.name,
message: error.message,
statusCode: error.$metadata?.httpStatusCode
});
throw error;
}
|
마치며
AWS SDK v3는 모듈화 설계로 Lambda 성능을 크게 개선합니다.
필요한 클라이언트만 임포트하면 93% 번들 크기 감소, Command 패턴으로 명확하고 재사용 가능한 API 호출, Cold Start 77% 시간 단축(1.8초 → 420ms), TypeScript 네이티브 지원으로 자동 완성 및 타입 체크, 메모리 사용량 감소로 50% 비용 절감이 가능합니다.
Lambda 함수를 개발한다면, SDK v3로 마이그레이션하는 것이 성능과 비용 모두에서 유리합니다. 특히 Serverless Framework와 함께 사용하면 개발 경험이 훨씬 향상됩니다.
도움이 되셨길 바랍니다! 😀