SiS 외부 스테이지 파일 업로드: SP로 에러 뚫기
안녕하세요~ 포스 삼촌입니다.
요즘 PRANALINK와 나연스떼 2호점 준비가 본격화되면서 하루하루가 스펙터클합니다. 현장 중심의 우리 나연 대표는 늘 실행력이 앞서서 멋진 아이디어를 쏟아내지만, 그걸 전부 서포트하는 것은 여간 힘든게 아니네요. 더군다나 늘 비.용 효.율.적이어야 하거든요^^
어제 오후, PRANALINK 도쿄 지사 설립을 위한 데모를 앞두고 나연 대표가 사색이 되었습니다.

🚨 발등에 떨어진 불: 내부 스테이지(Internal Stage)에선 잘 되던게 왜?
Streamlit-in-Snowflake(SiS) 환경에서 st.file_uploader로 파일을 받아 스노우플레이크 스테이지에 업로드하는 작업, 다들 한번즘 해보셨을 거에요.
보통 Snowpakr의 Session API를 써어 아래처럼 활용하곤 합니다. (샘플코드가 독스에 잘 나와 있거든요^^)
# 일반적인 내부 스테이지 업로드 패턴
session.file.put_stream(
input_stream=uploaded_file,
stage_location="@MY_INTERNAL_STAGE/uploads/plan.pdf",
auto_compress=False,
overwrite=True
)
내부 스테이지(Internal Stage)로 Snowpark put\_stream으로 파일을 업로드할 때는 아주 잘 실행되요. 초기 코드는 다 이렇게 되어 있었어요.
그런데 문제는 조직의 엔터프라이즈 환경이었죠.
프라나링크의 스튜디오 도면 및 대용량의 비정형 문서들은 다른 어플리케이션 및 협력사들과의 활용을 위해 외부 스테이지(External Stage)를 이용해야 했거든요. 그래서 타깃 스테이지를 AWS S3를 이용하는 외부 스테이지로 바꿨을 뿐인데...
ProgrammingError: 091003 (22000): Failure using stage area.
Cause: [MY_EXTERNAL_STAGE GET and PUT commands are not supported with external stage]
이런 오류가 발생했어요. 이번 포스트에서는 원인을 추적하고, 내부/외부 스테이지 모두에서 동일하게 동작하는 해결책을 정리해보자구요.
🔍 원인 분석: put_stream의 치명적인 함정
나비와 함께 스노우플레이크 공식 문서를 살펴 보면서 여러 차례의 테스트를 거치고 나서야 이마를 탁 쳤어요.
- session.file.put_stream의 본질: 이 메서드는 백그라운드에서 스노우플레이크의 SQL 명령어인 PUT을 실행합니다.
- 스노우플레이크의 철칙: PUT 명령어는 내부 스테이지(Internal Stage)에만 지원됩니다. S3, GCS, Azure Blob 같은 외부 스테이지(External Stage)에는 애초에 직접 PUT을 날릴 수 없도록 막혀 있던 것이죠.
SiS 앱 내부에서 업로드하는 파일을 바로 외부 스테이지로 쏴주려다 보니 오류가 발생한 것이죠. Snowflake PUT 공식 문서에도 이렇게 명시되어 있더라구요.
PUT does not support uploading files onto an external stage. To upload files to an external stage, use the utilities provided by your cloud service.
💡 여러 차례의 헤딩 끝에 찾아낸 탈출구: Stored Procedure를 브릿지로 쓴다!
'아니 근데, 이러면 너무 불편하잖아?'라고 생각하면서 이것 저것 테스트를 해보았어요. 지금까지 경험한 Snowflake라면 Stage의 종류가 어떤 타입이건 간에 동일한 코드로 실행되는 방법이 있을 것 같았거든요. 외부 스테이지를 이용할 때는 직접 연동하는 SDK를 활용한다? 보안 거버넌스상 이건 영 찜찜했어요.
그러던 도중 SQL의 PUT 커맨드의 공식 문서에 작은 단서가 있었어요.
`snowflake.snowpark.FileOperation.put` from Snowpark stored procedures does support external stages and bills at normal data transfer rates.
stored procedures 란 조건이 붙어 있고, does 에는 굵게 강조도 되어 있었죠. 아래처럼요^^

CoCo와 많은 대화를 하면서 찾아낸 건, 위 문서의 내용을 바탕으로 put_stream을 SiS 런타임에서 바로 실행하는게 아니라 Stored Procedure 런타임으로 우회해서(감싸서? 포장해서?) 실행하면 어떨까? 라는 것이었고, 성공했어요~ ^^v
🎯 해결: 공용 Stored Procedure로 감싸기
업로드 로직을 SP로 한 번 감싸면, SiS에서 호출하더라도 SP 컨텍스트에서 실행되므로 외부 스테이지에 정상적으로 업로드되었어요.
CREATE OR REPLACE PROCEDURE PRANALINK_OPS.UTILS.SP_PUT_TO_STAGE(
STAGE_PATH STRING,
FILE_BYTES BINARY
)
RETURNS STRING
LANGUAGE PYTHON
RUNTIME_VERSION = '3.11'
PACKAGES = ('snowflake-snowpark-python')
HANDLER = 'run'
AS $$
import io
def run(session, stage_path, file_bytes):
session.file.put_stream(
io.BytesIO(file_bytes),
stage_path,
auto_compress=False,
overwrite=True
)
return 'OK'
$$;
여러 앱에서 사용될 것 같으니 OPS 레이어의 UTILS 스키마에 생성했지요.
그리고 Streamlit에서는
def try_put_stream(stage: str, file_bytes: bytes, filename: str):
stage_location = f"@{stage}/{filename}"
try:
session.call('PRANALINK_OPS.UTILS.SP_PUT_TO_STAGE', stage_location, file_bytes)
return True, None
except Exception as e:
return False, e
이 함수 하나로 내부 스테이지와 외부 스테이지 모두 동일하게 처리할 수 있었습니다.^^
⚠️ 알아두어야 할 제한사항
그런데 한가지 주의해야할 점이 있어요.
위 SP에서 사용한 BINARY 파라미터에요. SP의 BINARY 파라미터는 최대 64MB이고, st.file_uploader의 기본 업로드 제한이 200MB이므로, 64MB를 초과하는 파일은 이 방식으로 처리할 수 없어요. 실제 사용하는 앱에 안내 문구를 추가해야 할 수도 있고, 고용량의 파일을 많이 다루는 조직이라면, 일단 내부 스테이지로 업로드했다가 외부 스테이지로 move하는 등의 다른 프로세스를 고려해야 합니다.
일반적인 업무 파일(엑셀, CSV, 이미지 등)은 대부분 64MB 이내이므로 실용적으로는 충분히 사용할만 해서 PRANALINK의 기본 UTIL로 등록하긴 했어요~
🪐 마치며
데모 시작 3분 전, 마침내 나연 대표의 화면에 초록색 완료 메시지가 떴습니다. ^^ 핫핫.. 아슬아슬..
실무에서는 로컬 환경이나 내부 테스트에선 잘 돌던 코드(특히 요즘은 바이브코딩으로 AI가 생성한 코드들이 대부분^^;)가 엔터프라이즈 환경으로 넘어오는 순간 에러를 뿜어내며 멈춰 서곤 합니다.
그럴 때 당황해서 임기응변식 보안 구멍을 뚫는게 아니라 플랫폼에서 사용 가능한 대안을 찾아 확실한 실험을 해보고 우아한 우회로를 설계해 적용하는 것. 그것이 조직에서 일하는 데이터 엔지니어링이라 생각합니다.
(공식 문서에 이런 방법이 다 안내되어 있으면 얼마나 좋았을까? 라는 생각에 작성하게 된 포스트^^)
오늘도 안전하고 비용 효율적인 스노우플레이크 여정 되시길 바랍니다. 나마스떼, 아니 나연스떼!
테스트 환경
- Snowflake Edition, Cloud & Region: Ent., AWS `ap-northeast-2` (Seoul)
- External Stage: AWS `ap-northeast-2` (Seoul)
- Runtime: Snowflake Python 3.11 / Snowpark Python
- Note: 타 클라우드 프로바이더(Azure, GCP)나 타 리전에서는 세부 동작이 상이할 수 있슴
참고 자료
- [Snowflake PUT 문서] https://docs.snowflake.com/en/sql-reference/sql/put
- [Snowpark FileOperation.put_stream API] https://docs.snowflake.com/en/developer-guide/snowpark/reference/python/latest/snowpark/api/snowflake.snowpark.FileOperation.put_stream
- [File Upload and Download with Streamlit in Snowflake (DEV Community)] https://dev.to/tsubasa_tech/file-upload-and-download-with-streamlit-in-snowflake-1joi)
- [Snowflake BINARY 데이터 타입] https://docs.snowflake.com/en/sql-reference/data-types-text)