【初心者向け】Snowflake のステージについて整理して実際に試してみる

記事タイトルとURLをコピーする

はじめに

データドリブンな人間を目指している香取です。
Snowflake を使っていると、データのアップロード時に「内部ステージ」「外部ステージ」という概念に出会うことがあります。
どちらがどう違って、どのように使い分けるのか、理解できていなかったので、簡単にステージについて整理してみました。

Snowflake のステージとは

ステージ (Stage) とは、Snowflake にデータをロードする前の一時的な保存場所のことです。 データベースに直接データを投入するのではなく、まずはステージという中間的な場所にデータを置いてから、COPY INTO <テーブル> コマンドで Snowflake のテーブルにロードします。

ステージには以下の 2 種類があります

  • 内部ステージ (Internal Stage) : Snowflake アカウント内で管理されるストレージ
  • 外部ステージ (External Stage) : Amazon S3 などの Snowflake アカウント外のクラウドストレージ

内部ステージ概要

内部ステージは、Snowflake アカウント内で管理されるストレージです。 主にローカルマシンから直接ファイルをアップロードしたい場合に使用します。

内部ステージは 3 種類あります。(公式ドキュメント)
基本的には自動で作成されるユーザーステージかテーブルステージを使い、複数ユーザーで共有する & 複数テーブルにデータをロードしたい場合は名前付きステージを作成して使用するようなイメージです。

ユーザーステージ

  • Snowflake ユーザーごとに自動で作成される専用ステージ
  • 単一のユーザーのみステージング可能
  • 複数のテーブルにデータをロードできる
  • Snowflake 管理ステージのため変更や削除はできない

テーブルステージ

  • Snowflake で作成されたテーブルごとに自動で作成されるステージ
  • 複数ユーザーがステージング可能
  • 単一のテーブルにのみデータをロードできる
  • Snowflake 管理ステージのため変更や削除はできない

名前付きステージ

  • ユーザーが任意で作成するステージ (CREATE STAGE コマンドを使用)
  • 複数ユーザーがステージング可能
  • 複数のテーブルにデータをロードできる
  • ステージに対する権限があれば作成、変更、削除が可能

外部ステージ概要

外部ステージは Amazon S3 などの外部クラウドストレージを指します。 Amazon S3 などの外部クラウドストレージから直接 Snowflake にデータをロードしたい場合や、複数の Snowflake アカウント間でデータを共有したい場合に使用します。

外部ステージに使用できるクラウドストレージ

外部ステージには以下のクラウドストレージが使用できます。(公式ドキュメント)

  • Amazon S3
  • Google Cloud Storage
  • Microsoft Azure Blob Storage

内部ステージを使ってデータをアップロードしてみる

実際に名前付きステージを使ってローカルの CSV ファイルをアップロードしてみます。

ここでは名前付きステージを使用していますが、ユーザーステージやテーブルステージでも同様の手順でデータをアップロードできます。(公式ドキュメント)

また、コマンドは SnowSQL を使用してローカル環境から実行しています。SnowSQL のインストール方法については 公式ドキュメント や下記の記事を参照してください。

blog.serverworks.co.jp

クリックすると展開します

step1. サンプルデータの準備

まず、以下のような sample_data.csv ファイルを用意します

id,name,age,city
1,田中太郎,25,東京
2,佐藤花子,30,大阪
3,鈴木次郎,28,名古屋

step2. データベースとテーブルの作成

事前に使用するデータベースとテーブルを作成しておきます。

$ <username>#COMPUTE_WH@(no database).(no schema)>CREATE DATABASE STAGE_TEST_DB;

+----------------------------------------------+                                
| status                                       |
|----------------------------------------------|
| Database STAGE_TEST_DB successfully created. |
+----------------------------------------------+
1 Row(s) produced. Time Elapsed: 0.172s
$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>USE DATABASE STAGE_TEST_DB;

+----------------------------------+                                            
| status                           |
|----------------------------------|
| Statement executed successfully. |
+----------------------------------+
1 Row(s) produced. Time Elapsed: 0.051s
$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>CREATE TABLE USERS_INTERNAL (
    ID INT,
    NAME STRING,
    AGE INT,
    CITY STRING
  );

+-----------------------------------+                                           
| status                            |
|-----------------------------------|
| Table USERS_INTERNAL successfully created. |
+-----------------------------------+
1 Row(s) produced. Time Elapsed: 0.214s

step3. 名前付きステージの作成

名前付きステージは自分で作成する必要があるため、CREATE STAGE コマンドでステージを作成します。

$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>CREATE STAGE INTERNAL_NAMED_STAGE_TEST
    FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = ',' SKIP_HEADER = 1);

+------------------------------------------------------------+                  
| status                                                     |
|------------------------------------------------------------|
| Stage area INTERNAL_NAMED_STAGE_TEST successfully created. |
+------------------------------------------------------------+
1 Row(s) produced. Time Elapsed: 0.119s

step4. 名前付きステージへのファイルアップロード (ステージング)

内部ステージにファイルをアップロードするには、PUT コマンドを使用します。(公式ドキュメント)

./sample_data.csv ファイルを Snowflake の名前付きステージにアップロードする例を示します。

$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>PUT file://./sample_data.csv @INTERNAL_NAMED_STAGE_TEST;

+-----------------+--------------------+-------------+-------------+--------------------+--------------------+----------+---------+
| source          | target             | source_size | target_size | source_compression | target_compression | status   | message |
|-----------------+--------------------+-------------+-------------+--------------------+--------------------+----------+---------|
| sample_data.csv | sample_data.csv.gz |          95 |         144 | NONE               | GZIP               | UPLOADED |         |
+-----------------+--------------------+-------------+-------------+--------------------+--------------------+----------+---------+
1 Row(s) produced. Time Elapsed: 0.389s

step5. ステージ内のファイル確認

アップロードしたファイルがステージに正しく保存されているか確認します。

$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>LIST @INTERNAL_NAMED_STAGE_TEST;

+----------------------------------------------+------+----------------------------------+------------------------------+
| name                                         | size | md5                              | last_modified                |
|----------------------------------------------+------+----------------------------------+------------------------------|
| internal_named_stage_test/sample_data.csv.gz |  144 | 562eef0761884b8bd1d09482dc60c932 | Sun, 6 Jul 2025 22:37:05 GMT |
+----------------------------------------------+------+----------------------------------+------------------------------+
1 Row(s) produced. Time Elapsed: 0.108s

step6. テーブルへのデータロード

COPY INTO <テーブル> コマンドを使用して、名前付きステージにアップロードしたファイルをテーブルにロードします。

-- ステージから USERS_INTERNAL テーブルにデータをロード
$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>COPY INTO USERS_INTERNAL
  FROM @INTERNAL_NAMED_STAGE_TEST/sample_data.csv.gz
  FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = ',' SKIP_HEADER = 1);

+----------------------------------------------+--------+-------------+-------------+-------------+-------------+-------------+------------------+-----------------------+-------------------------+
| file                                         | status | rows_parsed | rows_loaded | error_limit | errors_seen | first_error | first_error_line | first_error_character | first_error_column_name |
|----------------------------------------------+--------+-------------+-------------+-------------+-------------+-------------+------------------+-----------------------+-------------------------|
| internal_named_stage_test/sample_data.csv.gz | LOADED |           3 |           3 |           1 |           0 | NULL        |             NULL |                  NULL | NULL                    |
+----------------------------------------------+--------+-------------+-------------+-------------+-------------+-------------+------------------+-----------------------+-------------------------+
1 Row(s) produced. Time Elapsed: 0.758s
-- データが正しくロードされたか確認
$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>SELECT * FROM USERS_INTERNAL;

+----+----------+-----+--------+                                                
| ID | NAME     | AGE | CITY   |
|----+----------+-----+--------|
|  1 | 田中太郎 |  25 | 東京   |
|  2 | 佐藤花子 |  30 | 大阪   |
|  3 | 鈴木次郎 |  28 | 名古屋 |
+----+----------+-----+--------+

外部ステージを使ってデータをアップロードしてみる

Amazon S3 上の CSV ファイルを外部ステージを使って Snowflake にロードしてみます。

クリックすると展開します

step1. データの準備

「内部ステージを使ってデータをアップロードしてみる」の「1. サンプルデータの準備」で使用した CSV ファイルを S3 バケットにアップロードし、「2. データベースとテーブルの作成」と同様の手順で、データベースとテーブル (USERS_EXTERNAL) を作成しておきます。

step2. IAM ポリシーの作成

S3 バケットのある AWS アカウントで以下の IAM ポリシーを作成します。(公式ドキュメント)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect":"Allow",
            "Action":[
                "s3:PutObject",
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:DeleteObject",
                "s3:DeleteObjectVersion"
                ],
            "Resource": "arn:aws:s3:::<bucket>/<prefix>/*"
        },
        {
            "Effect":"Allow",
            "Action":[
                "s3:ListBucket",
                "s3:GetBucketLocation"
                ],
            "Resource":"arn:aws:s3:::<bucket>",
        }
    ]
}

step3. IAM ロールの作成

以下の条件で、作成した IAM ポリシーを適用する IAM ロールを作成します。(公式ドキュメント)

  • 信頼されたエンティティタイプ : AWS アカウント
    • AWS アカウント : 別の AWS アカウント
      • アカウント ID : 自分の AWS アカウント ID を一時的に入力します。後で、信頼関係を変更し、Snowflake へのアクセスを許可します。
    • オプション : 「外部 ID を要求する」にチェックをいれます
      • 外部 ID : 0000 などのダミー ID を入力します。後で、 IAM ロールの信頼関係を変更し、ストレージ統合の外部 ID を指定します。
  • 許可を追加
    • 先ほど作成した IAM ポリシーを選択します
  • ロール名を指定し、ロールを作成します

step4. ストレージ統合の作成

Snowflake で外部ステージを使用するために、CREATE STORAGE INTEGRATION コマンドでストレージ統合を作成します。

docs.snowflake.com

$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>CREATE STORAGE INTEGRATION S3_INTEGRATION
  TYPE = EXTERNAL_STAGE
  STORAGE_PROVIDER = 'S3'
  ENABLED = TRUE
  STORAGE_AWS_ROLE_ARN = '<作成した IAM ロールの ARN>'
  STORAGE_ALLOWED_LOCATIONS = ('<s3://your-bucket-name>/<path>/');

+--------------------------------------------------+                            
| status                                           |
|--------------------------------------------------|
| Integration S3_INTEGRATION successfully created. |
+--------------------------------------------------+
1 Row(s) produced. Time Elapsed: 0.229s

step5. Snowflake が使用する AWS の IAM ユーザーと外部 ID を取得する

DESC INTEGRATION を実行し、Snowflake が使用する AWS の IAM ユーザーと外部 ID を取得します。

$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>DESC INTEGRATION S3_INTEGRATION;

+---------------------------+---------------+--------------------------------------------------------------+------------------+
| property                  | property_type | property_value                                               | property_default |
|---------------------------+---------------+--------------------------------------------------------------+------------------|
~略~
| STORAGE_AWS_IAM_USER_ARN  | String        | arn:aws:iam::xxxxxxxxxxxx:user/xxxxxxxx-x                    |                  |
~略~
| STORAGE_AWS_EXTERNAL_ID   | String        | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX               |                  |
~略~

step6. IAM ロールの信頼ポリシーを更新する

先ほど取得した「Snowflake が使用する AWS の IAM ユーザーの ARN (STORAGE_AWS_IAM_USER_ARN)」と「外部 ID (STORAGE_AWS_EXTERNAL_ID)」を使って、IAM ロールの信頼ポリシーを更新します。(公式ドキュメント)
これにより、Snowflake がこの IAM ロールを使用して S3 バケットへアクセスできるようになります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "<STORAGE_AWS_IAM_USER_ARN の値を入力>"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "<STORAGE_AWS_EXTERNAL_ID の値を入力>"
                }
            }
        }
    ]
}

step7. 外部ステージの作成

CREATE STAGE コマンドを使用して、外部ステージを作成します。ここでは、「3. Snowflake でストレージ統合を作成する」で作成したストレージ統合を使用します。

$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>CREATE STAGE EXTERNAL_NAMED_STAGE_TEST
  STORAGE_INTEGRATION = S3_INTEGRATION
  URL = 's3://<your-bucket-name>/<path>/sample_data.csv'
  FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = ',' SKIP_HEADER = 1);

+------------------------------------------------------------+                  
| status                                                     |
|------------------------------------------------------------|
| Stage area EXTERNAL_NAMED_STAGE_TEST successfully created. |
+------------------------------------------------------------+
1 Row(s) produced. Time Elapsed: 0.742s

step8. 外部ステージ内のファイル確認

LIST コマンドを使用して、外部ステージ内のファイルを確認します。

$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>LIST @EXTERNAL_NAMED_STAGE_TEST;

+-----------------------------------------------------+------+----------------------------------+------------------------------+
| name                                                | size | md5                              | last_modified                |
|-----------------------------------------------------+------+----------------------------------+------------------------------|
| s3://any-bucket-dori/snowflake-test/sample_data.csv |   95 | 92e240d8ae4010bc981a04e81611fe89 | Sun, 6 Jul 2025 22:59:35 GMT |
+-----------------------------------------------------+------+----------------------------------+------------------------------+
1 Row(s) produced. Time Elapsed: 1.187s

step9. テーブルにデータをロードする

COPY INTO <テーブル> コマンドを使用して、外部ステージからデータをロードします。

-- ステージから USERS_EXTERNAL テーブルにデータをロード
$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>COPY INTO USERS_EXTERNAL
  FROM @EXTERNAL_NAMED_STAGE_TEST
  FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = ',' SKIP_HEADER = 1);

+-----------------------------------------------------+--------+-------------+-------------+-------------+-------------+-------------+------------------+-----------------------+-------------------------+
| file                                                | status | rows_parsed | rows_loaded | error_limit | errors_seen | first_error | first_error_line | first_error_character | first_error_column_name |
|-----------------------------------------------------+--------+-------------+-------------+-------------+-------------+-------------+------------------+-----------------------+-------------------------|
| s3://any-bucket-dori/snowflake-test/sample_data.csv | LOADED |           3 |           3 |           1 |           0 | NULL        |             NULL |                  NULL | NULL                    |
+-----------------------------------------------------+--------+-------------+-------------+-------------+-------------+-------------+------------------+-----------------------+-------------------------+
1 Row(s) produced. Time Elapsed: 1.840s
-- データが正しくロードされたか確認
$ <username>#COMPUTE_WH@STAGE_TEST_DB.PUBLIC>SELECT * FROM USERS_EXTERNAL;

+----+----------+-----+--------+                                                
| ID | NAME     | AGE | CITY   |
|----+----------+-----+--------|
|  1 | 田中太郎 |  25 | 東京   |
|  2 | 佐藤花子 |  30 | 大阪   |
|  3 | 鈴木次郎 |  28 | 名古屋 |
+----+----------+-----+--------+
3 Row(s) produced. Time Elapsed: 0.450s

おわりに

今回は Snowflake のステージについて、内部ステージと外部ステージの違いや使い方を整理してみました。

ポイントを再度まとめると

  • Snowflake にはデータをロードする前に一時的に保存するための「ステージ」という概念がある
  • ステージには「内部ステージ」と「外部ステージ」の 2 種類がある
  • 内部ステージは Snowflake アカウント内で管理されるストレージであり、ローカルマシンから直接ファイルをアップロードしたい場合に使用する
  • 内部ステージにはユーザーステージ、テーブルステージ、名前付きステージの 3 種類があり、用途に応じて使い分ける
  • 外部ステージは Amazon S3 などのクラウドストレージから直接 Snowflake にデータをロードしたい場合や、複数の Snowflake アカウント間でデータを共有したい場合に使用する

皆さんも実際に手を動かして、ステージの使い方をマスターしてみてください!

参考

香取 拓哉 (記事一覧)

2023年度新卒入社
データドリブンな人間を目指しています
好きな食べ物は芽ねぎ