사이드 프로젝트의 게시글 작성 화면이 있는데 사용자가 이미지 파일을 선택하면 서버에서는 요청 받은 이미지 파일들을 Google Cloud Storage에 저장하는 방식으로 처리를 간단하게 구현하였다.
아직 서버에 부하가 있을만큼 트래픽이 발생하지는 않지만, 추 후 발생할 문제점에 대해 고민을 해보았다..
- 대용량 이미지 업로드 시 속도 저하: 네트워크 한계로 인해 이미지 업로드 속도가 느려질수도 있음
- 서버 부하 발생: 이미지 파일 자체를 클라이언트로 부터 받고, 해당 이미지 파일을 저장하기 위해 Google Cloud Storage로 요청하는 과정에서 부하 발생
이러한 문제점들이 발생할 수도 있다는 생각이 들었다.
이미지 업로드 시 서버 처리 과정 (To -Be)
https://cloud.google.com/storage/docs/access-control/signed-urls#overview
서명된 URL | Cloud Storage | Google Cloud
의견 보내기 서명된 URL 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 페이지에서는 특정 Cloud Storage 리소스에 대한 제한된 시간 동안 액세스 권한을 부
cloud.google.com
아래 Docs 를 참고하여 코드를 작성하였다.
// 파일 업로드 URL 발급
async uploadToGCS(where: string, date: string, fileName: string, fileType: string): Promise<string> {
try {
const contentType = this.validateFileType(fileType);
const filePath = this.generateFilePath(where, date, fileName);
const fileSize = "5242880"; //5 * 1048576; // 5MB
// 파일 업로드
const options: GetSignedUrlConfig = {
version: "v4",
action: "write",
expires: Date.now() + 1 * 60 * 1000, // 1분 후 만료
contentType: contentType,
extensionHeaders: {
"x-goog-content-length-range": `0,${fileSize}`,
},
};
const [url] = await this.storage
.bucket(this.bucketName)
.file(filePath)
.getSignedUrl(options);
return url;
} catch (error) {
console.error("Error generating signed URL:", error);
throw error;
}
}
validateFileType, generateFilePath의 경우 개인적으로 파일 확장자 체크와 경로를 생성해 주는거라 삭제 하셔도 무관하다.
"x-goog-content-length-range"의 경우 악의적으로 용량이 큰 파일을 업로드 할까봐, 5MB이상 업로드 하지 못하게하는 옵션이다.
이미지와 같이 Signed URL을 발급 받게 된다.
발급된 URL은 지정한 filepath의 file name으로 파일이 저장된다. 즉 1개의 파일 업로드시 1개의 발급된 URL을 사용해야 한다는 뜻...
이제 발급된 URL로 PUT 요청을 통해 이미지를 아래와 같이 업로드 하면 된다!
큰 기능의 로직은 여기서 끝이다.
이제 고민해야 할 부분이 한가지 더 남았다. 이미지가 저장된 경로를 클라이언트에게 전달해야하고, 만약 이미지 업로드에 실패한 이미지가 있으면 그 부분도 에러 처리가 필요하다.
고민 끝에
파일 존재 여부를 확인하는 로직을 추가하였다.
TypeScript + GCP Storage: 비동기적으로 파일 존재 여부 확인하기
Google Cloud Storage(GCS)를 사용할 때, 특정 경로에 파일이 존재하는지 확인하는 기능이 필요하다. 예를 들어, 여러 개의 파일이 특정 날짜에 업로드되었는지 확인하고, 존재하는 파일의 공개 URL을 가져오는 작업이 필요할 수도 있다.
async checkFileExists(where: string, date: string, fileName: string[]): Promise<{ [key: string]: any; }> {
try {
const fileExistPromises = fileName.map(async (file) => {
const filePath = this.generateFilePath(where, date, file);
const exist = await this.storage.bucket(this.bucketName).file(filePath).exists();
if (exist[0]) {
const publicUrl = this.storage.bucket(this.bucketName).file(filePath).publicUrl();
return { [file]: publicUrl };
} else {
return { [file]: false };
}
});
const resultsArray = await Promise.all(fileExistPromises);
return Object.assign({}, ...resultsArray);
} catch (error) {
console.error("checkFileExists", error);
throw error;
}
}
주요 기능
- 파일 경로 생성: generateFilePath(where, date, file) 함수를 사용하여 특정 디렉터리와 날짜를 포함한 파일 경로를 생성.
- 파일 존재 여부 확인:
- this.storage.bucket(this.bucketName).file(filePath).exists()를 호출하여 파일이 존재하는지 확인.
- 이 함수는 배열 [boolean]을 반환하며, exist[0]가 true이면 파일이 존재하는 것이다.
- 파일 공개 URL 가져오기:
- 파일이 존재하면 publicUrl()을 사용하여 GCS의 공개 URL을 반환.
- 병렬 처리 (Promise.all):
- fileName 배열의 각 파일에 대해 exists() 메서드를 실행하고, Promise.all()을 사용하여 모든 비동기 요청을 병렬로 처리.
- 결과 반환:
- { 파일명: 공개 URL 또는 false } 형식의 객체를 반환.
동작 과정 예시
만약 아래와 같은 입력 값이 주어진다면:
const where = "uploads";
const date = "2024-02-10";
const fileNames = ["image1.png", "image2.jpg", "document.pdf"];
스토리지 버킷의 파일 상태가 아래와 같다면:
- image1.png ✅ (존재함)
- image2.jpg ❌ (존재하지 않음)
- document.pdf ✅ (존재함)
함수 실행 결과는 다음과 같이 반환될 것입니다:
{
"image1.png": "https://storage.googleapis.com/my-bucket/uploads/2024-02-10/image1.png",
"image2.jpg": false,
"document.pdf": "https://storage.googleapis.com/my-bucket/uploads/2024-02-10/document.pdf"
}
이런식으로 로직을 구성해봤다.
여러가지 고민과 기능을 구현하면서 좋은 경험이 되었다.
조언은 언제나 환영입니다!
긴글 읽어주셔서 감사합니다.
'Google Cloud' 카테고리의 다른 글
[Cloud Storage] 버킷에 CORS를 적용할 수 있다고? (0) | 2025.02.20 |
---|---|
[Google Cloud] API Gateway에 대한 개념과 설정 방법 (0) | 2025.02.11 |
Cloud Storage에 JSON파일이 Upload 될 때, Bigquery Loads 파이프 라인 구성 (0) | 2024.08.13 |
[Secret Manager] API 를 통하여 SSH 비밀 키 추출 및 SFTP 전송 (0) | 2024.04.09 |
[Cloud Storage] MD5 hash 값을 활용하여 파일 무결성 체크 (0) | 2024.03.25 |