외부 함수 모범 사례
외부 함수 모범 사례 (External Functions Best Practices)
이 문서는 외부 함수의 효율성을 높이고, 원격 서비스가 Snowflake와 호환되도록 설계되지 않았을 때 발생할 수 있는 예상치 못한 결과를 방지하기 위한 모범 사례를 정리해 드려요. 배치 API 사용, 행 단위 처리, 무상태(stateless) 설계, 부작용 회피, 지연 시간 최소화 등 실무에서 꼭 알아야 할 원칙을 하나씩 설명해요.
본문
이 문서는 효율성을 높이고, 원격 서비스가 Snowflake와 호환되도록 설계되지 않았을 때 발생할 수 있는 예상치 못한 결과를 방지하기 위한 모범 사례를 다뤄요.
추가 모범 사례는 다음 문서에서 찾을 수 있어요:
(이것은 Microsoft Azure 문서이지만, 그 안의 많은 조언은 모든 클라우드 플랫폼의 원격 서비스에 적용돼요.)
가능하면 원격 서비스의 배치 API를 사용하세요
일부 원격 서비스는 배치 모드와 단일 행 모드를 모두 제공해요. 외부 함수를 사용하는 쿼리가 여러 행을 보낼 것으로 예상된다면, Snowflake는 성능 향상을 위해 원격 서비스의 배치 모드를 사용하길 권장해요.
이 규칙은 다음 경우에는 반드시 적용되지는 않아요:
- 각 행이 매우 큰 경우 (예: 수백 킬로바이트 이상).
- 원격 서비스가 행을 개별적으로 받을 때와 배치로 받을 때 다르게 처리하는 경우. (자세한 내용은 한 번에 한 행씩 처리하기를 참조하세요.)
한 번에 한 행씩 처리하기
네트워킹 오버헤드를 최소화하기 위해 Snowflake는 일반적으로 원격 서비스로 보낼 행을 배치로 묶어요. 배치의 개수와 각 배치의 크기는 달라질 수 있어요.
또한 배치의 순서와 배치 내 행의 순서도 달라질 수 있어요. 쿼리에 ORDER BY 절이 있어도, ORDER BY는 보통 외부 함수가 호출된 후에 적용돼요.
배치 크기와 행 순서가 보장되지 않으므로, 특정 행의 값이 같은 배치나 이전 배치의 다른 행에 의존하는 함수를 작성하면 비결정적(non-deterministic) 결과가 발생할 수 있어요.
Snowflake는 원격 서비스가 각 행을 독립적으로 처리하길 강력히 권장해요. 각 입력 행의 반환 값은 다른 입력 행이 아닌 오직 그 입력 행에만 의존해야 해요. (현재 외부 함수는 예를 들어 윈도우 함수를 지원하지 않아요.)
배치 크기가 보장되지 않으므로, 배치 수를 세는 것은 의미가 없다는 점도 주의하세요.
외부 함수가 무상태(stateless)인지 확인하기를 참조하세요.
원격 서비스가 각 행을 정확히 한 번만 전달받는다고 가정하지 마세요
Snowflake가 원격 서비스를 호출하고 원격 서비스가 요청을 받아 결과를 반환했는데도, 일시적인 네트워크 문제로 Snowflake가 결과를 받지 못하면 Snowflake가 요청을 반복할 수 있어요. Snowflake가 재시도하면 원격 서비스는 같은 행을 두 번(또는 그 이상) 볼 수 있어요.
이로 인해 예상치 못한 효과가 발생할 수 있어요. 예를 들어 원격 서비스가 같은 값에 대해 두 번 이상 호출될 수 있으므로, 고유 ID를 할당하는 원격 서비스는 그 ID 시퀀스에 공백(gap)이 생길 수 있어요. 어떤 경우에는 요청 헤더의 sf-external-function-query-batch-id 필드에서 배치 ID를 추적해 특정 배치의 행이 이전에 처리되었는지 판단함으로써 이런 효과를 줄일 수 있어요. Snowflake가 특정 배치에 대한 요청을 재시도할 때, 그 배치에 대해 이전에 사용했던 것과 같은 배치 ID를 사용해요.
Snowflake는 다음 오류를 받으면 재시도해요:
- 모든 일시적인 네트워크 전송 오류.
- 429 상태 코드로 실패하는 모든 요청.
- 5XX 상태 코드로 실패하는 모든 요청.
요청은 총 재시도 타임아웃(total retry timeout)에 도달할 때까지 재시도돼요. 총 재시도 타임아웃은 사용자가 구성할 수 없어요. Snowflake는 향후 이 한도를 조정할 수 있어요.
성공적인 재시도 없이 총 재시도 타임아웃에 도달하면 쿼리가 실패해요.
원격 서비스가 작동 중인데 외부 함수 호출이 타임아웃되고, Snowflake와 원격 서비스 사이의 모든 요소가 정상으로 보인다면, 더 작은 배치 크기를 시도해 타임아웃 오류가 줄어드는지 확인해 볼 수 있어요.
최대 배치 크기 설정 방법은 CREATE EXTERNAL FUNCTION을 참조하세요.
외부 함수가 무상태(stateless)인지 확인하세요
일반적으로 외부 함수(원격 서비스 포함)는 상태 정보를 저장하지 않아야 해요. 다음 두 가지 모두 해당돼요:
- 내부 상태 (원격 서비스가 내부적으로 저장하는 상태).
- 외부 상태 (원격 서비스 바깥에 저장된 상태, 예를 들어 상태를 유지하는 다른 원격 서비스로 보내거나 거기서 읽는 상태 정보).
원격 서비스가 상태 정보를 변경한 다음 그 정보를 사용해 향후 출력에 영향을 주면, 함수가 예상과 다른 값을 반환할 수 있어요.
예를 들어 내부 카운터를 가지고 원격 서비스가 시작된 이후 받은 행 수를 반환하는 간단한 원격 서비스를 생각해 봐요. 일시적인 네트워크 문제가 있고 Snowflake가 같은 데이터로 요청을 반복하면, 원격 서비스는 다시 보내진 행을 두 번(또는 그 이상) 셀 거예요.
외부 상태와 관련된 예는 부작용 피하기를 참조하세요.
함수가 무상태가 아닌 드문 경우에는, 호출자용 문서에 함수가 무상태가 아니라고 명확히 명시해야 하고 함수는 volatile로 표시되어야 해요.
원격 서비스가 요청을 비동기적으로 처리한다면, 원격 서비스 작성자는 일시적으로 상태를 저장하고 관리하도록 원격 서비스를 작성해야 해요. 예를 들어 원격 서비스는 HTTP POST 요청의 배치 ID를 저장해서, 같은 배치 ID로 HTTP GET이 수신되면 지정된 배치가 아직 처리 중일 때 원격 서비스가 HTTP 코드 202를 반환할 수 있게 해야 해요.
쿼리는 여러 이유로 중단될 수 있으므로, 원격 서비스가 결과 생성이 끝난 후 최종 GET이 도착한다는 보장은 없어요. 비동기 요청에 대한 상태를 저장하는 원격 서비스는 결국 타임아웃되어 그 내부 상태를 정리해야 해요. 최적의 타임아웃은 향후 변경될 수 있지만, 현재 Snowflake는 비동기 요청 정보를 삭제하기 전에 최소 10분, 가급적 12시간 동안 보존하길 권장해요.
부작용(side-effect) 피하기
외부 함수(원격 서비스 포함)는 외부 상태를 변경하는 것 같은 부작용을 피해야 해요. (원격 서비스 바깥에 저장된 정보)
예를 들어 원격 서비스가 범위를 벗어난 값을 정부 기관에 보고한다면, 그것은 부작용이에요.
부작용이 유용할 수도 있지만, 외부 함수 호출의 부작용은 항상 예측 가능하지는 않아요. 예를 들어 익명화된 건강 기록을 분석해 진단을 반환하는 원격 서비스를 호출한다고 가정해 봐요. 진단 결과 환자가 전염병에 걸렸다면 그 진단이 해당 질병의 사례 수를 세는 기관에 보고된다고 가정해 봐요. 이것은 유용한 부작용이에요. 하지만 이런 경우 다음과 같은 문제에 취약해요:
- 외부 함수 호출이 롤백되는 트랜잭션 안에 있으면, 그 부작용은 롤백되지 않아요.
- 같은 행으로 원격 서비스가 두 번 이상 호출되면(예: 일시적인 네트워크 실패와 재시도), 부작용이 두 번 이상 발생할 수 있어요. 예를 들어 감염된 환자가 통계에 두 번 집계될 수 있어요.
행이 과대 집계되는 대신 과소 집계될 수 있는 상황도 있어요.
함수에 부작용이 있는 아주 드문 경우에는, 호출자용 문서에 부작용이 무엇인지 명확히 명시해야 하고 함수는 volatile로 표시되어야 해요.
함수를 volatile 또는 immutable로 분류하세요
함수는 volatile 또는 immutable로 분류할 수 있어요. (CREATE EXTERNAL FUNCTION 문장은 사용자가 함수가 volatile인지 immutable인지 지정할 수 있게 해요.)
외부 함수가 immutable로 간주되려면 다음 기준을 충족해야 해요:
- 같은 입력 값이 주어지면 함수는 같은 출력 값을 반환해요. (예: SQRT 함수는 같은 입력이 주어지면 같은 출력을 반환하지만, CURRENT_TIMESTAMP 함수는 같은 입력이 주어져도 반드시 같은 출력을 반환하지는 않아요.)
- 함수에 부작용이 없어요. (자세한 내용은 부작용 피하기를 참조하세요.)
함수가 이 두 기준을 충족하면, Snowflake는 원격 서비스로 보내는 행 또는 배치 수를 줄이기 위해 특정 유형의 최적화를 사용할 수 있어요. (이 최적화는 시간이 지나면서 발전할 수 있으며, 여기서 자세히 설명되지는 않아요.)
Snowflake는 immutability 또는 immutability에 영향을 주는 요소(예: 부작용)를 감지하거나 강제할 수 없어요. 원격 서비스 작성자는 원격 서비스가 immutable로 표시될 기준을 충족하는지 문서화해야 해요. 원격 서비스에 부작용이 있다면, 그 원격 서비스를 호출하는 외부 함수는 함수 호출이 같은 입력 값에 대해 같은 출력 값을 반환하더라도 volatile로 표시되어야 해요. 원격 서비스가 immutable인지 확실하지 않다면, 그 원격 서비스를 호출하는 모든 외부 함수는 volatile로 표시되어야 해요.
타임아웃 오류 고려하기
외부 함수 호출은 Snowflake, 원격 서비스, 프록시 서비스, 그리고 잠재적으로 체인의 다른 요소들을 포함해요. 이 요소들 중 어느 것도 특정 함수 호출이 얼마나 오래 걸려야 하는지 모르므로, 정확히 언제 기다리는 것을 멈추고 타임아웃 오류를 반환해야 할지도 알지 못해요. 각 단계는 자체적인 독립 타임아웃을 가질 수 있어요. 타임아웃과 재시도에 대한 자세한 내용은 타임아웃 오류와 재시도 고려를 참조하세요.
지연 시간 최소화하기
외부 함수 호출의 지연 시간을 최소화하고 성능을 높이기 위해, Snowflake는 가능하면 다음을 권장해요:
-
API Gateway를 가장 자주 호출하는(또는 가장 많은 데이터를 보내는) Snowflake 인스턴스와 같은 클라우드 플랫폼과 리전에 두세요.
-
원격 서비스를 직접 작성했다면(기존 서비스를 사용하는 대신), 그 원격 서비스를 호출되는 곳과 같은 클라우드 플랫폼과 리전에 배포하세요.
-
가능한 한 적은 데이터를 보내세요. 예를 들어 원격 서비스가 입력 값을 검사하고 그 일부에만 작동할 것이라면, 모든 행을 원격 서비스에 보내 필터링하게 하는 것보다 SQL에서 필터링해 관련 행만 원격 서비스에 보내는 것이 보통 더 효율적이에요.
또 다른 예로, 큰 반정형(semi-structured) 데이터 값을 포함하는 열을 처리하고 있고 원격 서비스가 그 데이터 값의 작은 조각에만 작동할 것이라면, 전체 열을 보내 원격 서비스가 처리 전에 그 작은 조각을 추출하게 하는 것보다 Snowflake SQL로 관련 조각을 추출해 그 조각만 보내는 것이 보통 더 효율적이에요.
외부 함수를 한 단계씩 개발하고 테스트하세요
Snowflake는 Snowflake로 테스트하기 전에 Snowflake 없이 테스트하길 권장해요.
외부 함수 개발 초기 단계에는 클라우드 플랫폼 프록시 서비스 콘솔(예: Amazon API Gateway 콘솔)과 원격 서비스 개발 콘솔(예: AWS Lambda 콘솔)을 사용해 프록시 서비스와 원격 서비스를 개발·테스트하는 데 도움을 받아요.
예를 들어 Lambda 함수를 개발했다면, Snowflake에서 호출해 테스트하기 전에 Lambda 콘솔을 통해 광범위하게 테스트하고 싶을 거예요.
프록시 서비스 콘솔과 원격 서비스 콘솔을 통해 테스트하는 것은 보통 다음과 같은 장점이 있어요:
- 문제의 원인을 찾을 곳이 적기 때문에 문제 진단이 더 쉬울 수 있어요.
- 데이터 페이로드를 보면 유용한 디버깅 정보를 얻을 수 있어요. Snowflake는 오류 메시지에서 데이터 페이로드의 어떤 부분도 보여주지 않아요. 이는 보안을 강화하지만 디버깅을 느리게 만들 수 있어요.
- Snowflake는 HTTP 5xx 오류를 자동 재시도하는데, 이는 어떤 상황에서는 디버깅을 더 느리거나 어렵게 만들 수 있어요.
- Snowflake를 통한 테스트는 클라우드 플랫폼 크레딧 외에 Snowflake 크레딧도 소모해요.
물론 Snowflake 없이 원격 서비스와 프록시 서비스를 최대한 테스트한 후에는 Snowflake로도 테스트해야 해요. Snowflake로 테스트하는 장점은 다음과 같아요:
- 외부 함수에 관련된 모든 단계를 테스트하게 돼요.
- Snowflake 테이블을 데이터 소스로 사용하면 대량의 데이터로 테스트하기 쉬워져 외부 함수 성능에 대한 현실적인 추정을 얻을 수 있어요.
다음 테스트 케이스를 고려하세요:
- NULL 값.
- "빈" 값 (예: 빈 문자열, 빈 반정형 데이터 타입).
- 해당된다면 아주 긴 VARCHAR 및 BINARY 값.
원격 서비스를 비동기로 만드세요
원격 서비스를 작성 중이고, 원격 서비스가 예상 타임아웃 안에 결과를 반환하지 못할 수 있다면, 원격 서비스를 비동기로 만드는 것을 고려해 보세요. 자세한 내용은 비동기 vs. 동기 원격 서비스를 참조하세요.
외부 함수 인자가 원격 서비스가 파싱하는 인자와 일치하는지 확인하세요
외부 함수에 인자를 전달하거나 외부 함수에서 인자를 받을 때, 데이터 타입이 적절한지 확인하세요. 전송된 값이 수신되는 데이터 타입에 맞지 않으면, 값이 잘리거나 손상되거나 원격 서비스 호출이 실패할 수 있어요.
예를 들어 일부 Snowflake SQL 숫자 데이터 타입은 일반적으로 사용되는 JavaScript 데이터 타입보다 더 큰 값을 저장할 수 있으므로, JavaScript에서 큰 숫자를 JSON에서 역직렬화하는 것은 특히 민감해요.
원격 서비스에 보내는 인자의 개수, 데이터 타입 또는 순서를 변경했다면, 외부 함수에도 그에 해당하는 변경을 하는 것을 잊지 마세요. 현재 ALTER FUNCTION 명령에는 파라미터를 변경하는 옵션이 없으므로, 인자를 변경하려면 외부 함수를 DROP하고 다시 CREATE해야 해요.
더 알아보기 (Learn more)
- CREATE EXTERNAL FUNCTION — 외부 함수 생성 구문과 옵션
- Writing external functions — 외부 함수 개요와 관련 문서 목록
- 고성능 외부 함수 설계하기 — 비동기·확장성·동시성·신뢰성 설계 팁
- AWS Management Console로 외부 함수 만들기 — 실제 생성 절차