アクセス履歴¶
このトピックでは、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_1
と view_3
は、 DIRECT_OBJECTS_ACCESSED
と BASE_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
。
列系統¶
列系統(つまり、列のアクセス履歴)は、Account Usage ACCESS_HISTORY ビューを拡張して、書き込み操作でソース列からターゲット列にデータがどのように流れるかを指定します。Snowflakeは、ソース列からのデータを参照する後続のすべてのテーブルオブジェクト(例: INSERT、 MERGE、 CTAS)を介して、ソース列からのデータを追跡します。ただし、系統チェーン内のオブジェクトがドロップされている場合を除きます。Snowflakeは、 ACCESS_HISTORY ビューの OBJECTS_MODIFIED 列を強化することで、列系統にアクセスできるようにします。
詳細については、次をご参照ください。
サポートされている操作¶
ACCESS_HISTORY ビューがサポートする読み取りおよび書き込み操作の詳細については、 ACCESS_HISTORY ビュー の使用上の注意セクションをご参照ください。
利点¶
Snowflakeのアクセス履歴には、読み取りおよび書き込み操作に関する次の利点があります。
- データ検出
未使用のデータを検出して、データをアーカイブするか削除するかを決定します。
- 機密データの移動状況の追跡
外部クラウドストレージの場所(例: Amazon S3バケット)からターゲットのSnowflakeテーブルへの、またはその逆のデータ移動を追跡します。
あるSnowflakeテーブルから異なるSnowflakeテーブルへの内部データの移動を追跡します。
機密データの移動をトレースした後、ポリシー(マスキング および 行アクセス)を適用してデータを保護し、 アクセス制御設定 を更新して、ステージへのアクセスをさらに規制します。テーブル、および タグ を設定して、機密データを含むステージ、テーブル、および列のコンプライアンス要件について追跡できるようにします。
- データ検証
レポート、ダッシュボード、およびチャートやグラフなどのデータ可視化製品の正確性と整合性は、データを元のソースまで追跡できるため、検証されます。
データのスチュワードは、特定のテーブルまたはビューをドロップまたは変更する前に、ユーザーに通知することもできます。
- コンプライアンス監査
GDPR や CCPA などのコンプライアンス規制を満たすために、テーブルまたはステージで書き込み操作を実行したSnowflakeユーザーを特定し、書き込み操作がいつ発生したかを特定します。
- 全体的なデータガバナンスの強化
ACCESS_HISTORY ビューは、アクセスされたデータ、データアクセスがいつ行われたか、およびアクセスされたデータがデータソースオブジェクトからデータターゲットオブジェクトにどのように移動したかを統一的に示します。
列系統には、次の追加の利点があります。
- 派生オブジェクトの保護
データスチュワードは、派生オブジェクト(例: CTAS)を作成した後に追加作業なしで、機密性の高いソース列に簡単に タグ付け できます。その後、データスチュワードは、機密性の高い列を含むテーブルを 行アクセスポリシー で保護するか、機密性の高い列自体を マスキングポリシー または タグベースのマスキングポリシー で保護できます。
- 機密列のコピー頻度
データプライバシー担当者は、機密データを含む列のオブジェクト数(例: テーブル1つ、ビュー2つ)を迅速に判断できます。機密データを含む列がテーブルオブジェクトに何回出現するかを知ることで、データプライバシー担当者は、規制コンプライアンス基準をどのように満たしているかを証明できます(例: 欧州連合の一般データ保護規則(GDPR)基準に準拠)。
- 根本原因の分析
列系統は、データをそのソースまで追跡するメカニズムを提供します。これは、データ品質の低下に起因する障害点を特定し、トラブルシューティングプロセス中に分析する列の数を減らすのに役立ちます。
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()) ;
32998411400350
のobject_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()) ;
32998411400350
のobject_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;条件:
T1
、T2
...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"]
例: 列系統¶
次の例では、 ACCESS_HISTORY ビューをクエリし、 FLATTEN 関数を使用して OBJECTS_MODIFIED 列をフラット化します。
代表的な例として、Snowflakeアカウントで次の SQL クエリを実行して、以下のテーブルを生成します。ここで、番号付きのコメントは次を示しています。
// 1
:directSources
フィールドとターゲット列の間のマッピングを取得します。// 2
:baseSources
フィールドとターゲット列の間のマッピングを取得します。
// 1
select
directSources.value: "objectId" as source_object_id,
directSources.value: "objectName" as source_object_name,
directSources.value: "columnName" as source_column_name,
'DIRECT' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "directSources",
outer => true
) directSources
union
// 2
select
baseSources.value: "objectId" as source_object_id,
baseSources.value: "objectName" as source_object_name,
baseSources.value: "columnName" as source_column_name,
'BASE' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "baseSources",
outer => true
) baseSources
;
戻り値:
SOURCE_OBJECT_ID
SOURCE_OBJECT_NAME
SOURCE_COLUMN_NAME
SOURCE_COLUMN_TYPE
TARGET_OBJECT_NAME
TARGET_COLUMN_NAME
1
D.S.T0
NAME
BASE
D.S.T1
NAME
2
D.S.V1
NAME
DIRECT
D.S.T1
NAME