액세스 내역

이 항목에서는 Snowflake의 사용자 액세스 기록에 대한 개념을 제공합니다.

이 항목의 내용:

개요

Snowflake의 액세스 기록은 사용자 쿼리가 열 데이터를 읽을 때와 SQL 문이 원본 데이터 오브젝트에서 대상 데이터 오브젝트로 복사하는 COPY 명령의 변형과 함께 INSERT, UPDATE, DELETE와 같은 데이터 쓰기 작업을 수행할 때를 말합니다. 사용자 액세스 기록은 Account Usage ACCESS_HISTORY 뷰를 쿼리하여 찾을 수 있습니다.

ACCESS_HISTORY 뷰의 각 행에는 SQL 문마다 단 하나의 레코드가 포함됩니다. 레코드는 쿼리가 직간접적으로 액세스한 열(즉, 쿼리를 위한 데이터의 출처인 기본 테이블)을 설명합니다. 사용자(즉, 쿼리 연산자), 쿼리, 테이블 또는 뷰, 열 및 데이터 사이에는 직접 링크가 있기 때문에 이러한 레코드는 규정 준수 감사를 간소화하며 인기 있고 자주 액세스하는 테이블 및 열에 대한 정보를 제공합니다.

읽기 및 쓰기 작업 추적하기

ACCESS_HISTORY 뷰는 다음 열을 포함합니다.

QUERY_ID | QUERY_START_TIME | USER_NAME | DIRECT_OBJECTS_ACCESSED | BASE_OBJECTS_ACCESSED | OBJECTS_MODIFIED

마지막 열 OBJECTS_MODIFIED 가 Snowflake 열, 테이블 및 스테이지와 관련된 데이터 쓰기 정보를 지정하는 동안 처음 5개 열을 통해 읽기 작업이 추적됩니다.

Snowflake의 쿼리, 그리고 데이터베이스 오브젝트가 만들어진 방식에 따라, Snowflake가 DIRECT_OBJECTS_ACCESSED, BASE_OBJECTS_ACCESSEDOBJECTS_MODIFIED 열에 반환하는 정보가 결정됩니다.

열 세부 정보는 ACCESS_HISTORY 뷰의 섹션을 참조하십시오.

읽기

읽기 쿼리와 ACCESS_HISTORY 뷰가 이 정보를 기록하는 방법을 이해하려면 다음 시나리오를 고려하십시오.

  • 일련의 오브젝트: base_table » view_1 » view_2 » view_3.

  • 다음과 같은 view_2 에 대한 읽기 쿼리:

    select * from view_2;
    

이 예에서 Snowflake는 다음을 반환합니다.

  • 쿼리가 view_2 를 지정하므로 view_2DIRECT_OBJECTS_ACCESSED 로 반환합니다.

  • view_2 에서 데이터의 원래 소스이므로 base_tableBASE_OBJECTS_ACCESSED 로 반환합니다.

view_1view_3DIRECT_OBJECTS_ACCESSEDBASE_OBJECTS_ACCESSED 에 포함되지 않습니다. 이러한 뷰 중 어느 것도 쿼리에 포함되지 않았고, view_2 의 데이터에 대한 소스 역할을 하는 기본 오브젝트가 아니기 때문입니다.

쓰기

쓰기 작업과 ACCESS_HISTORY 뷰가 이 정보를 기록하는 방법을 이해하려면 다음 시나리오를 고려하십시오.

  • 데이터 원본: base_table

  • 데이터 원본에서 테이블 만들기(즉, CTAS):

    create table table_1 as select * from base_table;
    

이 예에서 Snowflake는 다음을 반환합니다.

  • 테이블이 직접 액세스되었고 데이터의 원본이므로 BASE_OBJECTS_ACCESSEDDIRECT_OBJECTS_ACCESSED 열의 base_table.

  • 테이블을 만들 때 작성된 열이 있는 OBJECTS_MODIFIEDtable_1.

지원되는 작업

ACCESS_HISTORY 뷰가 지원하는 읽기 및 쓰기 작업에 대한 전체 설명은 ACCESS_HISTORY 뷰의 사용법 노트 섹션을 참조하십시오.

이점

Snowflake의 액세스 기록은 다음과 같은 이점을 제공합니다.

데이터 검색

미사용 데이터를 발견하여 데이터의 보관 또는 삭제 여부를 결정합니다.

민감한 데이터의 이동 방식 추적

외부 클라우드 저장소 위치(예: Amazon S3 버킷)에서 대상 Snowflake 테이블로, 또는 그 반대로의 데이터 이동을 추적합니다.

Snowflake 테이블에서 다른 Snowflake 테이블로의 내부 데이터 이동을 추적합니다.

민감한 데이터의 이동을 추적한 후, 데이터를 보호하기 위한 정책(마스킹행 액세스)을 적용하고, 액세스 제어 설정 업데이트하여 스테이지와 테이블에 대한 액세스를 추가로 규제하고, 민감한 데이터가 있는 스테이지, 테이블, 열이 규정 준수 요구 사항에 맞는지 추적할 수 있도록 태그 를 설정합니다.

데이터 유효성 검사

데이터를 원본과 비교해 추적할 수 있으므로 보고서, 대시보드 그리고 차트 및 그래프와 같은 데이터 시각화 제품의 정확성과 무결성이 검증됩니다.

데이터 관리자가 사용자에게 알린 후 주어진 테이블 또는 뷰를 삭제하거나 변경할 수도 있습니다.

규정 준수 감사

테이블 또는 스테이지에서 쓰기 작업을 수행한 Snowflake 사용자와 GDPRCCPA 와 같은 규정 준수 규제를 충족하려고 쓰기 작업이 발생한 시간을 식별합니다.

전반적인 데이터 거버넌스 강화

ACCESS_HISTORY 뷰에서는 액세스한 데이터, 데이터 액세스가 발생한 시간, 액세스한 데이터가 데이터 원본 오브젝트에서 데이터 대상 오브젝트로 이동한 방법을 통합적으로 볼 수 있습니다.

ACCESS_HISTORY 뷰 쿼리하기

다음 하위 섹션에서는 ACCESS_HISTORY 뷰에 대한 쿼리 예시를 제공합니다.

예시 쿼리 중 일부는 쿼리 성능을 향상하기 위해 QUERY_START_TIME 열을 필터링함에 유의하십시오. 성능을 향상하기 위한 다른 옵션은 더 좁은 시간 범위에 대하여 쿼리를 수행하는 것입니다.

예: 쿼리 읽기

아래 하위 섹션에서는 다음 사용 사례에 대한 읽기 작업의 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())
    ;
    
  • 다음과 같이 object_id32998411400350 을 사용하여, 지난 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())
    ;
    
  • object_id32998411400350 을 사용하여, 지난 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 열은 데이터가 테이블의 두 열에 기록되었음을 지정합니다.

{
  "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, T2T7 은 테이블의 이름을 지정합니다.

  • S1S2 는 스테이지의 이름을 지정합니다.

액세스 기록을 쿼리하여 스테이지 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》]

맨 위로 이동