클라우드 비용 없이 2GB 서버에서 39개 제품 재고 파이프라인 운영하기
요약
본 글은 클라우드 비용 없이 저사양 서버(vCPU 1개, RAM 2GB)에서 다수의 제품 재고 파이프라인을 안정적으로 운영하는 방법을 제시합니다. 핵심은 메타데이터 관리와 상태 추적에 있으며, 병렬 처리 대신 순차 처리를 통해 메모리 오버플로우를 방지하고 작업의 신뢰성을 높이는 데 중점을 둡니다.
핵심 포인트
- 메타데이터는 고정된 계약이므로 스크립트가 소유해야 합니다.
- 상태 추적을 위한 '원장(ledger)' 시스템 구축이 필수입니다.
- 병렬 처리 대신 순차 처리를 통해 메모리 누수를 방지하세요.
- 최대 RSS를 측정하여 실제 서버의 한계치를 파악하는 것이 중요합니다.
새벽 2시 40분에 저는 Adobe Stock에 같은 40개의 파일을 두 번 업로드하는 배치 재실행을 지켜보며 제 파이프라인을 망쳤습니다. 업로드가 중간에 시간 초과가 되어 스크립트를 다시 시작했고, 제 원장(ledger)은 제가 업데이트하는 것을 잊어버린 텍스트 파일이었습니다. 이것이야말로 계정이 플래그 지정되는 가장 빠른 방법이며, 저는 스스로 그렇게 했습니다.
이 실패 때문에 저는 이제 모든 것을 메모리 대신 원장에 기록하고, 전체 39개 제품 파이프라인을 vCPU 1개와 RAM 2GB를 가진 서버에서 운영합니다. 클라우드 계정도 없고, GPU도 없고, 월별 청구서도 없습니다. 같은 작업을 하고 싶다면 실제로 중요한 것이 무엇인지 알려드리겠습니다.
제출물이 죽는 곳은 예술이 아니라 메타데이터입니다
세 개의 주요 스톡 채널 모두 파일 하나를 보기 전에 CSV 헤더를 검증합니다. Adobe는 5개 열을 기대하고, Vecteezy는 4개, Dreamstime은 15개를 기대합니다. 열 하나만 잘못해도 아무도 당신의 작업을 보기 전에 업로드가 즉시 거부됩니다.
그래서 저는 스프레드시트를 수동으로 편집하는 것을 중단했습니다. make_metadata.py는 파일 폴더를 읽어 각 플랫폼이 요구하는 정확한 헤더를 작성하며, 그 이상은 없습니다. Adobe에는 5개 열을 제공하고, Vecteezy에는 4개, Dreamstime에는 15개를 제공합니다. 만약 파일 이름이 vintage-compass-rose.jpg라면, 기본적으로 제목 케이스(title-cased)의 제목이 되며, 이는 첫 번째 시도로는 괜찮습니다. 제가 실제 키워드를 원할 때는 titles.json을 전달하고, 제출하기 전에는 항상 기본값을 대체합니다. 왜냐하면 파일 이름은 키워드 전략이 아니기 때문입니다.
요점은 스크립트가 영리하다는 것이 아닙니다. 요점은 헤더가 고정된 계약(fixed contract)이라는 것이므로, 스크립트가 소유해야지 긴 세션 끝에 당신의 기억이 소유해서는 안 된다는 것입니다.
이중 게시(Double-posting)는 아무도 계획하지 않는 실패 모드입니다
제 새벽 2시 실수에는 하나의 간극 때문에 발생했습니다. 즉, 무엇이 이미 업로드되었는지 아무것도 기억하지 못했다는 것입니다. 배치 스크립트는 기본적으로 상태가 없으며(stateless), 시간 초과나 충돌 후 재실행 시 모든 것을 재생합니다.
upload_tracker.py는 세 가지 명령(pending은 게시되지 않은 항목을 보여주고, done은 항목을 표시하며, status는 개수를 출력함)을 중심으로 구축된 작은 원장(ledger)입니다. Python 표준 라이브러리만 사용하기 때문에 설치할 것도 없고 망가뜨릴 것도 없습니다. 제가 실행하는 모든 배치 스크립트는 이제 행동에 나서기 전에 이 원장을 확인합니다. 재실행 시 비어 있는 pending 목록을 발견하고 멈춥니다. 이 단 하나의 습관이 어떤 업로드 속도 향상보다 더 가치가 있습니다.
한 번에 하나씩 실행하면 2GB RAM으로 충분하다
저는 몇 달 동안 이것을 잘못 이해했습니다. 1코어 박스에서 이미지 작업을 병렬로 분산하는 것은 아무것도 빠르게 만들지 못하며, 단지 렌더링 도중에 OOM killer(Out-of-Memory Killer)에 의해 프로세스가 종료되게 할 뿐입니다.
효과를 보게 해준 습관들:
- 한 번에 하나의 프로세스만 실행합니다. 절대 병렬 이미지 작업은 하지 않습니다.
- 각 에셋 처리 후
gc.collect()를 호출하여 200개 항목 배치 전체에서 메모리가 누적되는 것을 방지합니다. - 평균 사용량이 아닌 최대 RSS(Resident Set Size)를 측정합니다.
resource.getrusage(RUSAGE_SELF).ru_maxrss가 실제 한계치를 알려줍니다. 평균 사용량은 괜찮다고 말했지만, 최대 사용량은 진실을 말해주었습니다. - 무거운 렌더링 단계는 각 서브프로세스별 파일로 실행하여 프로세스가 종료될 때 OS가 메모리를 회수하도록 합니다.
- 필요한 해상도로 렌더링합니다. 300dpi 대신 60dpi에서 미리보기를 검사하는 것만으로도 한 단계의 메모리 사용량이 극적으로 줄었고, 출력물에서는 차이를 느낄 수 없었습니다.
이 모든 과정(완성된 제품 39개와 패키징된 출력물 856MB)은 이 제한 내에서 실행되었습니다. 그 배경에 있는 상위 프로젝트는 약 19,000줄의 파이프라인 코드로 구성되어 있으며, 그 어떤 부분도 GPU가 필요하지 않습니다.
콘텐츠를 제품으로 만드는 것은 주말이 아니라 두 개의 명령이다
저는 유용한 자료로 가득 찬 Markdown 문서를 research/ 폴더에 방치해 두고 아무것도 하지 않게 놔두었습니다.
두 스크립트가 이 모든 것을 가능하게 합니다. md2pdf.py는 Markdown 파일을 받아 제목, 표, 목록, 링크 등이 포함된 스타일리시한 PDF를 단 하나의 명령어로 렌더링합니다. pack_product.py는 폴더를 입력받아 zip 파일, 1280×720 커버 이미지, 그리고 600×600 썸네일을 생성합니다. 실제 커버가 있는 zip 파일이 판매됩니다. 아무 내용 없는 파일은 그렇지 않으며, 저는 저의 자체 카탈로그에서 그 판매 수치를 증명할 수 있습니다.
두 스크립트는 정확히 두 개의 외부 패키지(third-party packages)만 사용합니다: 커버용 Pillow와 PDF용 reportlab입니다. 나머지 모든 것은 표준 라이브러리(standard library)로 구성되어 있습니다. 작은 서버에서는 의존성 개수가 RAM 예산과 직결됩니다.
저를 구원한 폴더 레이아웃
카탈로그는 고정된 구조가 없으면 잡동사니 서랍처럼 변합니다. 제 카탈로그는 다섯 개의 폴더로 구성되어 있습니다: 원본 결과물용 assets/, 제출 준비 완료 파일과 메타데이터 CSV 파일을 위한 output/, 완성된 zip 파일과 커버를 위한 packs/, 노트와 자료 출처용 research/, 그리고 도구들을 위한 scripts/입니다.
제가 뼈저리게 배운 규칙이 하나 있습니다: 절대 research/ 폴더를 삭제하지 마세요. 저는 한때
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기