MinIO에 파일을 올리는 방법은 크게 세 가지다. mc CLI, Python SDK, presigned URL. 어떤 방법을 쓸지는 "누가 자격증명을 갖고 있는가"로 먼저 나뉜다. 서버나 스크립트가 직접 MinIO에 접근할 수 있으면 mc나 SDK를 쓴다. 브라우저나 모바일 클라이언트가 직접 올려야 한다면 presigned URL이 유일한 선택지다.
mc CLI로 바로 올리기
mc는 MinIO 공식 CLI 클라이언트다. 로컬 환경에서 빠르게 파일을 올리거나 shell 스크립트로 자동화하는 데 쓴다. 먼저 MinIO 서버에 대한 alias를 등록해야 한다.
mc alias set myminio https://minio.example.com ACCESS_KEY SECRET_KEY
이후 업로드는 mc cp로 한다.
# 단일 파일 업로드
mc cp /path/to/file.tar.gz myminio/mybucket/file.tar.gz
# 디렉토리 전체 동기화
mc mirror /local/data/ myminio/mybucket/
mc mirror는 로컬 디렉토리와 버킷을 동기화한다. 이미 있는 파일은 건너뛰고 변경된 파일만 올리므로 대량 파일을 정기적으로 올리는 운영 스크립트에 적합하다. --max-workers 플래그로 동시 전송 스레드 수를 조절할 수 있고, --limit-upload로 업로드 대역폭을 MiB/s 단위로 제한하는 것도 가능하다. 멀티파트는 내부적으로 자동 처리한다.
mc의 전제 조건은 명확하다. access key와 secret key가 실행 환경에 있어야 한다. CI/CD 파이프라인이나 백엔드 서버에서는 자연스럽지만, 브라우저에서 직접 올리는 시나리오는 이 방법으로 해결되지 않는다.
Python SDK(minio-py)로 업로드
애플리케이션 코드에서 MinIO에 올려야 할 때는 minio-py SDK를 쓴다. 핵심 메서드는 put_object와 fput_object 두 가지고, 차이는 입력 형태다.
fput_object는 파일 경로를 받는다.
from minio import Minio
client = Minio(
"minio.example.com",
access_key="ACCESS_KEY",
secret_key="SECRET_KEY",
secure=True,
)
client.fput_object(
bucket_name="mybucket",
object_name="uploads/photo.jpg",
file_path="/tmp/photo.jpg",
content_type="image/jpeg",
)
put_object는 파일 경로 대신 스트림과 크기를 받는다. 파일을 디스크에 먼저 저장하지 않고 메모리에서 바로 올릴 때 쓴다.
import io
data = b"hello, minio"
client.put_object(
bucket_name="mybucket",
object_name="notes/hello.txt",
data=io.BytesIO(data),
length=len(data),
content_type="text/plain",
)
length를 미리 알 수 없는 스트림이라면 -1을 넘기고 part_size를 명시하면 SDK가 알아서 multipart로 처리한다.
mc 대신 SDK를 써야 하는 경우는 파일 업로드가 애플리케이션 로직과 묶여 있을 때다. 업로드 전에 메타데이터를 DB에 기록하거나, 업로드 결과에 따라 다른 작업을 트리거하는 흐름이라면 SDK가 맞다. 단순히 파일을 옮기는 거라면 mc cp 한 줄이 훨씬 빠르다.
대용량 파일: multipart upload가 필요한 시점
minio-py 소스에 정의된 상수를 보면 구조가 명확하다.
MIN_PART_SIZE= 5 MB (5 × 1024 × 1024)MAX_PART_SIZE= 5 GB (5 × 1024 × 1024 × 1024)MAX_MULTIPART_COUNT= 10,000
fput_object나 put_object에 5 MB를 넘는 파일이 들어오면 SDK가 자동으로 multipart upload로 전환한다. 기본 part_size는 object_size / 10000으로 계산된 값과 MIN_PART_SIZE 중 큰 쪽으로 결정된다.
문제는 기본값이 작은 파일 기준으로 최적화되어 있다는 점이다. 1 GB 파일을 올릴 때 기본 part_size를 그대로 두면 파트 수가 수백 개가 되고 HTTP 왕복 오버헤드가 쌓인다. part_size를 직접 지정하면 이를 제어할 수 있다.
client.fput_object(
bucket_name="mybucket",
object_name="data/large.bin",
file_path="/data/large.bin",
part_size=16 * 1024 * 1024, # 16 MB per part
)
동일 네트워크 조건에서 1 GB 파일 기준으로 part_size별 파트 수와 체감 속도를 비교하면 다음과 같다.
| part_size | 파트 수 (1 GB 기준) | 특징 |
|---|---|---|
| 5 MB (기본 하한) | 약 204개 | 파트 수 많아 오버헤드 증가 |
| 16 MB | 약 64개 | 대부분의 환경에서 균형점 |
| 64 MB | 약 16개 | 고속 네트워크·대용량 파일에 유리 |
실측 환경에 따라 다르지만 16 MB part와 5 MB part 사이에 20~40% 차이가 나는 경우가 많다. 여러 파일을 동시에 올린다면 part_size 튜닝보다 ThreadPoolExecutor로 병렬화하는 게 효과가 더 크다.
put_object에 대용량 스트림을 넘기면서 length를 모른다면 업로드 도중 에러가 날 수 있다. 이 경우에는 length=-1과 part_size 명시를 함께 쓰거나, 파일 경로를 알고 있다면 fput_object를 쓰는 게 안전하다.
presigned URL — 서버 자격증명 없이 클라이언트가 직접 올릴 때
브라우저나 모바일 앱에서 파일을 올리는 흐름에서 서버가 파일을 중계하면 서버 대역폭을 두 배 쓴다. 파일이 클라이언트 → 서버 → MinIO 경로를 거치기 때문이다. presigned URL은 이 문제를 구조적으로 해결한다. 서버가 서명된 일회성 URL을 발급하면, 클라이언트가 그 URL로 MinIO에 직접 PUT 요청을 보낸다.
서버 측 URL 발급 코드:
from datetime import timedelta
url = client.presigned_put_object(
bucket_name="mybucket",
object_name="uploads/user-photo.jpg",
expires=timedelta(hours=1),
)
# https://minio.example.com/mybucket/uploads/user-photo.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&...
이 URL을 클라이언트에 내려주면, 클라이언트는 별도의 자격증명 없이 직접 업로드한다.
curl -X PUT \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg \
"https://minio.example.com/mybucket/uploads/user-photo.jpg?X-Amz-Algorithm=..."
URL에는 대상 버킷, 오브젝트 경로, 만료 시각, 서명이 모두 포함되어 있다. 만료 전에 URL이 유출되면 그 경로에 임의 파일을 올릴 수 있다. 민감한 경로라면 만료 시간을 5~15분으로 짧게 잡는 게 낫다. 오브젝트 경로를 UUID나 랜덤 문자열로 구성하면 URL이 탈취되더라도 피해 범위가 해당 파일 하나로 제한된다. 업로드 완료 후 서버가 해당 오브젝트 존재 여부를 확인하는 흐름을 추가하면 미완료 업로드도 감지할 수 있다.
방법 선택 기준
| 상황 | 추천 방법 |
|---|---|
| 로컬/서버에서 단발성 업로드, 운영 스크립트 | mc cp |
| 디렉토리 동기화, 배치 | mc mirror |
| 애플리케이션 코드에서 파일 경로로 업로드 | fput_object |
| 메모리 스트림·동적 데이터 업로드 | put_object |
| 브라우저/모바일이 직접 올리는 경우 | presigned PUT URL |
| 100 MB 이상 대용량 파일 | fput_object + part_size 조정 |
세 방법은 서로 배타적이지 않다. 운영 스크립트는 mc를 쓰고, 동일 서비스의 애플리케이션 레이어는 SDK를 쓰고, 사용자 업로드는 presigned URL을 쓰는 구성이 실무에서 가장 자연스럽다. part_size를 기본값에서 16~64 MB로 올리는 것만으로 대용량 업로드 속도가 눈에 띄게 달라지므로, 1 GB 이상 파일을 자주 올린다면 한번쯤 직접 측정해볼 만하다.