COPY INTO 와 스테이지 (대량 적재, 파일 포맷, 언로드)
내부와 외부 스테이지, 스토리지 통합, 파일 포맷, COPY INTO 의 오류 처리와 중복 방지 기록, 변환 적재, 파일 크기 권장, 테이블에서 파일로 내보내는 언로드까지 대량 적재의 기본을 정리합니다.
스테이지 종류
| 스테이지 | 위치 | 표기 | 쓰임 |
|---|---|---|---|
| 사용자 스테이지 | Snowflake 내부, 사용자마다 | @~ | 개인 파일 |
| 테이블 스테이지 | Snowflake 내부, 테이블마다 | @%orders | 한 테이블에만 넣을 파일 |
| 이름 있는 내부 스테이지 | Snowflake 내부 | @raw.int_stage | 팀이 공유하는 업로드 장소 |
| 외부 스테이지 | S3, GCS, Azure Blob | @raw.s3_stage | 데이터 레이크 파일 적재 |
내부 스테이지는 PUT 명령(SnowSQL, Snowflake CLI, 드라이버)으로 파일을 올리고, 외부 스테이지는 클라우드 버킷을 가리킵니다. 2026년 3월에는 내부 스테이지에 대한 공개 접근을 막으면서 IP 허용 목록 예외를 두는 기능이 GA 가 되었습니다.
스토리지 통합으로 외부 스테이지 만들기
비밀 키를 스테이지에 직접 넣지 말고 스토리지 통합(Storage Integration) 으로 클라우드 IAM 역할을 신뢰하게 만듭니다.
CREATE STORAGE INTEGRATION s3_int
TYPE = EXTERNAL_STAGE
STORAGE_PROVIDER = 'S3'
ENABLED = TRUE
STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::123456789012:role/snowflake-load'
STORAGE_ALLOWED_LOCATIONS = ('s3://my-lake/raw/');
DESC INTEGRATION s3_int; -- STORAGE_AWS_IAM_USER_ARN, STORAGE_AWS_EXTERNAL_ID 를 AWS 신뢰 정책에
CREATE STAGE raw.s3_stage
URL = 's3://my-lake/raw/'
STORAGE_INTEGRATION = s3_int
FILE_FORMAT = raw.csv_fmt;
파일 포맷
CREATE FILE FORMAT raw.csv_fmt
TYPE = CSV
FIELD_DELIMITER = ','
PARSE_HEADER = TRUE -- 헤더로 열 이름 매칭 (MATCH_BY_COLUMN_NAME 과 함께)
FIELD_OPTIONALLY_ENCLOSED_BY = '"'
NULL_IF = ('', 'NULL')
EMPTY_FIELD_AS_NULL = TRUE
ENCODING = 'UTF8';
CREATE FILE FORMAT raw.parquet_fmt TYPE = PARQUET;
CREATE FILE FORMAT raw.json_fmt TYPE = JSON STRIP_OUTER_ARRAY = TRUE;
COPY INTO 기본
COPY INTO raw.orders
FROM @raw.s3_stage/orders/2026/10/
FILE_FORMAT = (FORMAT_NAME = 'raw.csv_fmt')
MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE
ON_ERROR = 'ABORT_STATEMENT'
PATTERN = '.*[.]csv[.]gz';
-- 실제로 넣지 않고 검사만
COPY INTO raw.orders FROM @raw.s3_stage/orders/ VALIDATION_MODE = 'RETURN_ERRORS';ON_ERROR 선택
| 값 | 동작 | 맞는 경우 |
|---|---|---|
ABORT_STATEMENT (COPY 기본) | 오류 하나면 전체 취소 | 정합성이 중요한 원장 |
CONTINUE | 오류 행만 건너뜀 | 로그, 이벤트 |
SKIP_FILE | 오류가 있는 파일 통째로 건너뜀 (Snowpipe 기본) | 파일 단위 재처리 |
SKIP_FILE_n, SKIP_FILE_n% | 오류가 n 개 또는 n% 넘으면 파일 건너뜀 | 약간의 오류는 허용 |
건너뛴 행은 VALIDATE(raw.orders, JOB_ID => '_last') 로 다시 볼 수 있습니다.
중복 적재 방지 (로드 메타데이터)
COPY 는 테이블마다 64일 동안 어떤 파일을 넣었는지 기억해 같은 파일을 다시 넣지 않습니다. 파일 이름과 ETag(체크섬)이 기준입니다.
- 같은 이름으로 내용을 바꿔 다시 올리면 ETag 가 달라져 다시 적재됩니다.
- 64일이 지나 기록이 없어진 파일은 상태를 알 수 없어 기본적으로 건너뜁니다.
LOAD_UNCERTAIN_FILES = TRUE로 넣거나FORCE = TRUE로 무조건 다시 넣을 수 있습니다.
SELECT file_name, status, row_count, row_parsed, first_error_message, last_load_time
FROM snowflake.account_usage.copy_history
WHERE table_name = 'ORDERS' AND last_load_time > DATEADD(day, -1, CURRENT_TIMESTAMP())
ORDER BY last_load_time DESC;변환하면서 적재
COPY INTO raw.orders (id, ordered_at, amount, src_file)
FROM (
SELECT $1::NUMBER, TO_TIMESTAMP_NTZ($2), $3::NUMBER(12,2), METADATA$FILENAME
FROM @raw.s3_stage/orders/
)
FILE_FORMAT = (FORMAT_NAME = 'raw.csv_fmt');열 순서 바꾸기, 형 변환, 일부 열만, 파일 이름 같은 메타데이터 열 추가 정도는 COPY 안에서 합니다. 조인이나 집계 같은 무거운 변환은 적재 후 Dynamic Tables 에서 합니다.
파일 크기와 웨어하우스
- 권장 파일 크기는 압축 후 100~250 MB 입니다. 수 KB 파일 수십만 개는 파일마다 드는 오버헤드 때문에 느리고, 수십 GB 파일 하나는 병렬로 나뉘지 않습니다.
- COPY 는 파일 단위로 병렬 처리됩니다. 웨어하우스를 키우면 동시에 처리하는 파일 수가 늘어나므로 파일이 충분히 많을 때만 큰 웨어하우스가 효과가 있습니다.
- 한 번에 넣을 파일이 많지 않다면 XS 나 S 로 충분하고, 수시로 들어오는 파일은 Snowpipe 가 더 쌉니다.
언로드 (테이블에서 파일로)
COPY INTO @raw.s3_stage/export/orders_
FROM (SELECT * FROM mart.orders WHERE d = '2026-10-20')
FILE_FORMAT = (TYPE = PARQUET)
HEADER = TRUE
MAX_FILE_SIZE = 268435456 -- 약 256 MB
OVERWRITE = TRUE;PARTITION BY 로 경로를 나눠 쓸 수 있습니다. 외부 시스템이 Snowflake 데이터를 계속 읽어야 한다면 파일 언로드보다 Iceberg 테이블 이나 데이터 공유 가 낫습니다.
트러블슈팅
증상: COPY 가 성공했다고 하는데 0 행이 들어갑니다.
원인: 이미 64일 안에 넣은 파일이라 건너뛰었거나,
PATTERN이 파일 경로와 맞지 않습니다.해결: 결과의
status가LOAD_SKIPPED인지 보고,LIST @stage/path로 실제 경로와 패턴을 맞춥니다. 일부러 다시 넣을 때만FORCE = TRUE를 씁니다.증상: "Access Denied" 또는 "is not authorized to perform sts:AssumeRole" 가 납니다.
원인: AWS IAM 역할의 신뢰 정책에 Snowflake IAM 사용자 ARN 이나 외부 ID 가 빠졌거나
STORAGE_ALLOWED_LOCATIONS밖의 경로입니다.해결:
DESC INTEGRATION값을 그대로 신뢰 정책에 넣고 경로를 확인합니다. 더 많은 오류는 적재 오류 트러블슈팅 에 있습니다.