고성능 외부 함수 설계하기

고성능 외부 함수 설계하기 (Designing high-performance external functions)

이 문서는 외부 함수의 동시성(concurrency), 신뢰성(reliability), 확장성(scalability)에 대한 정보를 다루며, 비동기(asynchronous) 외부 함수 사용에 관한 내용도 포함해요. 외부 함수를 운영 환경에서 안정적으로 쓰려면 이런 특성들을 이해하는 게 중요해요.

출처: Snowflake SQL Reference

본문

비동기 vs. 동기 원격 서비스 (Asynchronous vs. Synchronous remote services)

원격 서비스(remote service)는 동기(synchronous) 또는 비동기(asynchronous)일 수 있어요.

  • synchronous(동기): 동기 원격 서비스에 대한 호출은 블로킹(blocking) 호출이에요. 원격 서비스는 결과가 준비될 때까지 어떤 응답도 보내지 않아요. 해당 서비스는 폴링(poll)할 수 없어요. 동기 코드는 비동기 코드보다 구현하기 쉽습니다.
  • asynchronous(비동기): 비동기 원격 서비스는 호출자가 결과를 기다리는 동안 폴링할 수 있어요. 비동기 처리는 타임아웃에 대한 민감도를 줄여줘요.

비동기 서비스에 대한 자세한 내용은 Microsoft의 비동기 요청-응답 패턴(Asynchronous Request-Reply Pattern) 설명을 참고하세요. (이 정보는 Microsoft Azure에만 국한되지는 않아요.)

동기 원격 서비스는 HTTP POST 요청을 받아 요청을 처리하고 결과를 반환해요. 데이터를 처리하는 데 걸리는 시간에 따라, 요청이 수신된 시점과 결과가 반환되는 시점 사이에 상당한 지연이 있을 수 있어요.

비동기 원격 서비스는 HTTP POST 요청을 받고 (보통 거의 즉시) 요청을 받았다는 확인(acknowledgement)을 반환해요. 그런 다음 호출자(Snowflake)는 폴링 루프를 실행해서 비동기 처리의 상태를 확인하기 위해 하나 이상의 HTTP GET 요청(보통 각 요청 사이에 상당한 지연 포함)을 발행해요. GET은 요청 본문에 어떤 데이터도 보내지 않지만, 원래 POST와 동일한 헤더를 포함해요.

비동기 원격 서비스는 원격 서비스가 프록시 서비스(예: Amazon API Gateway) 같은 구성 요소에 내장된 타임아웃을 초과할 때 유용해요.

원격 서비스가 반드시 순수하게 동기거나 순수하게 비동기일 필요는 없어요. 원격 서비스는 요청의 데이터 양, 처리 중인 다른 요청의 수 등과 같은 요인에 따라 다른 시점에 동기적으로, 또 비동기적으로 동작할 수 있어요.

Snowflake의 외부 함수 구현은 일반적으로 동기 및 비동기 타사 함수 라이브러리 모두와 호환돼요. 위 다이어그램은 동기와 비동기 처리를 대조해요. 위쪽 경로는 동기이고, 아래쪽 경로(하나 이상의 HTTP GET 요청 포함)는 비동기예요. 동기 및 비동기 외부 함수의 예시를 보려면 Snowflake 샘플 함수(Snowflake Sample Functions)를 참고하세요.

동기 원격 서비스 (Synchronous remote service)

사용자가 외부 함수를 호출하기 전에 개발자와 Snowflake 계정 관리자는 Snowflake가 프록시 서비스에 접근할 수 있도록 구성해야 해요. 일반적으로 이 단계들은 대략 아래에 표시된 순서대로 수행됩니다(위 다이어그램의 오른쪽에서 시작해 Snowflake 쪽으로 왼쪽 이동):

  • 개발자가 원격 서비스를 작성해야 하며, 그 원격 서비스는 HTTPS 프록시 서비스를 통해 노출되어야 해요. 예를 들어 원격 서비스는 AWS Lambda에서 실행되는 Python 함수일 수 있고, Amazon API Gateway의 리소스를 통해 노출될 수 있어요.
  • Snowflake에서 ACCOUNTADMIN 또는 CREATE INTEGRATION 권한이 있는 역할이 프록시 서비스와 통신할 수 있게 하는 인증 정보를 포함한 "API integration" 객체를 만들어야 해요. API 통합은 SQL 명령 CREATE API INTEGRATION으로 만들어집니다.
  • Snowflake 사용자가 SQL 명령 CREATE EXTERNAL FUNCTION을 실행해야 해요. 사용자는 API 통합에 USAGE 권한이 있고 함수를 만들기에 충분한 권한이 있는 역할을 사용해야 해요.

참고: CREATE EXTERNAL FUNCTION 명령은 "Snowflake 외부에서 실행될" 코드를 로드한다는 의미의 외부 함수를 실제로 만들지는 않아요. 대신 CREATE EXTERNAL FUNCTION 명령은 Snowflake 외부에서 실행되는 코드를 간접적으로 참조하는 데이터베이스 객체를 만들어요. 더 정확히 말하면, CREATE EXTERNAL FUNCTION 명령은 다음을 포함하는 객체를 만듭니다:

  • 중계 함수(relay function)로 동작하는 HTTPS 프록시 서비스의 리소스 URL
  • 프록시 서비스에 인증할 때 사용할 API 통합의 이름
  • 사실상 원격 서비스의 별칭(alias)이 되는 이름. 이 별칭은 SQL 명령에서 사용됩니다. 예: SELECT MyAliasForRemoteServiceXYZ(col1) ...;

Snowflake의 별칭, HTTPS 프록시 서비스 리소스의 이름, 원격 서비스의 이름은 모두 다를 수 있어요. (세 개 모두에 같은 이름을 사용하면 관리를 단순화할 수 있지만요.)

위에서 설명한 단계가 외부 함수를 실행하는 가장 일반적인 방법이지만, 일부 변형이 허용됩니다. 예를 들어:

  • 원격 서비스가 체인의 마지막 단계가 아닐 수도 있어요. 원격 서비스가 작업의 일부를 하기 위해 또 다른 원격 서비스를 호출할 수 있어요.
  • 원격 서비스가 JSON 형식의 데이터를 받고 반환하지 않는다면, HTTPS 프록시 서비스의 리소스(중계 함수)가 데이터를 JSON 형식에서 다른 형식으로 변환(그리고 반환된 데이터를 다시 JSON으로 변환)할 수 있어요.
  • Snowflake는 원격 서비스가 부작용(side effect)이 없고 상태 정보를 유지하지 않는 진짜 함수(0개 이상의 입력 파라미터를 받아 출력을 반환하는 코드 조각)처럼 동작할 것을 권장하지만, 엄격하게 요구되지는 않아요. 원격 서비스는 다른 작업을 수행할 수도 있어요. 예를 들어 데이터의 어떤 값(온도 판독값 등)이 위험할 정도로 높으면 경고를 보내는 등의 작업을 할 수 있어요. 드물게 원격 서비스가 발행된 경고의 총 개수 같은 상태 정보를 유지할 수도 있어요.

비동기 원격 서비스 (Asynchronous remote service)

비동기 원격 서비스는 원격 서비스가 프록시 서비스 같은 구성 요소에 내장된 타임아웃을 초과할 때 유용해요. 비동기 원격 서비스는 동일한 구성 요소(클라이언트, Snowflake, 프록시 서비스, 원격 서비스)와 위에서 설명한 것과 동일한 일반 단계를 포함해요. 그러나 HTTP 요청과 응답의 세부 사항은 다릅니다. 비동기 동작은 원격 서비스를 작성하는 사람(그리고 Snowflake)에 의해 구현돼요. SQL 문은 비동기 원격 서비스의 경우에도 동기 원격 서비스와 동일해요.

자신만의 원격 서비스를 작성하고 Snowflake의 비동기 처리와 호환되도록 만들고 싶다면, 원격 서비스를 다음과 같이 동작하도록 작성하세요:

  • 특정 배치의 행에 대한 HTTP POST를 처음 받으면 원격 서비스는 HTTP 코드 202("Processing...")를 반환해요.
  • POST 이후 출력이 준비되기 전에 어떤 HTTP GET 요청이라도 받으면 원격 서비스는 HTTP 코드 202를 반환해요.
  • 모든 출력 행을 생성한 후에는 같은 배치 ID를 가진 다음 HTTP GET을 기다렸다가 받은 행과 함께 HTTP 코드 200("Successful completion...")을 반환해요.

간단히 말하면, 받은 각 배치에 대해 원격 서비스는 결과가 준비될 때까지 202를 반환하고, 그 후 다음 GET이 결과와 HTTP 200을 받아요.

각 배치에 대해 Snowflake는 비동기 원격 서비스와 다음과 같이 협력해요:

  • Snowflake는 처리할 데이터와 고유한 배치 ID를 포함하는 HTTP POST를 보내요.
  • Snowflake가 HTTP 202 응답을 받으면 다음 중 하나가 참이 될 때까지 루프를 돌아요:
    • Snowflake가 데이터와 HTTP 200을 받는 경우
    • Snowflake의 내부 타임아웃에 도달한 경우
    • Snowflake가 오류(예: HTTP 응답 코드 5XX)를 받는 경우

루프의 각 반복에서 Snowflake는 지연 후 해당 HTTP POST의 배치 ID와 같은 배치 ID를 포함하는 HTTP GET을 발행해서, 원격 서비스가 올바른 배치에 대한 정보를 반환할 수 있게 해요. 루프 내부의 지연은 처음에는 짧지만, Snowflake의 타임아웃에 도달할 때까지 HTTP 202 응답을 받을 때마다 길어져요.

  • HTTP 200이 반환되기 전에 Snowflake의 타임아웃에 도달하면 Snowflake는 SQL 쿼리를 중단(abort)해요. 현재 Snowflake의 타임아웃은 10분(600초)이며 사용자가 구성할 수 없어요. 이 타임아웃은 향후 변경될 수 있어요.

참고: 쿼리가 타임아웃에 도달하는 빈도는 부분적으로 원격 서비스의 확장성에 달려 있어요. 원격 서비스가 자주 타임아웃된다면 "Scalability" 절의 논의도 함께 참고하세요.

확장성 (Scalability)

원격 서비스, 프록시 서비스, 그리고 Snowflake와 원격 서비스 사이의 다른 모든 단계는 전송되는 최대 워크로드(peak workloads)를 처리할 수 있어야 해요. 일부 클라우드 플랫폼 제공자는 프록시 서비스와 원격 서비스에 대한 기본 사용 제한 또는 다른 할당량(quota)을 가지며, 이는 외부 함수 호출의 처리량을 제한할 수 있어요.

더 큰 Snowflake 웨어하우스 크기는 요청이 전송되는 동시성을 증가시킬 수 있어서, 프록시 서비스의 할당량을 초과할 수 있어요. 사용자는 쿼리 프로필(Query Profile)의 Retries due to transient errors 값을 보면 Snowflake가 (조절(throttling) 또는 다른 오류로 인해) 요청 배치를 보내는 것을 몇 번이나 재시도해야 했는지 확인할 수 있어요.

원격 서비스의 확장성 (Scalability of the remote service)

원격 서비스를 작성하는 개발자는 다음을 고려해야 해요:

  • 원격 서비스가 호출되는 빈도
  • 호출당 전송되는 행(row)의 수
  • 각 행을 처리하는 데 필요한 리소스
  • 호출의 시간 분포(최대 vs. 평균)

호출자가 몇 명의 개발자와 테스터에서 전체 조직으로 바뀌면서 용량이 시간이 지남에 따라 늘어나야 할 수 있어요. 원격 서비스가 여러 조직에서 사용된다면 조직 수가 증가함에 따라 용량이 증가해야 할 수 있어요. 게다가 조직의 수와 다양성이 증가하면 워크로드의 크기와 시기가 예측하기 더 어려워질 수 있어요.

원격 서비스 제공자는 최대 워크로드를 처리할 충분한 용량을 제공할 책임이 있어요. 서비스를 확장하는 데 다양한 기법을 사용할 수 있어요. 원격 서비스가 원격 서비스 작성자가 관리한다면, 작성자가 피크를 처리할 충분한 용량으로 서비스를 명시적으로 프로비저닝해야 할 수 있어요. 또는 작성자가 AWS Lambda 같은 호스팅된 자동 확장/탄력적 서비스를 사용하기로 결정할 수도 있어요.

원격 서비스는 과부하 상태일 때 HTTP 응답 코드 429를 반환해야 해요. Snowflake가 HTTP 429를 보면 행을 보내는 속도를 낮추고, 성공적으로 처리되지 않은 행 배치를 재시도해요. 확장성 문제 해결에 대한 자세한 내용은 확장성 및 성능 문제 해결(Troubleshooting scalability and performance issues) 문서를 참고하세요.

원격 서비스 호출이 시스템이 전반적으로 과부하된 것이 아니라 각 개별 호출이 오래 걸리기 때문에 타임아웃된다면, 비동기 원격 서비스(Asynchronous remote service) 구축 방법 설명을 참고하세요.

프록시 서비스의 확장성 (Scalability of the proxy service)

프록시 서비스도 확장 가능해야 해요. 다행히 주요 클라우드 제공자의 프록시 서비스는 일반적으로 확장 가능해요. 하지만 Amazon API Gateway와 Azure API Management를 포함한 일부 프록시 서비스는 기본 사용 제한이 있어요. 요청 속도가 제한을 초과하면 이 프록시 서비스들은 요청을 조절(throttle)해요. 필요하다면 AWS나 Azure에 프록시 서비스의 할당량을 늘려 달라고 요청해야 할 수 있어요.

외부 함수를 개발하거나 관리하는 사용자는 다음과 같은 플랫폼별 정보를 기억해야 해요:

  • Amazon API Gateway: Amazon API Gateway는 그 자체가 사용자 워크로드에 맞춰 자동 확장되는 관리형 AWS 서비스예요. 사용자는 API Gateway의 다양한 제한(limits of API Gateway)에 익숙해야 해요. Amazon API Gateway는 원격 서비스를 확장하는 데 도움이 되도록 구성할 수 있어요. 구체적으로, API Gateway는 필요할 때 원격 서비스의 부하를 줄이기 위해 요청의 캐싱(caching) 및/또는 조절(throttling)을 활성화하도록 구성할 수 있어요:

    • 캐싱 활성화(Enable caching)
    • 조절 활성화(Enable throttling)

    조절은 타임아웃과 재시도에 영향을 줄 수 있으므로, 사용자는 Snowflake가 타임아웃과 재시도를 처리하는 방법에 대한 정보도 검토하고 싶을 수 있어요:

    • 타임아웃 오류 고려하기(Account for timeout errors)
    • 원격 서비스가 각 행을 정확히 한 번만 받는다고 가정하지 마세요(Do not assume that the remote service is passed each row exactly once)
  • Azure API Management Service: Azure API Management의 경우 제한은 서비스에 선택된 SKU에 따라 달라져요. 제한은 Azure Subscription Service Limits 문서의 API Management limits 섹션에 문서화돼 있어요. 조절은 타임아웃과 재시도에 영향을 줄 수 있으므로, 사용자는 Snowflake가 타임아웃과 재시도를 처리하는 방법에 대한 정보도 검토하고 싶을 수 있어요:

    • 타임아웃 오류 고려하기(Account for timeout errors)
    • 원격 서비스가 각 행을 정확히 한 번만 받는다고 가정하지 마세요(Do not assume that the remote service is passed each row exactly once)

확장성 및 성능 문제 해결 (Troubleshooting scalability and performance issues)

  • QUERY_HISTORY 함수, QUERY_HISTORY_BY_* 함수를 사용해 성능 특성을 관찰하고 성능 문제를 디버깅하는 데 도움을 받으세요.
  • Query History 페이지를 사용해 요청당 평균 지연 시간(latency)을 확인하세요.
  • Query History 페이지를 사용해 "Do not assume that the remote service is passed each row exactly once" 섹션에 나열된 것을 포함해 일시적 오류로 인해 요청이 몇 번 재시도됐는지 확인하세요.
  • 원격 서비스 리소스 사용량을 모니터링해서 부하에 따라 어떻게 확장되는지 확인하고, 원격 서비스가 최대 부하를 처리할 충분한 용량이 있는지 확인하세요.
  • Amazon API Gateway 또는 원격 서비스에서 로깅을 활용해 요청별 세부 사항을 얻으세요.
  • Snowflake가 원격 서비스로 요청을 보내는 동시성을 제어하세요. 자세한 내용은 동시성(concurrency) 섹션을 참고하세요.
  • 원격 서비스가 과부하 상태일 때 HTTP 응답 코드 429를 반환하세요. 지연 시간이 증가할 때까지 기다리는 대신 가능한 한 빨리 반환하세요.
  • 프록시 서비스 타임아웃을 고려하세요. 예를 들어 2020년 7월 기준 Amazon API Gateway의 타임아웃은 30초예요. 타임아웃은 원격 서비스의 과부하를 포함한 다양한 요인으로 인해 발생할 수 있어요.

Snowflake는 합리적인 시간 내에 일시적 오류/타임아웃을 재시도하려 하지만, 서비스가 계속 과부하 상태이고 재시도가 성공하지 못하면 결국 쿼리는 중단돼요.

동시성 (Concurrency)

리소스 요구 사항은 행이 여러 호출에 분산되는 방식(각각 몇 개의 행을 가진 많은 병렬 호출 vs. 같은 총 행 수를 가진 하나의 호출)에 따라 달라져요. 높은 용량을 지원하는 시스템이 반드시 높은 동시성을 지원하는 것은 아니며 그 반대도 마찬가지예요. 필요한 최대 동시성과 가장 큰 합리적인 개별 워크로드를 추정하고, 두 유형의 피크를 모두 처리할 충분한 리소스를 제공해야 해요.

또한 동시성 추정은 Snowflake가 외부 함수 호출을 병렬화할 수 있다는 점을 고려해야 해요. 한 사용자의 단일 쿼리가 원격 서비스에 대한 여러 병렬 호출을 유발할 수 있어요. Snowflake에서 프록시 서비스 또는 원격 서비스로의 동시 호출 수에 영향을 주는 여러 요인이 있습니다:

  • 외부 함수로 쿼리를 실행하는 동시 사용자 수
  • 각 사용자 쿼리의 크기
  • 가상 웨어하우스의 컴퓨팅 리소스 양(즉 웨어하우스 크기)
  • 웨어하우스의 수

외부 함수에 부작용(side effects)이 있다면 동시성을 제대로 처리하는 것이 특히 복잡할 수 있어요. 사용자 행이 처리되는 순서에 따라 결과가 달라질 수 있어요. (Snowflake는 부작용이 있는 원격 서비스를 작성하거나 사용하는 것을 피할 것을 권장해요.)

신뢰성 (Reliability)

원격 서비스가 실행되는 위치에 따라 다음을 고려해야 할 수 있어요:

  • 신뢰성
  • 오류 처리
  • 디버깅
  • 업그레이드(원격 서비스가 새 기능을 추가하거나 버그 수정이 필요할 수 있는 경우)

원격 서비스가 상태 비저장(stateless)이 아니라면, 실패 후 복구도 고려해야 할 수 있어요. (Snowflake는 원격 서비스가 상태 비저장일 것을 강력히 권장해요.) 타임아웃과 재시도에 대한 정보는 타임아웃 오류 고려하기(Account for timeout errors)와 원격 서비스가 각 행을 정확히 한 번만 받는다고 가정하지 마세요(Do not assume that the remote service is passed each row exactly once) 문서를 참고하세요.

더 알아보기 (Learn more)

  • 외부 함수 소개 (external-functions-introduction)
  • 원격 서비스 입출력 데이터 형식 (external-functions-data-format)
  • 외부 함수 보안 (external-functions-security)
  • 외부 함수 트랜슬레이터 (external-functions-translators)