지난 3월 어느 저녁에 저는 배치 작업을 두 번 실행했습니다. 의도한 것은 아니었습니다. 첫 번째 실행은 멈춘 것처럼 보여서 제가 그것을 종료하고, 버그라고 생각되는 부분을 수정하여 다시 실행했습니다. 그런데 실제로 멈춘 것이 아니었습니다. 그 결과는 세 개의 기여자 대기열(contributor queues)에 걸쳐 60개의 중복 제출물이 쌓여 있는 상황이었습니다. 그리고 이 플랫폼들 중 한 곳에서 중복 배치 파일은 리뷰어들이 당신의 계정 전체를 더 주의 깊게 살펴보게 만드는 바로 그런 종류의 문제입니다.
그날 밤 저는 upload_tracker.py라는 스크립트를 작성했고, 그 이후로 모든 배치 작업에 포함되었습니다. 여기에는 중복 업로드(double-posting)가 발생하는 이유와 실제로 이를 방지하는 방법이 무엇인지에 대한 제가 배운 내용들이 담겨 있습니다.
플랫폼은 당신을 구해주지 못한다
거대 스톡 플랫폼 세 곳 모두 업로드 시 중복 제거(deduplicate) 기능을 제공하지 않습니다. Adobe Stock은 다섯 개의 열로 된 CSV 헤더(Filename, Title, Keywords, Category, Releases)를 제공하며, 이 헤더가 잘못되면 즉시 파일을 거부하지만, 같은 이름의 JPG 파일을 두 번 올리는 것은 아무 문제 없이 받아들입니다. Vecteezy는 네 개의 열로 된 헤더를 사용하며 똑같이 작동합니다. Dreamstime은 15개의 열을 요구하며 메타데이터 형식에 가장 엄격하지만, CSV에 대한 엄격함이 업로드 기록에 대한 확인과는 다릅니다.
따라서 중복 제거의 책임은 전적으로 사용자에게 있습니다. 저는 이 점에 놀랐습니다. 해시(hash) 체크나 최소한 경고가 있을 것이라고 생각했습니다. 하지만 그렇지 않습니다. 같은 파일을 두 번 게시하면, 그냥 통과되어 검토 대기열에 머물면서 실제 제출물을 사용할 수 있었던 검토 슬롯을 낭비하게 만듭니다.
원장(Ledger)이 기억력보다 낫다
해결책은 놀라울 정도로 간단합니다. 하나의 평면 텍스트 파일로, 에셋 하나당 한 줄씩 기록하며, 해당 에셋의 업로드가 실제로 완료되었을 때만 추가하는 것입니다. 이 도구는 세 가지 명령어를 가지고 있습니다:
python upload_tracker.py pending items.txt # 아직 완료되지 않은 항목 확인
python upload_tracker.py done
핵심 설계 결정은 `done`이 별도의 수동 단계라는 것입니다. 배치 스크립트는 항목을 큐에 넣는다고 해서 자동으로 완료로 표시하지 않습니다. 왜냐하면 큐에 넣는 것과 업로드하는 것은 다르기 때문입니다. 저도 이 점을 힘들게 배웠습니다. 초기 버전에서는 CSV가 생성될 때 자산을 완료된 것으로 표시했는데, 이는 업로드 도중 충돌이 발생하면 원장(ledger)이 저에게 거짓말을 하게 만들었습니다. 이제 원장은 사람이 확인한 내용만 기록합니다.
추가 전용(append-only)의 일반 텍스트 파일이기 때문에 `grep`으로 읽거나, 기계 간에 diff를 하거나, 어떤 백업에서도 복구할 수 있습니다. 데이터베이스도 없고, 의존성도 없습니다. `upload_tracker.py`와 `make_metadata.py` 모두 Python 표준 라이브러리만으로 실행됩니다. 심지어 가상 환경(virtualenv)이 필요하지 않습니다.
## 동일한 원장이 비용이 들기 전에 CSV를 확인합니다
문제의 나머지 절반은 재실행 시 메타데이터 파일이 새로운 것처럼 보이는 것과 완전히 동일하게 생성된다는 것입니다. 따라서 `make_metadata.py`가 Adobe(5개 열), Vecteezy(4개 열) 또는 Dreamstime(15개 열)에 대한 행을 작성하기 전에, 파일 이름을 원장과 교차 확인하는 것은 매우 쉽습니다. 이미 완료로 표시된 파일 이름은 조용히 재작성되는 것이 아니라 건너뜁니다. 이 단일 확인 덕분에 두 번째 실행으로 인한 재앙이 반복되는 것을 막을 수 있었습니다.
헤더 규칙도 여기서 중요합니다. 세 플랫폼 모두 업로드 시 잘못된 CSV 헤더를 즉시 거부합니다. 스크립트는 각 플랫폼이 요구하는 정확한 헤더만 작성하고 그 외에는 아무것도 작성하지 않습니다. 40개 파일 업로드가 실패한 후에 나쁜 헤더에 대해 알게 되는 일이 지겨워졌기 때문입니다.
## 작은 박스에서의 배치 위생 관리
제 전체 파이프라인은 GPU 없이 vCPU 1개와 RAM 2GB에서 실행되므로, 원장 역시 저렴해야 합니다. 그것은 바로 텍스트 파일에 한 줄을 추가하는 것이 아무 비용도 들지 않기 때문입니다. 나머지 배치를 그 박스에서 안전하게 유지하는 습관들이 여기에도 적용됩니다:
- 한 번에 하나의 프로세스만 처리합니다. 단일 코어에서 이미지 작업을 병렬로 분산(fan out)하지 마십시오.
- 각 에셋 처리 후 `gc.collect()`를 호출하여 항목 간 메모리를 해제합니다.
- 평균값이 아닌, 최대 RSS(`resource.getrusage(RUSAGE_SELF).ru_maxrss`)로 최고 피크 값을 측정해야 합니다. 평균값은 실행을 중단시키는 급증(spike)을 숨기기 때문입니다.
- 메모리가 실제로 OS로 반환되도록 무거운 렌더링 단계에서는 서브프로세스당 하나의 파일을 사용합니다.
배치 작업이 충돌하는 것이 애초에 대부분의 중복 게시를 유발하므로, 안정성과 중복 제거는 같은 문제입니다. 만약 실행을 성공적으로 마친다면, 다시 실행할 필요가 없습니다.
## 실제 적용 사례
원장(ledger)을 구현한 이후로 저는 세 플랫폼에서 39개의 완성된 제품, 약 856MB의 결과물을 처리했으며 중복 제출은 단 한 건도 없었습니다. 전체 상위 프로젝트는 약 19,000줄의 파이프라인 코드로 구성되어 있고, 원장은 그중 80줄 정도입니다. 이 80줄이 제가 가장 먼저 살릴 코드입니다.
현재 워크플로우는 다음과 같습니다: 에셋 생성 -> `make_metadata.py`를 사용하여 플랫폼별 CSV 구축 -> 모든 업로드 세션 전에 `pending` 상태 확인 -> 각 확인된 업로드 후 `done`으로 표시 -> 세션 끝에서 `status` 실행하여 카운트가 일치하는지 확인합니다. 30분의 장부 정리 시간이, 원하지 않았던 중복 삭제와 계정 검토로 보내야 할 오후 시간보다 훨씬 적습니다.
같은 설정을 원한다면, 네 가지 스크립트(메타데이터 빌더, 업로드 원장, Markdown to PDF 변환기, 1280x720 커버 및 600x600 썸네일을 렌더링하는 제품 패키저)가 'Pipeline Starter Kit'으로 번들되어 있습니다. 이 키트는 저의 벡터 프롬프트 팩과 같은 작업에서 파생되었습니다. 원장과 메타데이터 빌더는 Python 3.9 이상만 있으면 됩니다.
[벡터 프롬프트 팩 받기](https://revanzart-shop.revanzart.workers.dev/go/vector-prompt-pack?src=devto)
_디지털 제품을 제작하시나요? [디지털 제품 판매자를 위한 무료 가이드 및 키트](https://revanzart-shop.revanzart.workers.dev/guides)._
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