Scala로 저장 프로시저 작성하기

Scala에서 저장 프로시저를 작성할 수 있습니다. 저장 프로시저 내에서 Snowpark 라이브러리를 사용하여 Snowflake의 테이블에 대한 쿼리, 업데이트 및 기타 작업을 수행할 수 있습니다.

이 항목에서는 저장 프로시저의 논리를 작성하는 방법에 대해 설명합니다. 논리가 있으면 SQL을 사용하여 프로시저를 만들고 호출할 수 있습니다. 자세한 내용은 저장 프로시저 만들기저장 프로시저 호출하기 섹션을 참조하십시오.

이 항목의 내용:

소개

Snowflake 웨어하우스를 컴퓨팅 프레임워크로 사용하여 Snowflake 내에서 데이터 파이프라인을 구축하고 실행할 수 있습니다. 데이터 파이프라인용 코드의 경우, Scala용 Snowpark API를 사용하여 저장 프로시저를 작성합니다. 이러한 저장 프로시저의 실행을 예약하려면 작업 을 사용합니다.

처리기 코드가 실행될 때 로그 및 추적 데이터를 캡처할 수 있습니다. 자세한 내용은 로깅 및 추적 개요 섹션을 참조하십시오.

참고

익명 프로시저를 만들기도 하고 호출도 하려면 CALL(익명 프로시저 사용) 를 사용하십시오. 익명 프로시저를 만들고 호출하는 데는 CREATE PROCEDURE 스키마 권한이 있는 역할이 필요하지 않습니다.

전제 조건

버전 1.1.0 또는 최신 버전의 Snowpark 라이브러리를 사용해야 합니다.

처리기 코드가 스테이지에 저장될 저장 프로시저를 작성하는 경우, Java 버전 11.x에서 실행되도록 클래스를 컴파일해야 합니다.

Snowpark를 위한 개발 환경 설정

Snowpark 라이브러리를 사용하기 위한 개발 환경을 설정합니다. Snowpark Scala를 위한 개발 환경 설정하기 섹션을 참조하십시오.

처리기 코드 구조화 및 작성하기

프로시저를 생성하는 SQL로 처리기 소스 코드를 인라인으로 유지하거나 처리기로 컴파일된 결과를 별도의 위치에 유지하고 SQL에서 참조할 수 있습니다. 자세한 내용은 처리기 코드를 인라인 또는 스테이지에 유지하기 섹션을 참조하십시오.

프로시저와 함께 사용할 처리기 소스 코드 작성에 대한 자세한 내용은 처리기 코드 패키징하기 섹션을 참조하십시오.

프로시저 만들기 및 호출하기

프로시저 처리기를 작성했으면 SQL을 사용하여 프로시저를 생성하고 호출할 수 있습니다.

  • 저장 프로시저 만들기

    SQL로 저장 프로시저를 만드는 방법에 대한 자세한 내용은 저장 프로시저 만들기 섹션을 참조하십시오.

    SQL로 익명 프로시저를 만드는 방법에 대한 자세한 내용은 CALL(익명 프로시저 사용) 섹션을 참조하십시오.

  • 저장 프로시저 호출하기

    SQL에서 저장 프로시저를 호출하는 방법에 대한 자세한 내용은 저장 프로시저 호출하기 섹션을 참조하십시오.

제한 사항

Snowpark 저장 프로시저에는 다음과 같은 제한 사항이 있습니다.

  • 동시성은 지원되지 않습니다. 예를 들어 코드 내에서 여러 스레드의 쿼리를 제출할 수 없습니다. 여러 쿼리를 동시에 실행하는 코드는 오류를 일으킵니다.

  • 작업에서 저장 프로시저를 실행하는 경우, 작업을 만들 때 웨어하우스를 지정해야 합니다. (사용자는 서버리스 컴퓨팅 리소스를 사용하여 작업을 실행할 수 없습니다.)

  • 저장 프로시저에서 일부 Snowpark API를 사용할 때 다음 제한 사항이 있습니다.

    • PUT 및 GET 명령을 실행하는 API (Session.sql("PUT ...")Session.sql("GET ...") 포함)를 사용할 때 프로시저를 호출하는 쿼리에 대해 제공된 메모리 지원 파일 시스템의 /tmp 디렉터리에만 쓸 수 있습니다.

    • 비동기 작업용 API 를 사용하지 마십시오.

    • 새 세션을 만드는 API (예: Session.builder().configs(...).create())를 사용하지 마십시오.

    • session.jdbcConnection 과 이를 통해 반환되는 연결을 사용하면 안전하지 않은 동작이 발생할 수 있으므로 이 기능은 지원되지 않습니다.

  • 명명된 임시 오브젝트 만들기는 소유자의 권한 저장 프로시저에서 지원되지 않습니다. 소유자의 권한 저장 프로시저는 저장 프로시저 소유자의 권한으로 실행되는 저장 프로시저입니다. 자세한 내용은 호출자 권한 또는 소유자 권한 을 참조하십시오.

저장 프로시저에 대한 처리기 코드 작성하기

프로시저 논리의 경우, 프로시저가 호출될 때 실행되는 처리기 코드를 작성합니다. 이 섹션에서는 처리기의 설계에 대해 설명합니다.

프로시저를 생성하는 SQL 문과 함께 이 코드를 인라인으로 포함하거나 코드를 스테이지에 복사하고 프로시저를 생성할 때 그 스테이지에서 코드를 참조할 수 있습니다. 자세한 내용은 처리기 코드를 인라인 또는 스테이지에 유지하기 섹션을 참조하십시오.

저장 프로시저 작성 계획하기

  • 사용되는 메모리양을 제한합니다.

    Snowflake는 필요한 메모리 양 측면에서 메서드에 제한을 둡니다. 너무 많이 사용하지 않도록 방지하는 방법에 대한 자세한 내용은 Snowflake에서 적용한 제약 조건 내에서 유지되는 처리기 설계하기 섹션을 참조하십시오.

  • 스레드로부터 안전한 코드 작성하기

    처리기 메서드 또는 함수가 스레드로부터 안전한지 확인하십시오.

  • 보안 제한 사항을 이해하십시오.

    처리기 코드는 제한된 엔진 내에서 실행되므로 UDF 및 프로시저의 보안 모범 사례 에 설명된 규칙을 따라야 합니다.

  • 소유자의 권한 또는 호출자의 권한 사용 결정하기

    저장 프로시저를 작성하려고 계획할 때 저장 프로시저를 호출자의 권한으로 실행할지 소유자의 권한으로 실행할지 여부를 고려하십시오.

  • 저장 프로시저의 시간 초과 동작을 염두에 두십시오.

    코드 활동으로 타이머가 재설정되지 않는 한 저장 프로시저 실행 시간이 초과됩니다. 특히 시간 제한 타이머는 파일 작업, 쿼리, 결과 세트를 통한 반복을 포함하여, 코드와 데이터의 상호 작용에 의해 재설정됩니다.

클래스 또는 오브젝트 작성하기

정의하는 메서드나 함수는 클래스나 오브젝트의 일부여야 합니다.

클래스나 오브젝트를 작성할 때 다음 사항에 유의하십시오.

  • 클래스(또는 오브젝트) 및 메서드는 보호되거나 비공개여서는 안 됩니다.

  • 메서드가 정적이 아니고 사용자가 생성자를 정의하려는 경우, 클래스에 대해 인자 없는 생성자를 정의합니다. Snowflake는 초기화 시 이 인자 없는 생성자를 호출하여 클래스의 인스턴스를 만듭니다.

  • 동일한 클래스 또는 오브젝트에서 서로 다른 저장 프로시저에 대해 서로 다른 메서드를 정의할 수 있습니다.

메서드 또는 함수 작성하기

저장 프로시저에 대한 메서드 또는 함수를 작성할 때 다음 사항에 유의하십시오.

  • Snowpark Session 오브젝트를 메서드 또는 함수의 첫 번째 인자로 지정합니다.

    저장 프로시저를 호출하면 Snowflake는 자동으로 Session 오브젝트를 생성하여 저장 프로시저에 전달합니다. (Session Session 오브젝트를 직접 만들 수는 없습니다.)

  • 나머지 인자와 반환 값의 경우, Snowflake 데이터 타입 에 해당하는 Scala 타입 을 사용합니다.

  • 메서드 또는 함수는 값을 반환해야 합니다. Scala의 저장 프로시저의 경우 반환 값이 필요합니다.

  • 코드 활동으로 타이머가 재설정되지 않는 한 저장 프로시저 실행 시간이 초과됩니다. 특히 시간 제한 타이머는 파일 작업, 쿼리, 결과 세트를 통한 반복을 포함하여, 코드와 데이터의 상호 작용에 의해 재설정됩니다.

코드에 종속성을 사용할 수 있도록 만들기

처리기 코드가 처리기 자체 외부에 정의된 코드(예: JAR 파일의 클래스) 또는 리소스 파일에 의존하는 경우 이러한 종속성을 스테이지에 업로드하여 코드에서 사용 가능하게 만들 수 있습니다. 프로시저를 생성할 때 IMPORTS 절을 사용하여 이러한 종속성을 참조할 수 있습니다.

자세한 내용은 코드에 종속성을 사용할 수 있도록 만들기 섹션을 참조하십시오.

Snowflake 프로시저의 데이터에 액세스하기

Snowflake의 데이터에 액세스하려면 Snowpark 라이브러리 API를 사용합니다.

Scala 저장 프로시저에 대한 호출을 처리할 때 Snowflake는 Snowpark Session 오브젝트를 생성하고 저장 프로시저에 대한 메서드나 함수에 오브젝트를 전달합니다.

다른 언어의 저장 프로시저의 경우와 마찬가지로 세션의 컨텍스트(예: 권한, 현재 데이터베이스, 스키마 등)는 저장 프로시저가 호출자의 권한으로 실행되는지 소유자의 권한으로 실행되는지에 따라 결정됩니다. 자세한 내용은 Accessing and Setting the Session State 섹션을 참조하십시오.

Session 오브젝트를 사용하여 Snowpark 라이브러리 에서 API를 호출할 수 있습니다. 예를 들어, 테이블에 대한 DataFrame을 만들거나 SQL 문을 실행할 수 있습니다.

자세한 내용은 Scala용 Snowpark 개발자 가이드 를 참조하십시오.

참고

데이터 액세스에 대한 제한 사항을 비롯하여, 제한 사항에 대한 자세한 내용은 제한 사항 섹션을 참조하십시오.

데이터 액세스 예

다음은 지정된 수의 행을 한 테이블에서 다른 테이블로 복사하는 Scala 메서드의 예입니다. 이 메서드는 다음 인자를 사용합니다.

  • Snowpark Session 오브젝트

  • 행을 복사할 출처가 되는 테이블의 이름

  • 행이 저장될 테이블의 이름

  • 복사할 행의 수

이 예의 메서드는 문자열을 반환합니다.

object MyObject
{
  def myProcedure(session: com.snowflake.snowpark.Session, fromTable: String, toTable: String, count: Int): String =
  {
    session.table(fromTable).limit(count).write.saveAsTable(toTable)
    return "Success"
  }
}
Copy

다음 예에서는 메서드가 아닌 함수를 정의합니다.

object MyObject
{
  val myProcedure = (session: com.snowflake.snowpark.Session, fromTable: String, toTable: String, count: Int): String =>
  {
    session.table(fromTable).limit(count).write.saveAsTable(toTable)
    "Success"
  }
}
Copy

Scala 프로시저로 파일 읽기

처리기 코드로 파일의 내용을 읽을 수 있습니다. 파일은 처리기에 사용할 수 있는 Snowflake 스테이지에 있어야 합니다. 예를 들어 처리기에서 비정형 데이터를 처리하기 위해 파일을 읽을 수 있습니다.

스테이징된 파일의 내용을 읽기 위해, 처리기는 SnowflakeFile 클래스 또는 InputStream 클래스의 메서드를 호출할 수 있습니다. 계산 중에 파일에 동적으로 액세스해야 하는 경우 이 작업을 수행할 수 있습니다. 자세한 내용은 이 항목의 SnowflakeFile 을 사용하여 동적으로 지정된 파일 읽기 또는 InputStream 을 사용하여 동적으로 지정된 파일 읽기 섹션을 참조하십시오.

SnowflakeFile 은 다음 표에 설명된 것처럼 InputStream 과 함께 사용할 수 없는 기능을 제공합니다.

클래스

입력

참고

SnowflakeFile

URL 형식:

  • 함수의 호출자가 소유자이기도 하지 않은 경우 파일 삽입 공격의 위험을 줄이기 위해 범위가 지정된 URL입니다.

  • UDF 소유자가 액세스할 수 있는 파일의 파일 URL 또는 문자열 경로입니다.

파일은 명명된 내부 스테이지 또는 외부 스테이지에 있어야 합니다.

파일 크기와 같은 추가 파일 특성에 쉽게 액세스할 수 있습니다.

InputStream

URL 형식:

  • 함수의 호출자가 소유자이기도 하지 않은 경우 파일 삽입 공격의 위험을 줄이기 위해 범위가 지정된 URL입니다.

파일은 명명된 내부 스테이지 또는 외부 스테이지에 있어야 합니다.

참고

소유자의 권한 저장 프로시저의 경우 프로시저의 소유자는 범위가 지정된 URL이 아닌 모든 파일에 액세스할 수 있어야 합니다. 호출자의 권한 프로시저의 경우 호출자는 범위가 지정된 URL이 아닌 모든 파일에 액세스할 수 있어야 합니다. 어느 경우든 처리기 코드가 새 requireScopedUrl 매개 변수에 대한 boolean 값으로 SnowflakeFile.newInstance 메서드를 호출하도록 하여 스테이징된 파일을 읽을 수 있습니다.

다음 예에서는 범위가 지정된 URL이 필요하지 않음을 지정하면서 SnowflakeFile.newInstance 를 사용합니다.

var filename = "@my_stage/filename.txt"
var sfFile = SnowflakeFile.newInstance(filename, false)
Copy

SnowflakeFile 을 사용하여 동적으로 지정된 파일 읽기

다음 예의 코드에는 String 을 받아 파일의 내용과 함께 String 을 반환하는 처리기 함수 execute 가 있습니다. 런타임에 Snowflake는 프로시저의 input 변수에 있는 수신 파일 경로에서 처리기의 fileName 변수를 초기화합니다. 처리기 코드는 SnowflakeFile 인스턴스를 사용하여 파일을 읽습니다.

CREATE OR REPLACE PROCEDURE file_reader_scala_proc_snowflakefile(input VARCHAR)
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER = 'FileReader.execute'
PACKAGES=('com.snowflake:snowpark:latest')
AS $$
import java.io.InputStream
import java.nio.charset.StandardCharsets
import com.snowflake.snowpark_java.types.SnowflakeFile
import com.snowflake.snowpark_java.Session

object FileReader {
  def execute(session: Session, fileName: String): String = {
    var input: InputStream = SnowflakeFile.newInstance(fileName).getInputStream()
    return new String(input.readAllBytes(), StandardCharsets.UTF_8)
  }
}
$$;
Copy

다음 CALL 예제의 코드는 범위 지정된 파일을 가리키는 URL을 생성합니다. 이것은 스테이지 자체에 권한을 부여하지 않고 스테이징된 파일에 대한 임시 액세스를 허용하는 인코딩된 URL입니다.

CALL file_reader_scala_proc_snowflakefile(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy

InputStream 을 사용하여 동적으로 지정된 파일 읽기

다음 예의 코드에는 InputStream 을 받아 파일의 내용과 함께 String 을 반환하는 처리기 함수 execute 가 있습니다. 런타임에 Snowflake는 프로시저의 input 변수에 있는 수신 파일 경로에서 처리기의 stream 변수를 초기화합니다. 처리기 코드는 InputStream 을 사용하여 파일을 읽습니다.

CREATE OR REPLACE PROCEDURE file_reader_scala_proc_input(input VARCHAR)
RETURNS VARCHAR
LANGUAGE SCALA
RUNTIME_VERSION = 2.12
HANDLER = 'FileReader.execute'
PACKAGES=('com.snowflake:snowpark:latest')
AS $$
import java.io.InputStream
import java.nio.charset.StandardCharsets
import com.snowflake.snowpark_java.Session

object FileReader {
  def execute(session: Session, stream: InputStream): String = {
    val contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8)
    return contents
  }
}
$$;
Copy

다음 CALL 예제의 코드는 범위 지정된 파일을 가리키는 URL을 생성합니다. 이것은 스테이지 자체에 권한을 부여하지 않고 스테이징된 파일에 대한 임시 액세스를 허용하는 인코딩된 URL입니다.

CALL file_reader_scala_proc_input(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
Copy

테이블 형식 데이터 반환하기

테이블 형식으로 데이터를 반환하는 프로시저를 작성할 수 있습니다. 테이블 형식 데이터를 반환하는 프로시저를 작성하려면 다음을 수행하십시오.

  • CREATE PROCEDURE 문에서 프로시저의 반환 유형으로 TABLE(...) 을 지정합니다.

    반환된 데이터의 열 이름과 유형 을 알고 있는 경우 이들을 TABLE 매개 변수로 지정할 수 있습니다. 프로시저를 정의할 때 반환된 열을 모르는 경우(예: 런타임에 지정된 경우) TABLE 매개 변수를 생략할 수 있습니다. 그렇게 하면 프로시저의 반환 값 열이 해당 처리기에서 반환된 데이터 프레임의 열에서 변환됩니다. 열 데이터 타입은 SQL-Scala 데이터 타입 매핑 에 지정된 매핑에 따라 SQL로 변환됩니다.

  • Snowpark 데이터 프레임에서 테이블 형식 결과를 반환하도록 처리기를 작성합니다.

    데이터 프레임에 대한 자세한 내용은 Snowpark Scala에서 DataFrame 작업하기 섹션을 참조하십시오.

참고

다음 중 하나가 충족되는 경우 프로시저는 런타임 시 오류를 생성합니다.

  • TABLE을 반환 유형으로 선언하지만 처리기는 데이터 프레임을 반환하지 않습니다.

  • 처리기는 데이터 프레임을 반환하지만 프로시저는 TABLE을 반환 유형으로 선언하지 않습니다.

이 섹션의 예에서는 열이 문자열과 일치하는 행을 필터링하는 프로시저에서 테이블 형식 값을 반환하는 방법을 보여줍니다.

데이터 정의하기

다음은 직원 테이블을 생성하는 코드 예제입니다.

CREATE OR REPLACE TABLE employees(id NUMBER, name VARCHAR, role VARCHAR);
INSERT INTO employees (id, name, role) VALUES (1, 'Alice', 'op'), (2, 'Bob', 'dev'), (3, 'Cindy', 'dev');
Copy

행을 필터링하는 프로시저 선언하기

다음 두 예제의 코드로 테이블 이름과 역할을 인자로 취하여 역할 열 값이 인자로 지정된 역할과 일치하는 테이블의 행을 반환하는 저장 프로시저를 생성할 수 있습니다.

반환 열 이름 및 유형 지정하기

이 예에서는 RETURNS TABLE() 문에 열 이름과 유형을 지정합니다.

CREATE OR REPLACE PROCEDURE filter_by_role(table_name VARCHAR, role VARCHAR)
RETURNS TABLE(id NUMBER, name VARCHAR, role VARCHAR)
LANGUAGE SCALA
RUNTIME_VERSION = '2.12'
PACKAGES = ('com.snowflake:snowpark:latest')
HANDLER = 'Filter.filterByRole'
AS
$$
import com.snowflake.snowpark.functions._
import com.snowflake.snowpark._

object Filter {
    def filterByRole(session: Session, tableName: String, role: String): DataFrame = {
        val table = session.table(tableName)
        val filteredRows = table.filter(col("role") === role)
        return filteredRows
    }
}
$$;
Copy

참고

현재, RETURNS TABLE(...) 절에서는 GEOGRAPHY를 열 유형으로 지정할 수 없습니다. 이는 저장 프로시저를 생성하든 익명 프로시저를 생성하든 관계없이 적용됩니다.

CREATE OR REPLACE PROCEDURE test_return_geography_table_1()
  RETURNS TABLE(g GEOGRAPHY)
  ...
Copy
WITH test_return_geography_table_1() AS PROCEDURE
  RETURNS TABLE(g GEOGRAPHY)
  ...
CALL test_return_geography_table_1();
Copy

GEOGRAPHY를 열 유형으로 지정하려고 할 경우 저장 프로시저를 호출하면 오류가 발생합니다.

Stored procedure execution error: data type of returned table does not match expected returned table type
Copy

이 문제를 해결하려면 RETURNS TABLE() 에서 열 인자와 유형을 생략하면 됩니다.

CREATE OR REPLACE PROCEDURE test_return_geography_table_1()
  RETURNS TABLE()
  ...
Copy
WITH test_return_geography_table_1() AS PROCEDURE
  RETURNS TABLE()
  ...
CALL test_return_geography_table_1();
Copy
반환 열 이름 및 유형 생략하기

다음 예제의 코드에서는 처리기의 반환 값에 있는 열에서 반환 값 열 이름 및 유형을 추정할 수 있도록 하는 프로시저를 선언합니다. 이 코드는 RETURNS TABLE() 문에서 열 이름과 유형을 생략합니다.

CREATE OR REPLACE PROCEDURE filter_by_role(table_name VARCHAR, role VARCHAR)
RETURNS TABLE()
LANGUAGE SCALA
RUNTIME_VERSION = '2.12'
PACKAGES = ('com.snowflake:snowpark:latest')
HANDLER = 'Filter.filterByRole'
AS
$$
import com.snowflake.snowpark.functions._
import com.snowflake.snowpark._

object Filter {
    def filterByRole(session: Session, tableName: String, role: String): DataFrame = {
        val table = session.table(tableName)
        val filteredRows = table.filter(col("role") === role)
        return filteredRows
    }
}
$$;
Copy

프로시저 호출하기

다음 예에서는 저장 프로시저를 호출합니다.

CALL filter_by_role('employees', 'dev');
Copy

프로시저를 호출하면 다음 출력이 생성됩니다.

+----+-------+------+
| ID | NAME  | ROLE |
+----+-------+------+
| 2  | Bob   | dev  |
| 3  | Cindy | dev  |
+----+-------+------+

스테이징된 처리기로 저장 프로시저 준비하기

처리기가 원본으로 인라인으로 유지되지 않고 컴파일되어 스테이지에 복사되는 저장 프로시저를 생성할 계획이라면 JAR 파일에서 클래스를 컴파일하고 패키징해야 하며 JAR 파일을 스테이지로 업로드해야 합니다.

  1. 처리기 코드 컴파일 및 패키징하기

    저장 프로시저를 더 쉽게 설정하려면 저장 프로시저에 필요한 모든 종속성을 포함하는 JAR 파일을 빌드합니다. 나중에 JAR 파일을 스테이지에 업로드하고 CREATE PROCEDURE 문에서 JAR 파일을 가리켜야 합니다. 업로드하고 가리킬 JAR 파일이 더 적은 경우에는 이 프로세스가 더 간단합니다.

    • sbt을 사용하여 종속성이 있는 JAR 파일을 빌드합니다.

      SBT를 사용하여 코드를 빌드하고 패키지하는 경우 sbt-assembly 플러그인 을 사용하면 모든 종속성을 포함하는 JAR 파일을 만들 수 있습니다. 자세한 내용은 sbt로 Scala 처리기 코드 패키징하기 섹션을 참조하십시오.

    • Maven을 사용하여 종속성이 있는 JAR 파일을 빌드합니다.

      Maven을 사용하여 코드를 빌드하고 패키지하는 경우 Maven Assembly Plugin 을 사용하면 모든 종속성을 포함하는 JAR 파일을 만들 수 있습니다. 자세한 내용은 Maven으로 Java 또는 Scala 처리기 코드 패키징하기 섹션을 참조하십시오.

    • 다른 도구를 사용하여 종속성이 있는 JAR 파일을 빌드합니다.

      SBT 또는 Maven을 사용하지 않는 경우, 모든 종속성을 사용하여 JAR 파일을 빌드하는 방법에 대한 지침은 해당 빌드 도구의 설명서를 참조하십시오.

      예를 들어 IntelliJ IDEA 프로젝트(IntelliJ의 SBT 프로젝트가 아님)를 사용하는 경우 아티팩트 구성 설정에 대한 지침 을 참조하십시오.

  2. 스테이지에 파일을 업로드합니다.

    프로시저의 논리와 기타 종속성(있는 경우)을 프로시저에서 사용할 수 있도록 하려면 필요한 파일을 스테이지에 업로드해야 합니다. 자세한 내용은 코드에 종속성을 사용할 수 있도록 만들기 섹션을 참조하십시오.