...
람다 /tmp 임시 파일
추출, 변환, 로드 작업과 PDF 파일 생성 또는 미디어 처리와 같은 데이터 집약적인 애플리케이션일 경우, 대용량 데이터를 잠시 어딘가에 저장해야 할 필요가 생긴다.
단순하게 람다 자체 내에서 변수로 저장하게 되면 안그래도 적은 람다 메모리를 코드 실행이 아닌 데이터 저장에 성능을 뺏겨버릴수가 있다.
그래서 Lambda 에서는 /tmp 임시 공간을 무료로 제공해준다.
/tmp 영역은 말 그대로 람다에서 파일을 저장할 수 있는 로컬 스토리지 영역이다.
람다에서 이곳에 파일을 저장하고 저장된 파일을 꺼내 쓸 수 있다.
/tmp 영역은 람다의 실행 환경의 수명 동안 보존되며, 호출 사이의 데이터에 대한 임시 캐시를 제공한다. 따라서 람다 컨테이너가 종료 되고 새 실행 환경이 생성될 때마다 이 영역은 삭제된다.
즉, /tmp는 임시 저장 영역으로서, 단일 호출에서 코드에 필요한 데이터에만 사용해야 한다. (람다 컨테이너가 종료되면 초기화 되니까)
/tmp의 파일 작업은 로컬 하드 디스크와 동일하며 빠른 I/O 처리량을 제공한다. 그리고 용량은 512MB에서 최대 10GB까지 데이터 저장소로 사용할 수 있다.
예전에는 /tmp 스토리지를 512MB 밖에 지원안했기 때문에, Lambda에서 큰 파일을 다룰 필요가 있어 중간파일을 생성할때 S3 혹은 EFS와 연결하여 파일을 만들고 수정하곤 했지만, 이제는 바로 로컬 임시 스토리지를 이용하여 더욱 빠르게 작업이 가능하게 되었다.
/tmp 임시 스토리지 비용은 함수에 할당한 임시 스토리지의 양과 함수 실행 기간(밀리초 단위로 측정)에 따라 다르게 책정 된다. 무료로 512MB 까지는 제공하지만 그 이상 용량을 사용할땐 호출 기간동안 부과된다.
람다 함수에서 /tmp 로 접근 하는법은 간단하다.
람다 함수를 어느 로컬 폴더에서 돌아가는 소스 파일이라고 생각하고, /tmp (루트/tmp) 경로를 파일디스크립터로 불러오기만 하면 된다. 여기서 주의할 점은 상대경로가 아니라 루트 절대경로 기준인 점을 숙지하자.
람다 /tmp 사용 - Node.js
노드에서 람다의 /tmp 영역에 파일을 쓰고 읽는 로직을 작성해보자.
const fs = require('fs');
const fsPromise = fs.promises;
exports.handler = async (event) => {
if(!fs.existsSync('/tmp/test.txt')) { // 만일 tmp 폴더에 파일이 존재하지 않으면
const write = 'This is a test text'; // 파일에 쓸 내용
await fsPromise.writeFile('/tmp/test.txt', write); // 파일 쓰기
console.log('파일 쓰기 완료 !!!');
}
const read = await fsPromise.readFile('/tmp/test.txt'); // 파일 읽기
console.log(read.toString()); // 파일 내용 출력
}
함수를 실행하면 아무 문제없이 /tmp 영역에 파일을 쓰고 읽음을 알수 있다.
다시 테스트를 바로 재실행하면 다음과 같이 파일 쓰기는 안하고 읽기 만을 한 것을 알 수 있다.
이는 람다 함수를 5분 이내에 재실행하면 람다 컨테이너가 파괴되지 않아 /tmp 영역이 아직 살아있어서 위의 조건문에서 걸러져서 그렇다.
보다 자세한 람다 함수 실행원리에 대해 알고 싶다면 이 글을 참고하길 바란다.
하지만 fs 모듈로 대용량의 파일을 한꺼번에 버퍼를 읽어 처리하면 과부하가 와 함수가 터지게 된다.
그래서 대부분의 노드 개발자들은 Stream을 이용해 대용량 파일을 적은 메모리로 처리한다.
const fs = require('fs');
exports.handler = async (event) => {
if (!fs.existsSync('/tmp/test.txt')) {
// 만일 tmp 폴더에 파일이 존재하지 않으면
const write_data = 'This is a test text Stream !!'; // 파일에 쓸 내용
const writeStream = fs.createWriteStream('/tmp/test.txt');
writeStream.on('finish', () => {
console.log('파일 쓰기 완료 !!!');
});
writeStream.write(write_data); // chunck로 파일을 쓰기
writeStream.end(); // 파일 쓰기 완료
}
const readStream = fs.createReadStream('/tmp/test.txt');
const read_data = [];
// chunck크기 만큼 읽을 때 마다 이벤트를 발생시킨다. 파일이 크면 여러번 발생됨
readStream
.on('data', (chunck) => {
read_data.push(chunck);
})
.on('end', () => {
// chunck읽기가 다 끝마친 경우
// 파일 내용 출력
console.log(Buffer.concat(read_data).toString()); // chunck로 나뉘어진 버퍼 데이터들을 합친다.
})
.on('error', (err) => {
// stream도 비동기라, 비동기들은 항상 에러 처리를 직접 정의해 줘야 한다.
console.log('error : ', err);
});
};
/tmp 영역을 이용한 S3 응용 예제
다음 예제 코드는, S3 버킷에서 파일을 stream으로 읽어와 /tmp 에 저장하고 다시 다른 S3 버킷 경로로 파일을 넣는 예제이다. 중간에 /tmp 영역을 이용해 파일을 저장하는 이유는, 앞서 말했듯이 람다 함수내에서 버퍼 데이터를 그대로 변수에 저장해 놓고 사용할 경우 안그래도 크기가 적은 람다 함수 메모리를 최대한 활용할수 없기 때문이다.
const S3 = require('aws-sdk/clients/s3');
const s3 = new S3();
const fs = require('fs');
const Bucket = 'my-nodejs-practice';
const Key = '20220707145935696.pdf';
async function getObjects() {
return new Promise((resolve, reject) => {
const s3Stream = s3
.getObject({
Bucket,
Key,
})
.createReadStream();
s3Stream
.on('data', (data) => {
console.log('data : ', data);
})
.on('end', () => {
console.log('read end');
})
.on('error', (err) => {
// NoSuchKey: The specified key does not exist
fileStream.end();
console.error(err);
reject(err);
});
const fileStream = fs.createWriteStream('/tmp/temp', 'binary');
fileStream
.on('error', (err) => {
fileStream.end();
console.error(err);
reject(err);
})
.on('finish', () => {
console.log('Done.');
resolve('success');
});
s3Stream.pipe(fileStream); // 파이프로 /tmp 파일에 쓰기
});
}
async function putObjects() {
const fileStream = fs.createReadStream('/tmp/temp');
const s3Stream = await s3
.putObject({
Bucket,
Key: `test/${Key}`,
Body: fileStream,
})
.promise();
console.log('put done : ', s3Stream);
return s3Stream;
}
exports.handler = async (event) => {
//* S3에서 객체를 가져와 스트림으로 /tmp에 파일 쓰기
await getObjects();
//* /tmp에 있는 파일을 스트림으로 읽어와서 S3에 넣는다.
await putObjects();
};
람다 /tmp 사용 - Python
다음 예제 코드에서 /tmp 파일에 newData 라는 파일을 생성하고 내용을 추가했다.
함수를 실행하고 로그에 실제로 /tmp 파일의 내용이 출력이 되게 된다.
import json
import os
# /tmp/newData 파일에 쓰기
with open("/tmp/newData", "a") as file_data:
# 파일 내용 쓰기
print("Write data to tmp file", file=file_data)
print("tmp file using test", file=file_data)
def lambda_handler(event, context):
# 파일 읽기
with open("/tmp/newData", "r") as file_data:
for each_line in file_data:
print(each_line) # 파일 내용 출력
return "tmp_file_test"
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.