지난 3월, 저는 CSV 헤더 때문에 주말 전체를 날렸습니다. Adobe Stock에 올릴 준비가 된 에셋 60개를 가지고 있었고, 제가 원하는 순서로 메타데이터 파일을 만들었습니다. 제출 버튼을 눌렀더니 포털에서 몇 초 만에 배치(batch)를 거부했습니다. 헤더가 틀린 것이 문제였습니다. 다섯 개의 열이 필요한데 제 파일은 여섯 개였던 겁니다. 저는 그것을 고쳐서 그날 밤 Vecteezy에 같은 파일로 재제출했지만, 이번에는 Vecteezy가 네 개의 열을 원했기 때문에 또다시 거절당했습니다. 그때 저는 스프레드시트를 손으로 편집하는 것을 멈추고 대신 스크립트 작성을 시작했습니다.
8개월이 지난 지금, 이 파이프라인은 39개의 완성된 제품을 패키징했으며, 출력 용량은 약 856 MB이고, 사양은 vCPU 1개, RAM 2 GB, GPU 없음의 VPS에서 작동하고 있습니다. 실제로 효과가 있었던 것과 시간을 낭비했던 것을 알려드리겠습니다.
모든 스톡 채널은 고정된 업로드 CSV 형태를 가지고 있습니다. Adobe는 5개의 열을 원하고, Vecteezy는 4개를 원하며, Dreamstime은 15개를 원합니다. 헤더만 잘못해도 아무도 당신의 아트를 보기 전에 파일이 거부됩니다. 부분 점수는 없습니다.
그래서 저는 make_metadata.py를 작성했습니다. 이 스크립트는 폴더에 있는 파일들을 지정하면, 플랫폼을 선택할 수 있고, 해당 채널이 요구하는 정확한 헤더만 작성해 줍니다. 그 이상은 아닙니다. 파이썬 표준 라이브러리(Python standard library)만을 사용하기 때문에 작은 서버에서 설치하거나 고장낼 것이 없습니다. 실제 제목과 키워드가 담긴 titles.json 파일을 전달할 수도 있습니다. 이것 없이 실행하면 스크립트가 파일 이름을 타이틀 케이스(title-cases)로 변환하는데, 이는 첫 번째 시도에는 괜찮지만 제출하기 전에 교체해야 합니다. 파일 이름은 키워드가 아닙니다.
교훈은 이렇습니다. 플랫폼들은 여기서 기발함을 보상하지 않습니다. 그들이 요구하는 사양과 정확히 일치시키는 것을 매번 보상합니다.
중복 업로드는 플래그가 찍힐 가장 빠른 방법입니다
초기에는 이미 무엇을 올렸는지 잊어버려서 배치를 두 번 실행한 적이 있습니다. 그때는 아무 일도 일어나지 않았지만, 저는 기여자 포럼(contributor forum) 스레드를 충분히 읽었고, 반복적인 중복 제출은 계정 검토로 이어지는 실제 경로라는 것을 알게 되었습니다. 작은 계정으로는 그 위험을 감당할 수 없습니다.
upload_tracker.py는 작은 원장(ledger) 역할을 합니다. 항목을 완료로 표시하면, pending 재실행 시 해당 항목은 더 이상 나타나지 않습니다. 또한 표준 라이브러리만 사용합니다. 네 개의 스크립트 중 두 개는 pip 설치가 전혀 필요 없는데, 이는 서버 RAM이 2 GB에 불과하고 자정쯤 의존성 트리가 해결되는 것을 원치 않을 때 중요한 문제입니다.
2GB 환경은 좋은 습관을 강요한다
전체 파이프라인은 GPU 없이 vCPU 1개와 RAM 2 GB로 실행됩니다. 이는 제약처럼 들릴 수 있습니다. 하지만 실제로는 규율입니다. 이 시스템을 작동하게 만든 습관들은 다음과 같습니다:
- 한 번에 하나의 프로세스만 처리합니다. 단일 코어에서 이미지 작업을 병렬로 분산(fan out)하지 마십시오. vCPU 1개에서의 병렬 처리는 모든 것이 더 느리게 끝날 뿐이며 메모리 스파이크가 쌓입니다.
- 각 에셋 작업 후
gc.collect()를 호출합니다. Python은 예상보다 오래 참조를 유지하는 경향이 있으며, 2 GB 환경에서 400 MB와 900 MB의 워킹 세트(working set) 차이는 작동과 OOM(Out Of Memory) 종료 사이의 차이를 만듭니다.
- 평균 RSS가 아닌 최대 RSS를 측정합니다.
resource.getrusage(RUSAGE_SELF).ru_maxrss는 실제 상한선(ceiling)을 제공합니다. 평균 메모리는 오늘 밤 배치 작업이 살아남을지 여부에 대해 아무것도 알려주지 않습니다.
- 무거운 렌더링 단계는 파일당 서브프로세스로 실행하여, 항목 간에 메모리가 실제로 OS로 반환되도록 합니다.
- 필요한 해상도에서 렌더링합니다. 300 dpi 대신 60 dpi로 미리보기를 검사하는 것만으로도 한 단계의 메모리 사용량을 극적으로 줄일 수 있습니다. 아무도 보지 않을 픽셀을 렌더링하는 것이 작은 서버가 할 수 있는 가장 비싼 일입니다.
AI 이미지 생성을 위해 저는 이 과정을 서버 외부에서 완전히 처리합니다. 생성은 호스팅된 API를 통해 이미지당 몇 센트의 비용이 들며, 제 VPS는 오직 후처리(post-processing)만 담당합니다: 크기 조정(resizing), 압축(zipping), 표지(covers), PDF 변환. Pillow와 reportlab 라이브러리는 이 모든 것을 2 GB 환경에서 편안하게 처리합니다.
콘텐츠를 제품으로 전환하는 것은 또 다른 단계다
저는 실제 연구 자료로 가득 찬 마크다운 문서, 파이프라인 내에 규칙을 지키며 보관된 300페이지가 넘는 분량의 자료들을 가지고 있었지만, 이를 판매할 방법이 없었습니다. 워드 프로세서에 수동으로 복사하여 붙여넣는 방식으로는 제품 번호 두 개를 넘어 확장할 수 없습니다.
md2pdf.py는 Markdown 파일을 받아 제목, 표, 목록 및 링크가 포함된 깔끔하게 스타일링된 PDF를 단 하나의 명령어로 렌더링합니다. 이 스크립트는 전체 키트에서 유일한 두 개의 서드파티 패키지 중 하나인 reportlab을 사용합니다.
다음으로 pack_product.py는 제품을 번들로 만듭니다. 파일들을 zip으로 압축하고, Pillow를 사용하여 1280x720 크기의 커버와 600x600 크기의 썸네일을 렌더링합니다. 실제 커버가 있는 zip 파일이 판매됩니다. 아무것도 없는 파일은 그렇지 않습니다. 저는 직접 커버를 붙이기 시작하기 전과 후의 제 리스팅 번호들을 보면서 이 사실을 배웠습니다.
이 스크립트는 기본적으로 _system 폴더에 출력을 작성하며, --dir 또는 PACK_ROOT 환경 변수를 사용하여 이를 리디렉션할 수 있습니다. 스크립트를 수정하거나 하드코딩된 경로가 없습니다.
저를 구해준 폴더 구조
고정된 구조가 없는 카탈로그는 잡동사니 서랍이 됩니다. 전체 시스템이 가정하는 레이아웃은 다음과 같습니다:
project/
assets/ 원시 출력물, 에셋당 하나의 파일
output/ 제출 준비가 된 파일 및 metadata.csv
...
경험에서 얻은 경고 하나: research/ 폴더는 영원히 보관하세요. 저는 청소하기 위해 제 폴더를 한 번 삭제했다가, 새로운 제품에 여전히 참고하는 원자재들을 잃었습니다. 디스크 공간은 재조사보다 저렴합니다.
현재 모든 것이 존재하는 곳
위의 모든 것은 ai sticker factory playbook으로 패키징되어 있습니다. 이것은