Python 프로시저 처리기 코드 프로파일링하기

기본 제공 코드 프로파일러를 사용하여 처리기 코드를 실행하는 데 얼마나 많은 시간이나 메모리가 사용되었는지 확인할 수 있습니다. 프로파일러는 프로시저 처리기의 각 줄을 실행하는 데 소요된 시간 또는 메모리를 설명하는 정보를 생성합니다.

프로파일러를 사용하면 다음 중 하나에 초점을 맞춘 보고서를 한 번에 생성할 수 있습니다.

  • 라인당 시간: 보고서에 라인이 실행된 횟수, 실행에 걸린 시간 등이 표시됩니다.

  • 라인당 메모리 사용량: 보고서에 라인당 소비된 메모리의 양이 표시됩니다.

프로파일러는 생성된 보고서를 사용자가 지정한 Snowflake 내부 사용자 스테이지 에 저장합니다. GET_PYTHON_PROFILER_OUTPUT (SNOWFLAKE.CORE) 시스템 함수를 사용하여 프로파일러 출력을 읽을 수 있습니다.

참고

프로파일링은 Python 실행에 성능 오버헤드를 유발하며 쿼리 성능에 영향을 미칠 수 있습니다. 이 기능은 개발, 테스트 및 문제 해결을 위한 것으로 지속적인 프로덕션 워크로드에서는 활성화해서는 안 됩니다.

필수 권한

세션 수준 매개 변수를 설정해도 권한 확인이 트리거되지 않지만 ACTIVE_PYTHON_PROFILER 세션 매개 변수를 LINE 또는 MEMORY 로 설정하여 저장 프로시저를 실행하면 다음과 같은 권한이 확인됩니다.

  • 프로파일링 출력 스테이지에 대한 읽기/쓰기 권한이 있어야 합니다.

  • 프로필 저장 프로시저가 호출자 권한 저장 프로시저 인 경우 저장 프로시저에 USAGE 권한이 있는 역할을 사용해야 합니다.

  • 프로필 저장 프로시저가 소유자 권한 저장 프로시저 인 경우 저장 프로시저에 OWNERSHIP 권한이 있는 역할을 사용해야 합니다.

제한 사항

  • 저장 프로시저만 지원됩니다. UDF 지원은 아직 제공되지 않습니다.

  • 재귀 프로파일링은 지원되지 않습니다. 지정된 모듈의 최상위 함수만 프로파일링됩니다. 함수 내부에 정의된 함수는 그렇지 않습니다.

  • snowflake.snowpark API 를 통해 클라이언트 측에서 생성된 저장 프로시저 프로필은 지원되지 않습니다(예: Session.sproc.register 에서 생성된 저장 프로시저).

  • joblib 를 통해 병렬로 실행되는 Python 함수는 프로파일링되지 않습니다.

  • 시스템 정의 저장 프로시저는 프로파일링할 수 없습니다. 출력이 생성되지 않습니다.

사용법

프로파일러를 사용할 수 있도록 설정한 후에는 저장 프로시저를 호출하여 프로파일러 출력을 생성하는 것만으로 프로파일러를 사용할 수 있습니다. 프로시저 실행이 완료되면 프로파일러의 출력은 사용자가 지정한 스테이지의 파일에 기록됩니다. 시스템 함수를 사용하여 프로파일러 출력을 가져올 수 있습니다.

프로파일러를 설정하고 사용하려면 다음 단계를 따르십시오.

  1. 프로필 출력을 작성할 Snowflake 스테이지를 지정합니다.

    PYTHON_PROFILER_TARGET_STAGE 매개 변수를 스테이지의 정규화된 이름으로 설정합니다.

  2. 프로파일러를 활성화하고 프로필이 초점을 맞출 대상을 지정합니다.

    ACTIVE_PYTHON_PROFILER 세션 매개 변수를 설정합니다.

  3. 저장 프로시저를 호출합니다.

    프로파일러가 활성화된 후 저장 프로시저를 호출합니다.

  4. 프로파일링 출력을 봅니다.

    실행이 끝나면 프로파일링 출력은 출력 스테이지에 <query_id>_<sproc_name>.lprof 또는 <query_id>_<sproc_name>.mprof 의 명명 패턴을 가진 파일로 업로드됩니다.

프로필 출력을 작성할 Snowflake 스테이지 지정하기

프로파일러를 실행하기 전에 보고서를 저장할 스테이지를 지정해야 합니다. 스테이지를 지정하려면 PYTHON_PROFILER_TARGET_STAGE 매개 변수를 스테이지의 정규화된 이름으로 설정합니다.

  • 임시 스테이지를 사용하여 세션 기간 동안만 출력을 저장합니다.

  • 영구 스테이지를 사용하여 세션 범위 밖에서 프로파일러 출력을 보존하십시오.

다음 예의 코드는 프로파일러 출력을 수신하기 위해 임시 profiler_output 스테이지를 생성합니다.

USE DATABASE my_database;
USE SCHEMA my_schema;

CREATE TEMPORARY STAGE profiler_output;
ALTER SESSION SET PYTHON_PROFILER_TARGET_STAGE = "my_database.my_schema.profiler_output";
Copy

프로파일러를 활성화하고 프로필이 초점을 맞출 대상 지정하기

ACTIVE_PYTHON_PROFILER 세션 매개 변수를 생성할 프로필 보고서의 종류를 지정하는 값으로 설정합니다.

  • 프로필이 라인 사용 활동에 초점을 맞추도록 하려면 아래와 같이 매개 변수를 LINE 값(대/소문자 구분 없음)으로 설정하십시오.

    ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'LINE';
    
    Copy
  • 프로필이 메모리 사용 활동에 초점을 맞추도록 하려면 아래와 같이 매개 변수를 MEMORY 값(대/소문자 구분 없음)으로 설정하십시오.

    ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'MEMORY';
    
    Copy

저장 프로시저 호출

프로파일러가 활성화된 후 저장 프로시저를 호출합니다.

CALL YOUR_STORED_PROCEDURE();
Copy

기본적으로 프로파일러는 사용자의 모듈에 정의된 메서드를 프로파일링합니다. 다른 모듈도 프로필에 등록할 수 있습니다. 자세한 내용은 추가 모듈 프로파일링 섹션을 참조하십시오.

프로파일링 출력 보기

실행이 끝나면 프로파일링 출력은 출력 스테이지에 <query_id>_<sproc_name>.lprof 또는 <query_id>_<sproc_name>.mprof 의 명명 패턴을 가진 파일로 업로드됩니다.

출력은 SNOWFLAKE 데이터베이스 에서 시스템 함수 GET_PYTHON_PROFILER_OUTPUT 을 통해 액세스할 수 있습니다.

시스템 함수의 서명 형식은 다음과 같습니다.

SELECT SNOWFLAKE.CORE.GET_PYTHON_PROFILER_OUTPUT(<query_id>);
Copy

<query_id> 를 프로파일링이 활성화된 저장 프로시저 쿼리의 쿼리 ID 로 바꿉니다.

출력 스테이지에서 출력 파일에 직접 액세스할 수도 있습니다. 자세한 내용은 스테이징된 파일 보기 섹션을 참조하십시오.

참고

시스템 함수는 PYTHON_PROFILER_TARGET_STAGE 매개 변수로 지정된 스테이지에서 프로파일링 출력 파일을 찾습니다.

하위 저장 프로시저에 대한 프로파일링 출력은 상위 프로시저 출력에 추가되지 않습니다. 하위 저장 프로시저의 출력을 보려면 하위 프로시저 쿼리 ID 에서 시스템 함수를 명시적으로 호출합니다.

프로파일링을 위한 추가 모듈 포함하기

기본적으로 포함되지 않는 프로파일링 모듈을 포함할 수 있습니다. 프로파일링에 추가 모듈을 포함하려면 PYTHON_PROFILER_MODULES 매개 변수를 포함할 모듈의 이름으로 설정합니다.

기본적으로 모듈에 정의된 메서드는 프로파일링됩니다. 이러한 방법에는 다음이 포함됩니다.

  • 처리기 메서드

  • 모듈에 정의된 메서드

  • 패키지 또는 다른 모듈에서 가져온 메서드.

아래 예에서는 handler, helper, some_method 가 모두 기본적으로 프로파일링됩니다.

CREATE OR REPLACE PROCEDURE my_sproc()
RETURNS VARIANT
LANGUAGE PYTHON
RUNTIME_VERSION = 3.10
PACKAGES = ('snowflake-snowpark-python', 'other_package')
HANDLER='handler'
AS $$
from other_package import some_method

def helper():
...

def handler(session):
...
$$;
Copy

PYTHON_PROFILER_MODULES 매개 변수가 있는 모듈 포함하기

PYTHON_PROFILER_MODULES 매개 변수를 사용하여 기본적으로 포함되지 않는 모듈을 프로필에 포함할 수 있습니다. 이러한 방식으로 모듈을 포함하면 해당 모듈에서 사용된 모든 함수가 프로파일러 출력에 포함됩니다. 기본적으로 PYTHON_PROFILER_MODULES 매개 변수 값은 빈 문자열('')이며, 프로필은 인라인 처리기 코드(있는 경우)만 프로파일링합니다.

프로파일링할 모듈을 포함하려면 아래 그림과 같이 쉼표로 구분된 목록에 매개 변수 값으로 모듈 이름을 지정합니다.

ALTER SESSION SET PYTHON_PROFILER_MODULES = 'module_a, my_module';
Copy

스테이징 처리기 코드 프로파일링하기

도우미 함수를 포함하여 인라인이 아닌 스테이징된 처리기 코드를 프로파일링하려면 PYTHON_PROFILER_MODULES 매개 변수를 사용하여 프로파일링할 스테이징된 처리기를 명시적으로 지정해야 합니다.

기본적으로 프로파일러는 인라인이 아닌 스테이징된 처리기 코드(즉, 처리기 모듈이 IMPORTS 절로 지정된 경우)를 프로파일링하지 않습니다.

예를 들어, 기본적으로 이 프로시저는 자세한 프로파일링 출력을 생성하지 않습니다.

CREATE OR REPLACE PROCEDURE test_udf_1()
RETURNS STRING
LANGUAGE PYTHON
RUNTIME_VERSION = '3.8'
PACKAGES=('snowflake-snowpark-python')
HANDLER = 'test_python_import_main.my_udf'
IMPORTS = ('@stage1/test_python_import_main.py', '@stage2/test_python_import_module.py');
Copy

프로파일링을 위해 스테이징된 코드를 포함하려면 아래 그림과 같이 쉼표로 구분된 목록에 스테이징된 모듈 이름을 PYTHON_PROFILER_MODULES 매개 변수 값으로 지정하십시오.

ALTER SESSION SET PYTHON_PROFILER_MODULES = 'test_python_import_main, test_python_import_module';
Copy

이 예의 코드는 프로파일러를 사용하여 라인 사용량 보고서를 생성하고 검색하는 방법을 보여줍니다.

CREATE OR REPLACE PROCEDURE last_n_query_duration(last_n number, total number)
RETURNS string
LANGUAGE PYTHON
RUNTIME_VERSION=3.8
PACKAGES=('snowflake-snowpark-python')
HANDLER='main'
AS
$$
import snowflake.snowpark.functions as funcs

def main(session, last_n, total):
  # create sample dataset to emulate id + elapsed time
  session.sql('''
  CREATE OR REPLACE TABLE sample_query_history (query_id INT, elapsed_time FLOAT)
  ''').collect()
  session.sql('''
  INSERT INTO sample_query_history
  SELECT
  seq8() AS query_id,
  uniform(0::float, 100::float, random()) as elapsed_time
  FROM table(generator(rowCount => {0}));'''.format(total)).collect()

  # get the mean of the last n query elapsed time
  df = session.table('sample_query_history').select(
    funcs.col('query_id'),
    funcs.col('elapsed_time')).limit(last_n)

  pandas_df = df.to_pandas()
  mean_time = pandas_df.loc[:, 'ELAPSED_TIME'].mean()
  del pandas_df
  return mean_time
$$;

CREATE TEMPORARY STAGE profiler_output;
ALTER SESSION SET PYTHON_PROFILER_TARGET_STAGE = "my_database.my_schema.profiler_output";
ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'LINE';

-- Sample 1 million from 10 million records
CALL last_n_query_duration(1000000, 10000000);

SELECT SNOWFLAKE.CORE.GET_PYTHON_PROFILER_OUTPUT(last_query_id());
Copy

라인 프로파일러 출력은 다음과 같습니다.

Handler Name: main
Python Runtime Version: 3.8
Modules Profiled: ['main_module']
Timer Unit: 0.001 s

Total Time: 8.96127 s
File: _udf_code.py
Function: main at line 4

Line #      Hits        Time  Per Hit   % Time  Line Contents
==============================================================
    4                                           def main(session, last_n, total):
    5                                               # create sample dataset to emulate id + elapsed time
    6         1        122.3    122.3      1.4      session.sql('''
    7                                                   CREATE OR REPLACE TABLE sample_query_history (query_id INT, elapsed_time FLOAT)''').collect()
    8         2       7248.4   3624.2     80.9      session.sql('''
    9                                               INSERT INTO sample_query_history
    10                                               SELECT
    11                                               seq8() AS query_id,
    12                                               uniform(0::float, 100::float, random()) as elapsed_time
    13         1          0.0      0.0      0.0      FROM table(generator(rowCount => {0}));'''.format(total)).collect()
    14
    15                                               # get the mean of the last n query elapsed time
    16         3         58.6     19.5      0.7      df = session.table('sample_query_history').select(
    17         1          0.0      0.0      0.0          funcs.col('query_id'),
    18         2          0.0      0.0      0.0          funcs.col('elapsed_time')).limit(last_n)
    19
    20         1       1528.4   1528.4     17.1      pandas_df = df.to_pandas()
    21         1          3.2      3.2      0.0      mean_time = pandas_df.loc[:, 'ELAPSED_TIME'].mean()
    22         1          0.3      0.3      0.0      del pandas_df
    23         1          0.0      0.0      0.0      return mean_time

메모리 프로파일러 출력은 다음과 같습니다.

ALTER SESSION SET ACTIVE_PYTHON_PROFILER = 'MEMORY';

Handler Name: main
Python Runtime Version: 3.8
Modules Profiled: ['main_module']
File: _udf_code.py
Function: main at line 4

Line #   Mem usage    Increment  Occurrences  Line Contents
=============================================================
    4    245.3 MiB    245.3 MiB           1   def main(session, last_n, total):
    5                                             # create sample dataset to emulate id + elapsed time
    6    245.8 MiB      0.5 MiB           1       session.sql('''
    7                                                 CREATE OR REPLACE TABLE sample_query_history (query_id INT, elapsed_time FLOAT)''').collect()
    8    245.8 MiB      0.0 MiB           2       session.sql('''
    9                                             INSERT INTO sample_query_history
    10                                             SELECT
    11                                             seq8() AS query_id,
    12                                             uniform(0::float, 100::float, random()) as elapsed_time
    13    245.8 MiB      0.0 MiB           1       FROM table(generator(rowCount => {0}));'''.format(total)).collect()
    14
    15                                             # get the mean of the last n query elapsed time
    16    245.8 MiB      0.0 MiB           3       df = session.table('sample_query_history').select(
    17    245.8 MiB      0.0 MiB           1           funcs.col('query_id'),
    18    245.8 MiB      0.0 MiB           2           funcs.col('elapsed_time')).limit(last_n)
    19
    20    327.9 MiB     82.1 MiB           1       pandas_df = df.to_pandas()
    21    328.9 MiB      1.0 MiB           1       mean_time = pandas_df.loc[:, 'ELAPSED_TIME'].mean()
    22    320.9 MiB     -8.0 MiB           1       del pandas_df
    23    320.9 MiB      0.0 MiB           1       return mean_time