アクセス履歴

このトピックでは、Snowflakeのユーザーアクセス履歴に関する概念を説明します。

このトピックの内容:

概要

Snowflakeのアクセス履歴とは、ユーザークエリが列データを読み取り、 SQL ステートメントが INSERT、 UPDATE、 DELETE などのデータ書き込み操作を、ソースデータオブジェクトからターゲットデータオブジェクトに COPY コマンドのバリエーションとともに実行するときを指します。ユーザーアクセス履歴は、Account Usage ACCESS_HISTORY ビューをクエリすることで見つけられます。

ACCESS_HISTORY ビューの各行には、 SQL ステートメントごとに単一の記録が含まれます。この記録は、クエリが直接および間接的にアクセスした列(つまり、クエリのデータの基になるテーブル)を説明します。これらの記録は、法令順守の監査を容易にし、ユーザー(つまり、クエリ演算子)、クエリ、テーブルまたはビュー、列、およびデータの間に直接リンクがあるため、一般的で頻繁にアクセスされるテーブルと列に関する洞察を提供します。

読み取りおよび書き込み操作の追跡

ACCESS_HISTORY ビューには、次の列が含まれています。

QUERY_ID | QUERY_START_TIME | USER_NAME | DIRECT_OBJECTS_ACCESSED | BASE_OBJECTS_ACCESSED | OBJECTS_MODIFIED

読み取り操作は最初の5列で追跡され、最後の列 OBJECTS_MODIFIED は、Snowflakeの列、テーブル、およびステージに関連するデータ書き込み情報を指定します。

Snowflakeのクエリとデータベースオブジェクトの作成方法によって、Snowflakeが BASE_OBJECTS_ACCESSED 列、 OBJECTS_MODIFIED 列、 および DIRECT_OBJECTS_ACCESSED 列に対して返す情報が決まります。

列の詳細については、 ACCESS_HISTORY ビューの セクションをご参照ください。

読む

読み取りクエリと、 ACCESS_HISTORY ビューがこの情報を記録する方法を理解するために、次のシナリオを検討します。

  • 一連のオブジェクト: base_table » view_1 » view_2 » view_3

  • 次のような view_2 の読み取りクエリ:

    select * from view_2;
    

この例では、Snowflakeは次を返します。

  • クエリが view_2 を指定するため、 DIRECT_OBJECTS_ACCESSED として view_2

  • BASE_OBJECTS_ACCESSED として base_table。これは、 view_2 にあるデータの元ソースであるためです。

view_1view_3 は、 DIRECT_OBJECTS_ACCESSEDBASE_OBJECTS_ACCESSED に含まれません。これらのビューはどちらもクエリに含まれておらず、 view_2 のデータのソースとして機能するベースオブジェクトではないためです。

書き込み

書き込み操作と、 ACCESS_HISTORY ビューがこの情報を記録する方法を理解するために、次のシナリオを検討します。

  • データソース: base_table

  • データソース(つまり、 CTAS)からテーブルを作成します。

    create table table_1 as select * from base_table;
    

この例では、Snowflakeは次を返します。

  • テーブルは直接アクセスされ、データのソースであるため、 BASE_OBJECTS_ACCESSED 列と DIRECT_OBJECTS_ACCESSED 列内の base_table

  • テーブルの作成時に書き込まれた列を含む OBJECTS_MODIFIED 内の table_1

サポートされている操作

ACCESS_HISTORY ビューがサポートする読み取りおよび書き込み操作の詳細については、 ACCESS_HISTORY ビューの 使用上の注意 セクションをご参照ください。

利点

Snowflakeのアクセス履歴には、次の利点があります。

データ検出

未使用のデータを検出して、データをアーカイブするか削除するかを決定します。

機密データの移動状況の追跡

外部クラウドストレージの場所(例: Amazon S3バケット)からターゲットのSnowflakeテーブルへの、またはその逆のデータ移動を追跡します。

あるSnowflakeテーブルから異なるSnowflakeテーブルへの内部データの移動を追跡します。

機密データの移動をトレースした後、ポリシー(マスキング および 行アクセス)を適用してデータを保護し、 アクセス制御設定 を更新して、ステージへのアクセスをさらに規制します。テーブル、および タグ を設定して、機密データを含むステージ、テーブル、および列のコンプライアンス要件について追跡できるようにします。

データ検証

レポート、ダッシュボード、およびチャートやグラフなどのデータ可視化製品の正確性と整合性は、データを元のソースまで追跡できるため、検証されます。

データのスチュワードは、特定のテーブルまたはビューをドロップまたは変更する前に、ユーザーに通知することもできます。

コンプライアンス監査

GDPRCCPA などのコンプライアンス規制を満たすために、テーブルまたはステージで書き込み操作を実行したSnowflakeユーザーを特定し、書き込み操作がいつ発生したかを特定します。

全体的なデータガバナンスの強化

ACCESS_HISTORY ビューは、アクセスされたデータ、データアクセスがいつ行われたか、およびアクセスされたデータがデータソースオブジェクトからデータターゲットオブジェクトにどのように移動したかを統一的に示します。

ACCESS_HISTORY ビューのクエリ

次のサブセクションでは、 ACCESS_HISTORY ビューのクエリ例を示します。

一部のサンプルクエリは、クエリのパフォーマンスを向上させるために QUERY_START_TIME 列でフィルターすることに注意してください。パフォーマンスを向上させるもう1つのオプションは、より狭い時間範囲でクエリを実行することです。

例: クエリの読み取り

以下のサブセクションでは、次のユースケースの読み取り操作で ACCESS_HISTORY ビューをクエリする方法について詳しく説明します。

  • 特定のユーザーのアクセス履歴を取得します。

  • 次の質問に答えるために、 object_id (例: テーブルID)に基づいて、過去30日間の機密データアクセスのコンプライアンス監査を促進します。

    • 誰がデータにアクセスしたか。

    • いつデータにアクセスされたか。

    • どの列にアクセスされたか。

ユーザーアクセス履歴を返す

最新のアクセスから開始して、ユーザーとクエリの開始時刻の順にユーザーアクセス履歴を返します。

SELECT user_name
       , query_id
       , query_start_time
       , direct_objects_accessed
       , base_objects_accessed
FROM access_history
ORDER BY 1, 3 desc
;

コンプライアンス監査を促進する

次の例は、コンプライアンス監査を容易にするために役立ちます。

  • object_id 値を追加して、過去30日間に機密テーブルにアクセスしたユーザーを特定します。

    SELECT distinct user_name
    FROM access_history
         , lateral flatten(base_objects_accessed) f1
    WHERE f1.value:"objectId"::int=<fill_in_object_id>
    AND f1.value:"objectDomain"::string='Table'
    AND query_start_time >= dateadd('day', -30, current_timestamp())
    ;
    
  • 32998411400350object_id 値を使用して、過去30日間にアクセスがあった時期を特定します。

    SELECT query_id
           , query_start_time
    FROM access_history
         , lateral flatten(base_objects_accessed) f1
    WHERE f1.value:"objectId"::int=32998411400350
    AND f1.value:"objectDomain"::string='Table'
    AND query_start_time >= dateadd('day', -30, current_timestamp())
    ;
    
  • 32998411400350object_id 値を使用して、過去30日間にアクセスされた列を特定します。

    SELECT distinct f4.value AS column_name
    FROM access_history
         , lateral flatten(base_objects_accessed) f1
         , lateral flatten(f1.value) f2
         , lateral flatten(f2.value) f3
         , lateral flatten(f3.value) f4
    WHERE f1.value:"objectId"::int=32998411400350
    AND f1.value:"objectDomain"::string='Table'
    AND f4.key='columnName'
    ;
    

例: 書き込み操作

以下のサブセクションでは、次のユースケースの書き込み操作で ACCESS_HISTORY ビューをクエリする方法について詳しく説明します。

  • ステージからテーブルにデータをロードします。

  • テーブルからステージにデータをアンロードします。

  • PUT コマンドを使用して、ローカルファイルをステージにアップロードします。

  • GET コマンドを使用して、ステージからローカルディレクトリにデータファイルを取得します。

  • 機密性の高いステージデータの動きを追跡します。

ステージからテーブルにデータをロードする

外部クラウドストレージのデータファイルからターゲットテーブルの列に値のセットをロードします。

copy into table1(col1, col2)
from (select t.$1, t.$2 from @mystage1/data1.csv.gz);

DIRECT_OBJECTS_ACCESSED 列と BASE_OBJECTS_ACCESSED 列は、外部の名前付きステージにアクセスしたことを指定します。

{
  "objectDomain": STAGE
  "objectName": "mystage1",
  "objectId": 1,
  "stageKind": "External Named"
}

OBJECTS_MODIFIED 列は、データがテーブルの2つの列に書き込まれたことを指定します。

{
  "columns": [
     {
       "columnName": "col1",
       "columnId": 1
     },
     {
       "columnName": "col2",
       "columnId": 2
     }
  ],
  "objectId": 1,
  "objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
  "objectDomain": TABLE
}

テーブルからステージにデータをアンロードする

値のセットをSnowflakeテーブルからクラウドストレージにアンロードします。

copy into @mystage1/data1.csv
from table1;

DIRECT_OBJECTS_ACCESSED 列と BASE_OBJECTS_ACCESSED 列は、アクセスされたテーブル列を指定します。

{
  "objectDomain": TABLE
  "objectName": "TEST_DB.TEST_SCHEMA.TABLE1",
  "objectId": 123,
  "columns": [
     {
       "columnName": "col1",
       "columnId": 1
     },
     {
       "columnName": "col2",
       "columnId": 2
     }
  ]
}

OBJECTS_MODIFIED 列は、アクセスされたデータが書き込まれたステージを指定します。

{
  "objectId": 1,
  "objectName": "mystage1",
  "objectDomain": STAGE,
  "stageKind": "External Named"
}

PUT コマンドを使用して、ローカルファイルをステージにアップロードする

データファイルを内部(つまり、Snowflake)ステージにコピーします。

put file:///tmp/data/mydata.csv @my_int_stage;

DIRECT_OBJECTS_ACCESSED 列と BASE_OBJECTS_ACCESSED 列は、アクセスされたファイルへのローカルパスを指定します。

{
  "location": "file:///tmp/data/mydata.csv"
}

OBJECTS_MODIFIED 列は、アクセスされたデータが書き込まれたステージを指定します。

{
  "objectId": 1,
  "objectName": "my_int_stage",
  "objectDomain": STAGE,
  "stageKind": "Internal Named"
}

GET コマンドを使用して、ステージからローカルディレクトリにデータファイルを取得する

内部ステージからローカルマシン上のディレクトリにデータファイルを取得します。

get @%mytable file:///tmp/data/;

DIRECT_OBJECTS_ACCESSED 列と BASE_OBJECTS_ACCESSED 列は、アクセスされたステージとローカルディレクトリを指定します。

{
  "objectDomain": Stage
  "objectName": "mytable",
  "objectId": 1,
  "stageKind": "Table"
}

OBJECTS_MODIFIED 列は、アクセスされたデータが書き込まれたディレクトリを指定します。

{
  "location": "file:///tmp/data/"
}

機密性の高いステージデータの動きの追跡

時系列で実行される一連のクエリを移動するときに、機密性の高いステージデータを追跡します。

次のコマンドを実行します。5つのステートメントがステージデータにアクセスすることに注意してください。したがって、ステージアクセスの ACCESS_HISTORY ビューをクエリする場合、結果セットには5行が含まれている必要があります。

use test_db.test_schema;
create or replace table T1(content variant);
insert into T1(content) select parse_json('{"name": "A", "id":1}');

-- T1 -> T6
insert into T6 select * from T1;

-- S1 -> T1
copy into T1 from @S1;

-- T1 -> T2
create table T2 as select content:"name" as name, content:"id" as id from T1;

-- T1 -> S2
copy into @S2 from T1;

-- S1 -> T3
create or replace table T3(customer_info variant);
copy into T3 from @S1;

-- T1 -> T4
create or replace table T4(name string, id string, address string);
insert into T4(name, id) select content:"name", content:"id" from T1;

-- T6 -> T7
create table T7 as select * from T6;

条件:

  • T1T2 ... T7 は、テーブルの名前を指定します。

  • S1 および S2 は、ステージの名前を指定します。

アクセス履歴をクエリし、ステージ S1 へのアクセスを決定します。

次のテーブルに、 DIRECT_OBJECTS_ACCESSED、 BASE_OBJECTS_ACCESSED、および OBJECTS_MODIFIED 列のデータを示します。

DIRECT_OBJECTS_ACCESSED

BASE_OBJECTS_ACCESSED

OBJECTS_MODIFIED

[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68613,
        "columnName": "ID"
      },
      {
        "columnId": 68612,
        "columnName": "NAME"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66568,
    "objectName": "TEST_DB.TEST_SCHEMA.T2"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "objectDomain": "Stage",
    "objectId": 118,
    "objectName": "TEST_DB.TEST_SCHEMA.S2",
    "stageKind": "External Named"
  }
]
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
[
  {
    "objectDomain": "Stage",
    "objectId": 117,
    "objectName": "TEST_DB.TEST_SCHEMA.S1",
    "stageKind": "External Named"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68614,
        "columnName": "CUSTOMER_INFO"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66570,
    "objectName": "TEST_DB.TEST_SCHEMA.T3"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68610,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66564,
    "objectName": "TEST_DB.TEST_SCHEMA.T1"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68615,
        "columnName": "NAME"
      },
      {
        "columnId": 68616,
        "columnName": "ID"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66572,
    "objectName": "TEST_DB.TEST_SCHEMA.T4"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68611,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66566,
    "objectName": "TEST_DB.TEST_SCHEMA.T6"
  }
]
[
  {
    "columns": [
      {
        "columnId": 68618,
        "columnName": "CONTENT"
      }
    ],
    "objectDomain": "Table",
    "objectId": 66574,
    "objectName": "TEST_DB.TEST_SCHEMA.T7"
  }
]

次の例のクエリでは、 再帰的な共通テーブル式 を使用していることに注意してください。

with access_history_flatten as (
    select
        r.value:"objectId" as source_id,
        r.value:"objectName" as source_name,
        r.value:"objectDomain" as source_domain,
        w.value:"objectId" as target_id,
        w.value:"objectName" as target_name,
        w.value:"objectDomain" as target_domain,
        c.value:"columnName" as target_column,
        t.query_start_time as query_start_time
    from
        (select * from TEST_DB.ACCOUNT_USAGE.ACCESS_HISTORY) t,
        lateral flatten(input => t.BASE_OBJECTS_ACCESSED) r,
        lateral flatten(input => t.OBJECTS_MODIFIED) w,
        lateral flatten(input => w.value:"columns", outer => true) c
        ),
    sensitive_data_movements(path, target_id, target_name, target_domain, target_column, query_start_time)
    as
      -- Common Table Expression
      (
        -- Anchor Clause: Get the objects that access S1 directly
        select
            f.source_name || '-->' || f.target_name as path,
            f.target_id,
            f.target_name,
            f.target_domain,
            f.target_column,
            f.query_start_time
        from
            access_history_flatten f
        where
        f.source_domain = 'Stage'
        and f.source_name = 'TEST_DB.TEST_SCHEMA.S1'
        and f.query_start_time >= dateadd(day, -30, date_trunc(day, current_date))
        union all
        -- Recursive Clause: Recursively get all the objects that access S1 indirectly
        select sensitive_data_movements.path || '-->' || f.target_name as path, f.target_id, f.target_name, f.target_domain, f.target_column, f.query_start_time
          from
             access_history_flatten f
            join sensitive_data_movements
            on f.source_id = sensitive_data_movements.target_id
                and f.source_domain = sensitive_data_movements.target_domain
                and f.query_start_time >= sensitive_data_movements.query_start_time
      )
select path, target_name, target_id, target_domain, array_agg(distinct target_column) as target_columns
from sensitive_data_movements
group by path, target_id, target_name, target_domain;

クエリは、ステージ S1 のデータ移動に関連する次の結果セットを生成します。

PATH

TARGET_NAME

TARGET_ID

TARGET_DOMAIN

TARGET_COLUMNS

TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1

TEST_DB.TEST_SCHEMA.T1

66564

テーブル

["CONTENT"]

TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.S2

TEST_DB.TEST_SCHEMA.S2

118

ステージ

[]

TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.T2

TEST_DB.TEST_SCHEMA.T2

66568

テーブル

["NAME","ID"]

TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.T4

TEST_DB.TEST_SCHEMA.T4

66572

テーブル

["ID","NAME"]

TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T3

TEST_DB.TEST_SCHEMA.T3

66570

テーブル

["CUSTOMER_INFO"]

最上部に戻る